Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions forge-cli/runtime/pdp_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ type pdpRequest struct {
Caller pdpCaller `json:"caller"`
Agent string `json:"agent"`
Session string `json:"session,omitempty"`
Context map[string]any `json:"context,omitempty"`
// InvocationID is the turn's correlation id. Sent so the platform-written
// tool_call_decided event carries the same invocation_id as this agent's
// pdp_decision / task_deferred events and groups with them in the console's
// invocation timeline (otherwise the platform verdict is "unattributed").
InvocationID string `json:"invocation_id,omitempty"`
Context map[string]any `json:"context,omitempty"`
}

type pdpCaller struct {
Expand Down Expand Up @@ -154,7 +159,11 @@ func (p *pdpResolver) Resolve(ctx context.Context, hctx *coreruntime.HookContext
Caller: pdpCaller{Subject: "agent:" + p.agentID},
Agent: p.agentID,
Session: hctx.TaskID,
Context: map[string]any{},
// Same source as the sibling pdp_decision event (emitPDPDecision uses
// hctx.CorrelationID) so the platform tool_call_decided and this agent's
// pdp_decision carry an IDENTICAL invocation_id and group together.
InvocationID: hctx.CorrelationID,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified this is the same source emitPDPDecision stamps on the agent-side pdp_decision (CorrelationID: hctx.CorrelationID), so the platform verdict and the agent record share an identical invocation_id and group together — exactly the attribution fix intended. No change to the fail-closed enforcement path. 👍

Context: map[string]any{},
}
body, err := json.Marshal(reqBody)
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions forge-cli/runtime/pdp_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ func TestPDPResolver_Allow(t *testing.T) {
if req.Caller.Subject != "agent:member-service" || req.Caller.EntitledAccounts != nil {
t.Errorf("caller = %+v, want subject agent:member-service, no entitled_accounts", req.Caller)
}
// The turn's correlation id + task id must be sent so the platform-written
// tool_call_decided event attributes to the same invocation as this agent's
// pdp_decision (else it lands in "unattributed events").
if req.InvocationID != "corr-1" {
t.Errorf("invocation_id = %q, want corr-1 (the hook correlation id)", req.InvocationID)
}
if req.Session != "task-1" {
t.Errorf("session = %q, want task-1", req.Session)
}
writeEnvelope(w, `{"decision":"allow","reason":"within policy","policy_version":7}`)
}))
defer srv.Close()
Expand Down
Loading