fix(openai): artifact receipt의 중복 history gate를 제거한다

This commit is contained in:
toki 2026-08-15 14:48:18 +09:00
parent af96a1e03a
commit 450522186b
3 changed files with 63 additions and 2 deletions

View file

@ -645,7 +645,7 @@ func (s *artifactFrontierStore) consume(
return logicalRequestSnapshot{}, artifactDisposition{}, true, fmt.Errorf("artifact receipt rejected: result contains an explicit error signal")
}
snap, err := coordinator.consumeContinuationByLineage(ownerEdgeID, principalRef, lineage)
snap, err := coordinator.consumeArtifactContinuationByLineage(record.requestID, ownerEdgeID, principalRef, lineage)
if err != nil {
return logicalRequestSnapshot{}, artifactDisposition{}, true, err
}
@ -712,7 +712,11 @@ func (s *artifactFrontierStore) matchRecordLocked(
if record.protocol != protocol {
return nil, true, fmt.Errorf("%w: protocol changed", errLogicalRequestLineage)
}
if record.lineage != lineage.Prefix {
// Artifact receipts are already bound to the owner, principal, protocol,
// toolset, exact issued-call hash, call ids, and result matcher. The light
// flow also retains the immutable task independently, so caller SDK history
// reserialization is not an additional receipt boundary.
if record.lineage.Endpoint != lineage.Prefix.Endpoint || record.lineage.ToolsetDigest != lineage.Prefix.ToolsetDigest {
return nil, true, describeArtifactPrefixMismatch(record.lineage, lineage.Prefix)
}
return record, true, nil

View file

@ -234,6 +234,28 @@ func TestArtifactPairFrontierMatrix(t *testing.T) {
}
fixture.assertPhase(artifactPhaseLocalEligible)
})
t.Run("exact receipt tolerates caller history reserialization", func(t *testing.T) {
fixture := newArtifactPairFixture(t, endpoint, true)
ids := fixture.issuePair()
body := fixture.continuationBody([]artifactTestResult{
{id: ids[0], body: `{"written":true}`},
{id: ids[1], body: `{"written":true}`},
}, nil)
var envelope map[string]any
if err := json.Unmarshal(body, &envelope); err != nil {
t.Fatal(err)
}
messages := envelope["messages"].([]any)
messages[0].(map[string]any)["content"] = []any{map[string]any{
"type": "text", "text": "task", "cache_control": map[string]any{"type": "ephemeral"},
}}
body, _ = json.Marshal(envelope)
if _, _, err := fixture.continueRaw(body); err != nil {
t.Fatalf("exact receipt rejected reserialized history: %v", err)
}
fixture.assertPhase(artifactPhaseLocalEligible)
})
})
}
}

View file

@ -427,6 +427,41 @@ func (c *logicalRequestCoordinator) consumeContinuationByLineage(ownerEdgeID, pr
return target.snapshot(), nil
}
func (c *logicalRequestCoordinator) consumeArtifactContinuationByLineage(
requestID, ownerEdgeID, principalRef string,
lineage logicalRequestContinuationLineage,
) (logicalRequestSnapshot, error) {
c.mu.Lock()
defer c.mu.Unlock()
record, ok := c.requests[requestID]
if !ok || record.state != logicalRequestStateWaiting {
return logicalRequestSnapshot{}, errLogicalRequestNotFound
}
if record.ownerEdgeID != ownerEdgeID {
return logicalRequestSnapshot{}, errLogicalRequestOwnerMismatch
}
if record.principalRef != principalRef {
return logicalRequestSnapshot{}, errLogicalRequestPrincipal
}
if record.lineage.Endpoint != lineage.Prefix.Endpoint || record.lineage.ToolsetDigest != lineage.Prefix.ToolsetDigest {
return logicalRequestSnapshot{}, errLogicalRequestLineage
}
if record.expected == nil {
return logicalRequestSnapshot{}, errLogicalRequestNoFrontier
}
if record.expectedIssuedCallHash != lineage.IssuedCallHash || !sameLogicalRequestResultIDs(record.expected, lineage.ResultIDs) {
return logicalRequestSnapshot{}, errLogicalRequestFrontier
}
record.expected = nil
record.expectedIssuedCallHash = ""
record.lineage = lineage.Committed
record.activeStageID = ""
record.state = logicalRequestStateResumed
record.updatedAt = c.now()
return record.snapshot(), nil
}
func (c *logicalRequestCoordinator) snapshot(requestID string) (logicalRequestSnapshot, error) {
c.mu.Lock()
defer c.mu.Unlock()