Skip to content

Add the skill lifecycle hooks to the Trace mappings - #63

Merged
rocklambros merged 12 commits into
GenAI-Security-Project:integrationfrom
sabasiddique1:fix/skill-hooks-trace-mappings
Oct 12, 2026
Merged

rocklambros merged 12 commits into
GenAI-Security-Project:integrationfrom
sabasiddique1:fix/skill-hooks-trace-mappings

Conversation

@sabasiddique1

@sabasiddique1 sabasiddique1 commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

What changed

skillRegister, skillLoad, and skillUnload had no entry in otel-mapping.json or ocsf-mapping.json, so a deployment claiming ACS-Trace had no normative span name or class_uid for a skill event. This adds both mappings, renders them on the Trace Events and Extending OCSF pages, links them from the Skill concept page, and widens tests/test_hook_taxonomy.py to guard every surface that restates the hook set. docs/acs.md keeps integration's version, which #21 already updated.

Which issue does this implement

Closes #57, closes #58.

  • This is an editorial correction (typo, grammar, link, formatting) with no change in meaning, so it needs no issue

Base branch

  • integration, because this touches the specification, schemas, a reference implementation, an adapter, tests, or CI
  • main, because every changed path is on the documentation lane allowlist in CONTRIBUTING.md

Type of change

  • Specification change (schema, hooks, events, AgBOM)
  • Reference implementation or adapter
  • Documentation
  • Tooling or CI
  • Governance (licensing, security policy, contributor docs)

Specification changes

  • I opened a Discussion before this PR
  • Schema changes validate against the JSON Schema spec
  • I described the impact on downstream implementers below

Breaking for implementers? No. Nothing on the wire changes. Three new keys appear in each mapping's default; existing keys, span names, and class assignments are untouched.

OTel: acs.skill.register, acs.skill.load, acs.skill.unload. Required attributes come only from fields each hook schema requires. The digest maps to two attributes, acs.skill.digest.algorithm and acs.skill.digest.value, so every exporter emits the same values. acs.skill.load_path stays required because cascade containment is the reason the hook carries it.

OCSF, as the Spec leads decided: skillLoad and skillUnload map to Module Activity (1005) with activity_id 1 (Load) and 2 (Unload). Its required module object is the skill itself, and keeping loads off Process Activity (1007) lets a reader tell a skill loading from the tool calls its actions raise. skillRegister maps to 5001, labeled Device Inventory Info, its name in every OCSF release. The agbom/* rows still say Inventory Info until #121 renames them. A denied skillRegister still surfaces as Detection Finding 2004 through decision_class.

Guard

The first commit widens the test and fails on exactly the surfaces #58 lists, plus one it did not: the class table in extend_ocsf.md never listed steps/agentTrigger. That row is fixed here too.

Two maintainer commits harden the guard. Every failure message now says how to fix it. A page that lists a deliberate subset or names a proposed hook goes in SUBSET_SURFACES or PROPOSED_HOOKS with a reason. The reference pattern skips globs, paths, and anchors. The count check skips subset counts, reads spelled-out numbers, and scans README.md, which still said sixteen native hooks and now says nineteen.

Not changed

The AgBOM trigger list in docs/spec/inspect/README.md omits skill; filed as #120. The "Sixteen" in specification/proposals/skill-lifecycle/README.md describes the pre-skill state and is correct in context.

I tested this

  • I synced my branch with the base branch before opening this
  • uv run pytest -v passes on my machine
  • uv run mkdocs build --strict passes on my machine

Checklist

  • Commits are signed off with git commit -s (required by the DCO)
  • Prose follows STYLE.md
  • No secrets, tokens, or internal URLs in the diff

Security

  • This change has no security impact

Signed-off-by: Saba Siddique <sabasiddiqdev@gmail.com>
Signed-off-by: Saba Siddique <sabasiddiqdev@gmail.com>
@sabasiddique1

sabasiddique1 commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Retargeted to integration per #64 and synced; 283 guards pass (1 skipped on macOS), mkdocs build --strict clean.

Two decisions needed from the Spec leads (@bar-capsule @afogel):

  1. OCSF class for skill hooks — 5001 Inventory Info + 1007 Process Activity as implemented, or 1005 Module Activity (Load=1 / Unload=2)? I will update within the hour either way.
  2. status:accepted on [Docs] Skill lifecycle hooks have no ACS-Trace mapping (no OTel span, no OCSF class) #57 / [Bug] Skill lifecycle hooks missing from the trace mappings, acs.md, and the Trace pillar docs #58 — both already carry scope:in-focus and priority:P2.

RESTATING_THRESHOLD = 8 can become an explicit list if preferred. The agentTrigger row in extend_ocsf.md is a plain omission fix. OCSF 1.5 naming (5001 → "Device Inventory Info") and the missing skill entry in the AgBOM trigger list are out of scope here and filed as #120 and #121.

@rocklambros rocklambros moved this to In progress in ACS Project Tracker Sep 10, 2026
rocklambros added a commit that referenced this pull request Sep 10, 2026
Operationalizes the contribution governance the core team agreed on
September 8, filtered through the Strategic Adoption Plan v3 committed
outcome.

Design: `design/2026-09-09-contribution-governance-design.md` (v1.1)
Plan: `design/plans/2026-09-09-contribution-governance.md`

## What this installs

**Current Priority Scope.** `CONTRIBUTING.md` gains one section that
states what the project is driving at for the next ninety days, what is
deferred to v0.2.0, and what is out of scope by design. It is stated
once. Every other surface links to it rather than restating it, and the
landing page now renders it straight out of `CONTRIBUTING.md` so the two
cannot drift.

**Branching.** `main` publishes the site and all 44 schema `$id` URIs on
merge, so specification and code land on `integration` and publish on a
deliberate promotion. A guard (`tools/base_branch_guard.py`, 16 tests)
enforces a positive path allowlist and fails closed on anything nobody
anticipated. The promotion exemption checks the head *repository*, not
just the ref, so a fork branch named `integration` cannot walk a schema
change onto the publishing branch.

**Intake.** Blank issues are off. Six forms route by type, and no form
can stamp a `scope:`, `priority:`, `workstream:`, or `status:accepted`
label. That prohibition is the whole structural guarantee behind
maintainer-only triage. The sixth form asks for nothing but a
description, because the strongest outside contribution this project has
received would have fit none of the other five.

**The gate.** A change to behavior, normative text, or code references
an issue carrying `status:accepted`. An editorial correction does not,
wherever it lands. A pull request whose issue is not accepted yet is
neither closed nor reviewed. It waits, and a bot says why.

**Automation.** Four workflows: the base-branch guard, an integration
sync, a weekly promotion pull request, and the intake comment. Plus a
reminder that opens an issue when the priority scope passes its review
date.

**Phase 2 executor.** `tools/apply_governance.py` performs the live
migration steps idempotently, `--dry-run` by default. It is structurally
incapable of merging, closing, or retargeting a pull request, and a test
asserts no code path can emit those commands.

## Authorship policy change

The rule that a maintainer strips a `Co-Authored-By` trailer naming a
model is removed. The project now mandates neither direction. The human
`Signed-off-by` stays required, because only a person can make the DCO
certification. A standard about agent provenance should not erase the
provenance of its own commits.

## Reviewer notes

- Nothing on the published site changes except the landing page's
Contribute section. I built the full pipeline from `main` and from this
branch and diffed: the only files that differ are `index.html` and
`acs.css`. All 44 schemas are byte identical.
- 253 tests pass. `mkdocs build --strict` passes.
- This targets `integration` rather than `main` because it touches
`.github/`, `tools/`, and `tests/`, which is exactly what the guard it
installs requires.

## Still to do after this merges

Retarget #63, #24, #60, and #22 to `integration`. Merge #21, #20, and
#22 when the team is ready. Then run the executor for rulesets, default
branch, required check, and the seeded issues. The order that matters is
in the plan.

---------

Signed-off-by: rocklambros <rock@rockcyber.com>
@sabasiddique1
sabasiddique1 changed the base branch from main to integration September 14, 2026 17:26
@github-actions github-actions Bot added the status:needs-triage Not yet triaged. Applied by the issue forms label Sep 14, 2026
@github-actions

Copy link
Copy Markdown

Thanks for this. It is queued rather than ignored.

This pull request does not reference an issue carrying status:accepted, so a maintainer has not looked at it yet and will not until the underlying issue is triaged. Nothing here is rejected. See Current Priority Scope for what the project is working on, and help wanted for work that is already accepted.

If this is an editorial correction, tick that box in the description and this comment stops applying.

…oks-trace-mappings

Signed-off-by: Saba Siddique <sabasiddiqdev@gmail.com>
…que1/agent-control-standard into fix/skill-hooks-trace-mappings
@rocklambros rocklambros changed the title Add the skill lifecycle hooks to the Trace mappings Add the skill lifecycle hooks to the Trace mappings . Oct 5, 2026
@rocklambros rocklambros changed the title Add the skill lifecycle hooks to the Trace mappings . Add the skill lifecycle hooks to the Trace mappings Oct 5, 2026
@bar-capsule

Copy link
Copy Markdown
Collaborator

@sabasiddique1 sorry for the long wait. Decisions, plus two things for the same update:

  1. skillLoad and skillUnload: 1005 Module Activity, activity_id 1 (Load) and 2 (Unload). OCSF defines it as a process loading or unloading a module, and its required module object is the skill itself. Process Activity requires a process that was launched or terminated, which a skill load doesn't have, and keeping loads off 1007 lets a reader tell a skill loading from the tool calls its actions raise. Please update the skillLoad _note to match, and the rows in events.md and extend_ocsf.md.
  2. skillRegister: 5001, labeled "Device Inventory Info". We're pinning the mapping to OCSF 1.9.0 under [Docs] OCSF 1.5 renames class 5001 to "Device Inventory Info"; Trace docs still say "Inventory Info" #121, and that is 5001's official name in every OCSF release.
  3. docs/acs.md conflicts with integration. Set the ACS-Core floor: MODIFY and system/ping to SHOULD, Wrapped MCP and subagentStart as conditional MUSTs #21 rewrote the ACS-Core line and it already lists skillRegister/Load/Unload, so please keep integration's version of that line.
  4. digest in skill-register and skill-load is an object with algorithm and value. Please map it as two attributes, acs.skill.digest.algorithm and acs.skill.digest.value, so every exporter emits the same values.

The new taxonomy tests are a good guard: I checked that they catch the missing skill rows. Happy to approve once these are in.

@sabasiddique1

Copy link
Copy Markdown
Contributor Author

@bar-capsule thanks, that's clear. Pushing all four tomorrow.

…oks-trace-mappings

Signed-off-by: Saba Siddique <sabasiddiqdev@gmail.com>

# Conflicts:
#	docs/acs.md
…y Info, split the skill digest

Signed-off-by: Saba Siddique <sabasiddiqdev@gmail.com>
@sabasiddique1
sabasiddique1 requested a review from Lrd0036 as a code owner October 9, 2026 13:40
@sabasiddique1

Copy link
Copy Markdown
Contributor Author

@bar-capsule pushed: 1005 for load/unload with activity_id 1 and 2, "Device Inventory Info" on 5001, integration's acs.md line kept, and the digest split into acs.skill.digest.algorithm and acs.skill.digest.value. pytest and mkdocs --strict pass.

README.md still said sixteen native lifecycle hooks after the skill hooks brought the schemas to nineteen. The taxonomy guard missed it because it scanned only docs/ and only digit counts.

Signed-off-by: rocklambros <rock@rockcyber.com>
Every failure message now names the fix and, where one exists, the escape hatch. A page that lists a deliberate subset or names a proposed hook goes in SUBSET_SURFACES or PROPOSED_HOOKS with a reason, and a new test rejects entries that lose their reason or outlive their need.

The reference pattern skips globs, paths and anchors, which matched as unknown hooks. The count check skips subset counts below the restating threshold, reads spelled-out counts, and scans README.md, which is how the stale sixteen above went unnoticed. A renamed section heading now fails with the heading's name instead of a bare StopIteration.

Signed-off-by: rocklambros <rock@rockcyber.com>
@rocklambros rocklambros removed the status:needs-triage Not yet triaged. Applied by the issue forms label Oct 12, 2026
@github-actions

Copy link
Copy Markdown

This pull request's description says it closes #57 #58, which is on the project roadmap. When it merges, GitHub closes that issue and the roadmap counts it as delivered. Please confirm the change delivers the whole issue before merging.

@rocklambros

Copy link
Copy Markdown
Collaborator

@sabasiddique1 I reviewed this tonight, and it's ready from my side. Thank you for turning Bar's four asks around in a day.

What I checked

  • Required attributes. I checked every required_attributes entry against the three skill schemas on integration. Each one comes from a field the schema requires: skill_id, declared_capabilities and definition.digest.{algorithm,value} for register, skill_id, digest.{algorithm,value}, load_trigger and load_path for load, and skill_id and reason for unload. That's the mistake we're fixing in [Bug]: otel-mapping.json requires acs.capability on gen_ai.tool.call, but capability is optional in tool-call-request.json #227, and this PR doesn't repeat it.
  • Bar's four asks. All four are in: 1005 Module Activity with activity_id 1 and 2, 5001 labeled "Device Inventory Info", docs/acs.md left at integration's version, and the digest split into acs.skill.digest.algorithm and acs.skill.digest.value. The skillLoad note was updated too.
  • The OCSF names. I checked them against the OCSF schema repository at the 1.0.0, 1.1.0, 1.5.0 and 1.9.0 tags. Class 1005 is "Module Activity", with 1 = Load and 2 = Unload. Class 5001 is "Device Inventory Info" at every tag I checked.
  • Branch and checks. Your branch was current with integration, and all checks were green.

What I pushed to your branch, and why

I pushed two small commits, so please pull before you push again.

  1. README.md hook count. The README said "sixteen native lifecycle hooks". The schemas define nineteen, so I changed it to nineteen. Your guard didn't catch this, because it only scanned docs/ and only matched digits.

  2. Failure messages in tests/test_hook_taxonomy.py. Your four tests catch the right things. My question was what the next person sees when one fails, and I tried a few edits that the upcoming [Bug]: otel-mapping.json requires acs.capability on gen_ai.tool.call, but capability is optional in tool-call-request.json #227, [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 and Export Guardian traces as OTLP to a file and over HTTP #244 changes will plausibly make. Two of them failed when they shouldn't have:

    • A path or a glob, like #steps/toolcallrequest or steps/toolCall*, was read as an unknown hook.
    • A sentence like "these 2 native hooks" failed the count check.

    None of the messages said how to fix the problem.

    So the guard now does five things:

    • Every message says what to do.
    • A page that lists a subset on purpose, or names a hook proposed for later, goes in SUBSET_SURFACES or PROPOSED_HOOKS with a reason. Both start empty, and a new test rejects an entry with no reason or one that's no longer needed.
    • The reference pattern skips globs, paths and anchors.
    • The count check skips small subset counts, reads spelled-out numbers like "sixteen", and also scans README.md.
    • A renamed heading now fails with the heading's name instead of a bare StopIteration.

I tested the guard by putting "sixteen" back in the README. It failed with this message: README.md: sixteen. Update the count. The full suite passes (907 passed, 1 skipped), and mkdocs build --strict is clean.

I also updated the PR description, because it becomes the squash commit message. It still said skillLoad maps to 1007 and that docs/acs.md changes, which Bar's decision replaced. Please check that it still says what you mean.

One thing I'm leaving as it is

The skillRegister note still says "modeled as Inventory Info", and the agbom/* rows still use the old name. So 5001 shows up under two names for a short time. My #121 PR renames the agbom/* rows, and I'll fix that note in the same PR. You don't need to change anything.

Next step

I pushed the last commit, so the branch rules say someone else has to approve. @bar-capsule, you said you'd approve once your four asks were in. They are, and the only new changes are the two commits above. When Bar approves, I'll squash it with Closes #57 and Closes #58 and add the sign-offs.

@rocklambros
rocklambros merged commit 185ef4e into GenAI-Security-Project:integration Oct 12, 2026
6 checks passed
@rocklambros

Copy link
Copy Markdown
Collaborator

For the record, since this merged with the admin bypass on October 12 and I owe a note on why.

I pushed the last two commits here (the hook-taxonomy guard fixes), so my approval couldn't count, and I merged before Bar's approval came in because the trace work queued behind it (PR #261, PR #244 and issue #228) needed these mappings. Here's what was in place at merge: Saba had addressed all four of Bar's asks on October 9, my two commits only touched the guard, and CI was green on the merged head, 0775c83 (test, build and closing-choice).

@bar-capsule, if you get a chance to look at it after the fact, anything you find goes into a follow-up PR.

President added a commit to disciplinedware/agent-control-standard that referenced this pull request Oct 15, 2026
The embedded schemas follow integration after GenAI-Security-Project#63, GenAI-Security-Project#258 and GenAI-Security-Project#261.

The parity recording stores the git object IDs of the deciding TypeScript
paths instead of a commit, so a squash merge no longer makes
TestRecordingIsCurrent fail. The paths include agt.lock and the
agt-bridge package.json, which pin the AGT commit and SDK. The recorder
and the test read them from one list; the test fails when the recording
names other paths or was made from another corpus, and the recorder
refuses uncommitted changes to them. A test checks that the Go workflow
runs on every deciding path. agt/README.md tells a TypeScript contributor
when to re-record.

A test requires agt-native.lock and agt.lock to pin the same AGT repo
and commit. The native build fails when AGT at that commit changes
ffi.rs, which agteval/acs.h is written from, or fetches another opa than
agt-native.lock pins.

Under the agt evaluator, policy.opa_timeout must be shorter than
protocol.decision_timeout; guardian.yaml ships 4s. Otherwise a slow opa
lets the agent stop waiting before AGT's deny arrives (GenAI-Security-Project#269).

make load measures either evaluator under concurrent clients, selected
by EVALUATOR like the other targets. Under agt each decision runs one opa
process; on a 20-core host decisions stop rising
near 16 concurrent ones, and at 256 they reach the 5s timeout.
guardian.yaml now ships limits.max_engine_calls 32, and docs/load.md
records the results. The load run found that an engine call gave its slot
back only after its answer was sent, so a client's next step could be
refused engine_saturated with no call running; callEngine now releases
the slot first, also after a panic.

When the host adapter's audit log cannot be written, the record goes to
stderr as one JSON line with the final disposition, and the refusal's
reasoning says whether the record went to stderr or nowhere. The audit file's close error now
counts. The Codex and OpenCode hooks pass their own stderr. The §6.3 row
says that ALLOW at a hook that permits no DENY is an interpretation
(GenAI-Security-Project#270), and the docs say only the adapter library reaches those hooks.

Dependabot proposes Go updates, one pull request per dependency across
both modules. The Go workflow runs weekly, so govulncheck sees advisories
for the toolchain that Dependabot does not move, triggers on the
agt-bridge package.json, and no longer fetches full history. A test
checks that the method table permits DENY exactly where hooks.md does,
and fails on a Decision line it cannot classify.
The coverage floor counts every package except commands, examples and
test helpers.

The Guardian meets the expectations GenAI-Security-Project#256 sets for reference
implementations, mapped to tests in docs/conformance.md. audit.max_file_bytes
and audit.max_files rotate each audit file; guardian.yaml keeps at most
1 GiB per log; max_file_bytes needs max_files of at least 1, so a
rotation never deletes the live log. make load can set the rotation and
fails if the log drops, splits or reorders a record or exceeds its bound;
docs/load.md records a run and the cost of long sessions. storage.provider names the session store. The HMAC secret
file holds base64 of at least 32 bytes. key_id is a configured label with
no default, an unknown key_id is refused (GenAI-Security-Project#118, GenAI-Security-Project#272), and the hooks and
the conformance harness take it as a flag. A toolCallResult whose
request_id_ref matches no request is recorded (GenAI-Security-Project#272).

Decisions from other pull requests that the Go code contradicted:
- A shell command's URL ends only at ASCII whitespace; U+FEFF or U+00A0
  in the userinfo no longer hides the host curl reaches (GenAI-Security-Project#268).
- Versions are compatible when they share X.Y under 0.x; the handshake
  negotiates only such a version, and a step must match it (GenAI-Security-Project#196, GenAI-Security-Project#255).
- A policy target that is not a string is denied evaluation_failed; the
  TypeScript fix is in GenAI-Security-Project#229, so two recorded cases are listed as ahead of
  the recording until it is re-recorded.
- The host adapter records every decision failure under either posture,
  with its class, handshake failures included (GenAI-Security-Project#37).
- The host adapter denies under any posture a signed error other than
  -32603, an unverified answer and a malformed answer; the docs mark this
  as an interpretation of §6.4 ahead of GenAI-Security-Project#32, which is open. The version
  rule is marked the same way against §3.
- The audit directory is 0700; a symlinked audit path or an audit file
  open to group or others is refused (GenAI-Security-Project#71).

Claude-Session: https://claude.ai/code/session_01DFdYnoBitHhPcUSq7wMWs1
Signed-off-by: Konstantin Trunin <konstantin@trunin.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

3 participants