Skip to content

[Bug]: acs.decision trace event cannot carry reason_codes, policy_references, cited_provenance_ids, or chain_hash, so replay from the trace alone is not possible #228

Closed
0 of 1 issue completed

Description

@rocklambros

What is broken

The acs.decision span event has no attributes for reason_codes, policy_references (with policy_version), cited_provenance_ids, or chain_hash. All four are on every ACS response envelope. A reader of the trace cannot reconstruct the decision without going back to the Guardian's own logs.

Where

specification/v0.1.0/trace/otel-mapping.json, decision_event (required: acs.decision, acs.evaluator; conditional: acs.reasoning, acs.confidence, acs.evaluator_version, acs.model_id)
specification/v0.1.0/response-envelope.json, AcsResult.reason_codes, policy_references, cited_provenance_ids, chain_hash
docs/spec/trace/events.md, ACS-Trace conformance bar, requirement 3

What the specification says, and what happens instead

events.md requirement 3 for ACS-Trace: carry provenance facts forward onto the Trace event "so audit replay reconstructs the policy decision without consulting Guardian state separately." response-envelope.json says policy_references is what "audit replay walks" and that cited_provenance_ids gives audit tooling "one place to look for which provenance objects drove this verdict." Neither reaches the trace. An acs.decision event shows deny plus free text. It cannot say which policy fired, which provenance drove it, or which chain head the decision was committed under.

Impact on implementers

Auditors and verifiers consuming OTel from an ACS deployment, the use case the Trace pillar exists for, have to return to Guardian logs to answer "why." Guardian implementers who want to emit these fields today have no attribute names, so two conformant deployments will pick different ones and their traces stop being comparable, which is the property events.md says the shared vocabulary protects.

Direction: add conditional attributes to decision_event, populated when the response carries the field. Suggested names, open to the Spec leads: acs.reason_codes (string array), acs.policy.ids, acs.policy.versions, acs.policy.rule_ids (parallel string arrays from policy_references), acs.cited_provenance_ids (string array), acs.chain_hash. Additive, nothing on the wire changes. The same gap applies to the OCSF decision_class. I'll open a Discussion if the Spec leads want one before a PR.

Found while scoping the OTel export for #91. Related: #37 (a fail-open proceed has no trace shape at all), #142 and #199 (the reference omits chain_hash on responses).

Current Priority Scope

Feeds conformance evidence

Update, October 12. The change for this issue goes further than "additive" above. It also requires acs.request_id and acs.session.id on every step span, and it changes the OCSF decision class's required_fields. Both break existing Trace exporters. The wire is unchanged. The details and the open questions are in the comments below.

Activity

  1. added
    status:acceptedIn the backlog. Maintainers only
    scope:in-focusFeeds the ninety-day committed outcome. Maintainers only
    workstream:specOwning workstream. Maintainers only
    and removed
    status:needs-triageNot yet triaged. Applied by the issue forms
    on Oct 5, 2026
  2. self-assigned this
    on Oct 5, 2026
  3. MKaptein commented on Oct 8, 2026

    @MKaptein

    The added attributes look good to me.

    I do have a question; my understanding is that as suggested now we end up with acs.policy.ids, acs.policy.versions and acs.policy.rule_ids as "parallel" arrays created from policy_references. However, in response-envelope.json none of the fields of a policy_references entry are required, so one entry can have a policy_version or rule_id and the next one may not. If an exporter skips values, the three arrays risk getting different lengths and a reader of the trace may no longer tell which version or rule belongs to which policy (correct?).

    Would it help to state that the three arrays always have the same length and order as policy_references, with an empty string where a value is missing?

  4. bar-capsule commented on Oct 8, 2026

    @bar-capsule
    Collaborator

    @rocklambros the attribute names look good to me, and I agree with Maurits: let's state that the three policy arrays always have the same length and order as policy_references, with an empty string where a value is missing. That way a reader can always tell which rule belongs to which policy.

    The OCSF decision_class has the same gap, and #235 covers its other problem: it requires verdict_id and evidences without saying what goes in them. Let's fix the OTel and OCSF decision events in the same PR, so both carry the same fields. From my side you can go straight to a PR.

  5. rocklambros commented on Oct 12, 2026

    @rocklambros
    CollaboratorAuthor

    Notes from reviewing #244, the OpenTelemetry exporter for the reference Guardian, for the PR that closes this issue.

    The padding rule. #244 is switching to the rule above: the three policy arrays have the same length and order as policy_references, with "" for gaps. I've asked it to use the same rule for the provenance arrays (acs.provenance.source_id padded to the length of acs.provenance.origin). The spec text should say so too. Otherwise one argument without a source_id hides every other argument's source_id, which goes against events.md:41 ("SHOULD carry ... when populated").

    acs.request_id. #244 puts the step's own request_id on every span as acs.request_id. The mapping has no name for it today, and without it acs.request_id_ref on the result span has nothing to join to. I'll add acs.request_id to the mapping in this PR, as an attribute on every step span.

    acs.decision.failure. Bar's decision on #37 says the event is "defined in otel-mapping.json next to decision_event". #244 now emits it from the Guardian, so I'll add the mapping entry in this PR, next to these decision attributes.

    trace-pillar.ts comes in the same PR. decisionAttributeSite in packages/conformance/src/trace-pillar.ts:444 throws on any decision attribute it doesn't know. So adding names to decision_event without updating the checker breaks integration. The reference implementation CI job only runs on reference-implementations/**, so a spec-only PR would pass CI and break the next reference PR. Touching trace-pillar.ts here makes the job run. #244 asked about this (its Q5), and the answer there is that it rides here.

    One more gap for replay from the trace (candidate, not blocking). The decision event can carry acs.cited_provenance_ids, but no span carries provenance ids, so a reader can't join a cited id to its origin and source_id. provenance_id is required in provenance.json, so an acs.provenance.ids array on the step span would always be complete. @bar-capsule @MKaptein, should that go in this PR, or in a follow-up?

  6. rocklambros commented on Oct 12, 2026

    @rocklambros
    CollaboratorAuthor

    The change for this issue is written and tested. It waits for two PRs to merge first. It's on the branch spec/228-decision-event. The top four commits are this issue's change. The five below them are copies of PR #258 (for issue #121) and PR #261 (for issue #227), and they go away in step 2.

    Questions for the Spec leads

    @bar-capsule @afogel, these ten items are my proposals, not your decisions. A yes or no on each settles it. Timing is in my comment below.

    1. acs.request_id and acs.session.id on every step span, as required attributes, in a new step_span_attributes block.
      • Both come from fields every request carries.
      • Requiring them breaks any existing ACS-Trace exporter that doesn't emit them.
      • Joins key on the pair, because request_id is unique only within a session.
      • The Observed Agent and the Guardian may each emit a span for one request, and a reader joins the two.
      • A retried attempt is its own request, with its own step span.
      • A Guardian event that matches no Observed Agent span is a discrepancy.
    2. The OCSF decision class.
      • disposition_id replaces verdict_id, which OCSF defines as an analyst's verdict on an incident.
      • Decision events list the security_control profile, which disposition_id requires.
      • evidences leaves required_fields.
      • The values are allow 1 (Allowed), deny 2 (Blocked), defer 14 (Delayed), and modify and ask 99 (Other).
      • disposition carries the OCSF caption for 1, 2 and 14, and the ACS name for 99.
    3. Provenance arrays.
      • acs.provenance.origin and acs.provenance.source_id are always arrays.
      • source_id is emitted when at least one object has a source_id, and is then padded with "" to the length of origin.
      • Your padding rule covered the policy arrays, and this extends it.
    4. What a decision event proves.
      • It records the Guardian's verdict.
      • A reader must not count it as enforced when any acs.decision.failure with the same session and request id exists in either party's stream.
      • A Guardian must not emit an OCSF decision event when the time from receiving the request to sending the response exceeds the negotiated timeout, because OCSF has no failure form yet.
      • Without these rules, a late deny under the default fail-open posture reads as a block for an action that ran.
    5. Counting failures.
      • A reader counts distinct session and request id pairs from either party's events. That counts failed attempts, so a step recovered by a retry still shows them.
      • A pair only the Guardian reported still counts.
      • A Guardian must not record a rejected replay as a failure, because the pair belongs to the earlier request.
      • This follows from your note that the Observed Agent's own records can miss events.
      • acs.failure.detail must be text the emitter composes itself, never bytes copied from the request, the response or an error.
    6. Unusable responses the spec already resolves are not decision failures.
    7. The three policy arrays are emitted together, whenever policy_references is non-empty, or not at all.
    8. acs.chain_hash on Trace proves nothing on its own. The trace carries neither the signature nor the ContextEntries.
      • Requirement 3 of the ACS-Trace bar now says what the trace does and doesn't let a reader reconstruct.
    9. Consumers should treat every trace attribute value as untrusted data. This is a SHOULD on the Trace Events page.
    10. OCSF joins.
      • Every OCSF step event carries request_id and session_id under unmapped.acs. A tool result also carries request_id_ref when it has one.
      • The decision event carries the pair as join_fields, plus decision, reasoning, reason_codes, policy_references, cited_provenance_ids, chain_hash and metadata, so it holds what the OTel event holds.
      • unmapped.acs.reasoning is the normative copy, and finding_info.desc may repeat it.

    Joining a cited provenance id to its origin and source_id is issue #262, a sub-issue of this one. This PR doesn't add acs.provenance.ids.

    Steps after PR #258 and PR #261 merge

    1. Wait until both PR Pin the OCSF mapping to 1.9.0 and use OCSF's class names #258 and PR Add per-hook conditional attributes to the OTel mapping #261 show Merged. Don't open the PR before then. It would show their commits too, and squashing it would land them under Closes #228.
    2. Rebase onto the merged integration:
      git fetch origin
      git switch spec/228-decision-event
      git rebase --onto origin/integration 99567e5 spec/228-decision-event
      
      99567e5 is the last copied commit. Expect one conflict in specification/v0.1.0/trace/ocsf-mapping.json, on the required_fields line, because PR Pin the OCSF mapping to 1.9.0 and use OCSF's class names #258 later added finding_info.title. Take this branch's version (git checkout --theirs on that file, then git rebase --continue). This branch's final list already includes the title. If review changes PR Pin the OCSF mapping to 1.9.0 and use OCSF's class names #258 or PR Add per-hook conditional attributes to the OTel mapping #261 again, also expect conflicts in otel-mapping.json, events.md, trace-pillar.ts, trace-pillar.test.ts or tests/test_trace_mapping_defaults.py. Keep integration's version of what those PRs own, and keep this branch's additions. A dry run on 2026-10-13 against simulated squash merges of PR Pin the OCSF mapping to 1.9.0 and use OCSF's class names #258 and PR Add per-hook conditional attributes to the OTel mapping #261 hit only the one conflict above, and every gate passed afterward.
    3. Check that the diff holds only this issue's change. git diff origin/integration...spec/228-decision-event --name-only must list exactly these ten files:
      • docs/spec/trace/events.md
      • docs/spec/trace/extend_ocsf.md
      • docs/spec/trace/extend_opentelemetry.md
      • reference-implementations/agt/packages/conformance/src/trace-pillar.ts
      • reference-implementations/agt/packages/conformance/test/trace-pillar.test.ts
      • specification/v0.1.0/provenance.json
      • specification/v0.1.0/response-envelope.json
      • specification/v0.1.0/trace/ocsf-mapping.json
      • specification/v0.1.0/trace/otel-mapping.json
      • tests/test_trace_mapping_defaults.py
    4. Run the gates.
      • From the repository root: uv run pytest -v and uv run mkdocs build --strict.
      • From reference-implementations/agt/: bun install --frozen-lockfile, bun run typecheck and bun test.
      • Expect 26 trace rows, every required row emittable.
      • If PR Add a Go Guardian reference implementation #169 (the Go Guardian) has merged by then, also run cd reference-implementations/agt-go && go generate ./internal/schema and commit the refreshed copy.
    5. Push with git push --force-with-lease, and open the PR against integration. The description must:
      • carry Closes #228 and Closes #263, plus Refs #37, Refs #235 and Refs #262, all inside the issue section of the template
      • state the breaking changes: acs.request_id and acs.session.id required on every step span, and the OCSF required_fields and profile
      • list the ten proposals above with the answers they got
    6. Review.
      • Questions 1 to 10 need an answer from @bar-capsule or @afogel first. If an answer is no, change the text before merging.
      • After that, one approval lands it. It comes from @bar-capsule or @afogel, or from @artmaro alongside one of theirs, because the PR changes tests/, which CODEOWNERS doesn't give to @artmaro.
      • Confirm the agt check passed. It isn't a required check.
    7. Merge with an explicit squash message, not auto-merge: the title, then Closes #228, a blank line, and the Signed-off-by lines. The repository squashes with the PR body by default, which drops the sign-off.

    After it merges

    • PR Export Guardian traces as OTLP to a file and over HTTP #244 (exporter).
    • Publish. The schema site deploys from main, and the organization blocks Actions from opening the promotion PR, so open it by hand:
      gh pr create --repo GenAI-Security-Project/agent-control-standard --base main --head integration --title "Promote integration to main" --body "Merging publishes the site and every schema \$id URI."
      
      Merge it with a merge commit (main allows only that). Then check that curl -s https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/trace/otel-mapping.json | grep -c decision_failure_event prints 1 or more.
    • CHANGELOG. The next release notes need a "Changed, Trace" entry. It covers the two new required attributes on every step span. It covers the OCSF decision class, which now requires finding_info and disposition_id with the security_control profile, and no longer requires verdict_id or evidences. It covers the three 5001 rows with activity_id 2, and the two removed provenance attributes.
    • issue [Bug] §6.4's mandatory fail-open audit event has no conformant shape, so the decision-failure rate is unmeasurable #37 still owns the §4.1 and §6.4 prose and the sessionEnd.summary count. §6.4 should pick up the fail-closed half of the recording rule, which so far lives only in the Trace pages.

    Edited after the Spec leads answered: ten files, the expected rebase conflict, and Closes #263. Earlier, after a second and third review: ten questions instead of five, the deadline removed, a §6.5 correction, retries counted per attempt, the new base commit and file count, and a publish command that works.

  7. rocklambros commented on Oct 12, 2026

    @rocklambros
    CollaboratorAuthor

    @bar-capsule @afogel, a direct ask on the ten questions in my comment above. Could you answer them by Thursday, October 15?

    The OpenTelemetry exporter (#244) is aiming to merge on Friday, October 16. I'd like the mapping it reads to carry these fields by then, and this PR can't open until #258 and #261 merge. So the order this week is:

    1. Review Pin the OCSF mapping to 1.9.0 and use OCSF's class names #258 and Add per-hook conditional attributes to the OTel mapping #261. Either one of you can approve each.
    2. Answer the ten questions here.
    3. I open the [Bug]: acs.decision trace event cannot carry reason_codes, policy_references, cited_provenance_ids, or chain_hash, so replay from the trace alone is not possible #228 PR, and one of you approves it.

    A yes or no per question is enough. If one needs more thought, tell me which and I'll split it into its own issue so the rest can land. I'm also posting this in Slack.

  8. afogel commented on Oct 12, 2026

    @afogel
    Collaborator

    Q1. Join keys on every step span: yes

    acs.request_id and acs.session.id are required on every step span. This is a breaking change we accept (so a minor version bump): any ACS-Trace exporter that doesn't emit them stops conforming. That means that joins use the (session, request) pair. A retried attempt counts as its own request. I'm okay with making breaking changes, we're at semvar <1.0.0, so minor bumps should mean to expect breaking changes.

    Q2. OCSF decision class: yes to the core switch, with changes

    I'm okay with the core switch, with a few changes. This would also be a breaking change for OCSF exporters, but I'm already proposing that given the suggested change above (minor release version bump (spec stays the same for now)).

    My recommendation is disposition_id replaces verdict_id in required_fields, and decision events list the security_control profile. OCSF's own Detection Finding text says a security control should apply the profile. As I understand it, verdict_id is an analyst's verdict on an incident, so a Guardian has no correct value to put there. We probably should update the spec to say "Guardians SHOULD NOT set verdict_id or the finding's status_id," because those belong to whoever triages it.

    As far as the decision class mappings for 2004, I believe Defer maps to 99 (Other) with disposition: "defer", not 14 (Delayed). Delayed reads as "it ran later," but an ACS defer holds the step and denies by default when it times out.

    We also set action_id: allow 1, deny 2, modify 4, ask 99, defer 99. disposition_id has no value for modify or ask (I checked up through 1.10.0-dev), and action_id 4 (Modified) gives modify its own code. Ask and defer still share 99, so action and disposition (the text fields) are required too. While that's stricter than OCSF, I think it's a reasonable constraint to place on upstream consumers of ACS.

    As I understand what's necessary for SOC operations, we should make sure that evidences stays required, but might want to define what goes in it: ai_agent.uid (from agent_id), actor.session.uid (from session_id), and the action being decided on (for a tool call, api.operation). On a 2004 event this is the only native place for the agent and the action. ai_operation doesn't apply to 2004, and the validator rejects a top-level ai_agent.

    A few more things go in native fields, since SIEM rules don't read unmapped:

    • policy_references → authorizations[].policy (uid, version, name)
    • the join key → metadata.correlation_uid, built from the pair (e.g. <session_id>:<request_id>)
    • is_alert: true for deny, false for allow. I'm not sure yet about ask and defer. @bar-capsule what do you think?

    Everything else stays in unmapped.acs the way you have it. I ran a deny with all of this through the OCSF 1.9.0 validator, and it passes clean.

    Which decisions should be findings at all goes to #235. I'd say a deny is a finding, and defer and ask mostly aren't. It seems like OCSF's own guidance leans that way too. The alternative home for defer and ask is the event for the action itself (e.g. the tool call's 1007 event) with the security_control profile. Those events don't validate yet because ACS can't fill device (#235), so for this PR every non-allow decision stays on 2004.

    Q3.

    Ship as written.

    Q4

    Yes, I agree with the first two rules. For the third, I think it's prudent for the Guardian to emit an OCSF decision event that describes what actually happened, instead of suppressing it.

    A decision event is the Guardian's record of what it decided, not proof of what happened. The Guardian decides and the Observed Agent acts, and the Guardian can't see what the agent actually did. So a deny event (in OCSF, disposition_id 2 Blocked and action_id 2 Denied) means the Guardian said to block the action, not that it was blocked. This applies to OTel and OCSF events alike.

    If any acs.decision.failure with the same (session, request) pair exists in either party's stream, a reader MUST NOT count the decision as enforced. This only works one way. A failure event means the decision wasn't enforced, but a missing failure event doesn't prove it was, because in v0.1 the Observed Agent can under-report. The verdict rule above covers that case: no decision event proves enforcement on its own. Since a Guardian SHOULD emit its own timeout failure when it knows it was late, its OTel stream flags its own late decisions even when nobody collects the Observed Agent's stream.

    For OCSF, I don't want to suppress the event. A late deny under fail-open means the policy said no and the action ran. That's an event a SOC needs to see, and suppressing it means OCSF goes quiet exactly when someone slows the Guardian down on purpose.

    The Guardian knows the two facts it needs: that it was late (from its own timing), and the posture (it sends on_decision_failure in the ServerHello). So it emits the normal decision event with the Q2 fields, plus:

    • action_id and disposition_id stay the verdict, exactly as on an on-time decision, so the two fields mean the same thing on every decision event
    • finding_info.types includes late_decision_fail_open or late_decision_fail_closed, so a SIEM rule can find late decisions, and the ones that ran anyway, without reading unmapped
    • the failure facts in unmapped.acs.failure (class, posture, timeout_ms, elapsed_ms, payload_bytes)
    • is_alert follows the on-time rule, and is also true for any non-allow verdict under fail-open, since the action ran without what the policy required. That settles ask and defer for the late case. The on-time case is still the open question for Bar in Q2.

    I don't think this will add much noise, because a Guardian that's down produces no decision at all, so an outage adds no OCSF decision events. In contrast, a slow Guardian produces mostly late allows, and those don't
    alert, so I think payload_bytes is what will let an analyst tell an outage from induced failure across many events.

    That said, this still only catches lateness the Guardian can see. It measures its own processing time, not network time, so a decision can still arrive late and look on time.

    It's also a small first piece of the OCSF failure form from #37/#235, built on the Q2 fields. When that form lands, it should keep this shape.

    Q5

    Accept as written.

    Counting distinct (session, request) pairs across both parties keeps a failure that both sides report from being counted twice, and counting Guardian-only pairs means the Observed Agent can't zero the count by staying quiet. If I understand correctly, because a retry gets a new request_id, this counts failed attempts, not steps, which the text already says. The replay rule looks good: logging a rejected replay against its pair would attach a failure to the earlier, legitimate request and undo its enforcement record. The detail rule keeps attacker-controlled bytes and payload secrets out of trace backends.

    One wording fix: acs.failure.posture should be "the deployment's failure posture," not "the failure posture applied," since a retried attempt never had a posture applied.

    Q6

    Accept as written.

    Q7

    Accept as written. Emitting all three or none gives a reader one rule, and since no policy_references field is required, no single array can serve as the anchor. That's why this differs from the provenance rule, where origin is required. Calling reason_codes a set heads off the obvious misreading.

    On "": since Q1 already makes this a minor bump, I'd rather pull #263's minLength: 1 into this same release than wait. Then the SHOULD NOT is something validation can enforce, for policy_references and for source_id.

    Q8

    Accept as written. Checking the head needs the full signed response, since the signature covers the whole envelope and the trace only carries part of it, plus the ContextEntries. Neither belongs on a trace, so saying the copy proves nothing on its own is the honest position, and it matches where #36 landed: Trace is observability, not evidence. The requirement 3 rewording now matches what the trace can actually do.

    I'd add one sentence: the same (session, request) pair showing different chain_hash values in the Guardian's and the Observed Agent's streams is an integrity event (§8.6). That's the one check a reader can run from the trace alone.

    Q9

    Accept as written, with a small proposed wording change. Producers can't sanitize safely because they don't know where a value ends up, so the consumer has to. For the change to language, I recommend "every trace value, in OTel attributes or OCSF fields," since OCSF events carry the same text (finding_info.desc, unmapped.acs.reasoning).

    Q10

    Overall, looks good, but I would make with three changes so it matches Q2. Every OCSF event, step events included, sets metadata.correlation_uid to the proposed <session_id>:<request_id>. The join only works natively if both sides carry it, and SIEM content doesn't read unmapped. finding_info.desc SHOULD carry reasoning, not MAY, because that's where an analyst looks for why. policy_references also goes in authorizations[].policy. The unmapped.acs copies all stay as you have them, as the exact fields for replay.

    Summary of changes to spec/228-decision-event

    • decision_class.required_fields: finding_info, action_id, action, disposition_id, disposition, evidences, metadata.correlation_uid
    • authorizations required when policy_references is non-empty; policy_references → authorizations[].policy (uid, version, name)
    • disposition_mapping: defer → 99
    • new action_mapping: allow 1, deny 2, modify 4, ask 99, defer 99; action carries the OCSF caption for 1, 2 and 4, and the ACS name for 99
    • the "records the verdict, not the enforced outcome" wording covers action_id as well as disposition_id
    • is_alert rule: deny true, allow false (on-time ask/defer pending, @bar-capsule)
    • per-step evidences mapping: ai_agent.uid, actor.session.uid, plus the action being decided on
    • Guardians SHOULD NOT set verdict_id or finding status_id
    • extend_ocsf.md deny example updated to match
    • metadata.correlation_uid = <session_id>:<request_id> on every OCSF event (step and decision); each event uses its own pair
    • finding_info.desc SHOULD carry reasoning; unmapped.acs.reasoning stays the exact copy
    • decision_event / decision_failure_event: a matching acs.decision.failure in either stream means the decision MUST NOT be counted as enforced
    • replace the OCSF 'MUST NOT emit a late decision' rule: a late decision emits the normal decision event, with action_id and disposition_id as the verdict, finding_info.types including late_decision_fail_open or late_decision_fail_closed, and failure facts in unmapped.acs.failure (class, posture, timeout_ms, elapsed_ms, payload_bytes)
    • late-decision is_alert: the on-time rule, and also true for any non-allow verdict under fail-open
    • acs.failure.posture description: "the deployment's failure posture" (not "applied")
    • pull [Bug] Empty strings in policy_references and source_id collide with Trace padding #263's minLength: 1 into this release, covering the four policy_references fields and source_id
    • events.md: add the cross-party chain_hash mismatch sentence (§8.6)
    • events.md:15: "every trace value, in OTel attributes or OCSF fields"
  9. MKaptein commented on Oct 13, 2026

    @MKaptein

    @bar-capsule @MKaptein, should that go in this PR, or in a follow-up?

    Thanks for asking / checking. A follow-up in #262 works well for me. It is the same idea as my comment above: the arrays should line up, so a reader can tell which value belongs to which entry. Nice to see that carried over to the provenance ids as well.

  10. rocklambros commented on Oct 13, 2026

    @rocklambros
    CollaboratorAuthor

    @afogel, thanks. All ten answers are on the branch now, in 273cabe, exactly as you wrote them up. That includes issue #263's minLength: 1. I ran your deny through the OCSF 1.9.0 validator myself, plus a late fail-open version with finding_info.types and unmapped.acs.failure filled in. Both came back with zero errors.

    @bar-capsule, on the open is_alert question: I took a look and I'm rolling with true for ask and defer, false for modify, on top of Ariel's deny true and allow false. It's in 0be7445.

    The deciding fact is in the schemas. ask-details.json lets timeout_disposition be allow, and defer-details.json lets a defer fall back to ask. Trace has no event for how either one ends, so if an ASK goes unanswered and the action runs anyway, the decision event is the only thing the SOC ever sees. Leaving that event quiet would hide exactly the case that matters. Modify is different, because the action ran in the form the policy asked for. A MODIFY the client can't apply becomes a DENY under §6.5, and that alerts already.

    It also lines up with severity. is_alert is now true exactly when severity_id is 3 or higher, so the two fields can't disagree, and a test pins that. If issue #235 later moves ask and defer off 2004, or we add an event for how an ASK ends, we can revisit it then.

    The PR is ready to open as soon as PR #258 and PR #261 merge. I dry-ran the whole sequence: squash PR #258 and PR #261 into integration, then rebase this branch onto the result. That gave one expected conflict on the required_fields line, and after resolving it, pytest, the strict docs build, the typecheck and the bun suite all passed. The change now touches 10 files instead of 8, because issue #263 adds response-envelope.json and provenance.json. I've updated the checklist above.

  11. moved this from Accepted to In progress in ACS Project Trackeron Oct 13, 2026
  12. added a commit that references this issue on Oct 15, 2026
  13. added a commit that references this issue on Oct 16, 2026
    405d82d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

priority:P1Maintainers onlyscope:in-focusFeeds the ninety-day committed outcome. Maintainers onlystatus:acceptedIn the backlog. Maintainers onlytype:bugSomething isn't workingworkstream:specOwning workstream. Maintainers only

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions