From 40412d8bbf080b02c96793e5ff43d74c5e17bb29 Mon Sep 17 00:00:00 2001 From: Rock Lambros <152409127+rocklambros@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:41:22 -0600 Subject: [PATCH] Fix 44 dead links to a personal fork in published spec prose (#65) Fixes 44 dead links in published specification prose, and adds the guard that would have caught them. ## The defect Every link of the form `https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/...` returns **HTTP 404**. The fork is public, but it has no `dev` branch. 25 of the 44 are live on the published hooks page right now. They all arrived in one commit, `f46d260 "Integrate canonical v0.1.0 (#2)"`, the donation import. The links were self-referential when the spec lived in that repo, the content moved to `GenAI-Security-Project`, and the links did not. The `dev` branch was later removed, turning stale links into broken ones. Affected: `hooks.md` (25), `specification.md` (8), `inspect/README.md` (3), `trace/events.md` (2), and one each in `extend_cyclonedx.md`, `extend_spdx.md`, `extend_swid.md`, `extend_ocsf.md`, `extend_opentelemetry.md`. ## The fix Each file link now points at that schema's own `$id`. The mapping was verified programmatically against `specification/v0.1.0/**/*.json` before anything was rewritten: every linked path exists, and its `$id` equals the replacement URL exactly. Those URLs return 200 and are already re-checked every six hours by `monitor-pages.yml`. That location is also durable in a way the old one was not. A `blob/` URL rots when a branch is renamed. A schema `$id` is the identity the specification already commits to. One of the 44 is a directory browse link with no `$id`. It points at the canonical repository tree instead. ## Why this was invisible Three things, each individually reasonable: - No link checker exists anywhere in CI. - `mkdocs build --strict` validates internal references only, never external URLs. - `tests/conftest.py` deliberately exempts `("a", "href")` as non-fetching, which is correct, because that guard protects against resources a browser fetches rather than links a human clicks. So the repository had strong guards against third-party asset injection and none against dead links in its own normative prose. ## The guard `tests/test_doc_links.py`, committed before the fix so the failure is in the history. - **Foreign repositories.** No file under `docs/` may link to a `github.com` repository outside a three-entry allowlist: this repository, plus `prowler-cloud/py-ocsf-models` and `ocsf/examples`, which are legitimate OCSF references already present. Positive allowlist, so a new foreign repository fails until someone adds it deliberately. - **Schema links stay canonical.** Every docs link into the published schema namespace must resolve to a schema that exists under `specification/` and whose `$id` equals that exact URL. This catches drift in both directions, including a schema that gets moved or renamed. ## Verification - Guard finds 44 violations before the fix, 0 after - `uv run pytest`: 204 passed, 1 skipped - `uv run mkdocs build --strict`: clean - Five rewritten URLs fetched live: all 200 - No `afogel` reference remains anywhere in the repository --------- Signed-off-by: rocklambros --- docs/spec/inspect/README.md | 6 +- docs/spec/inspect/extend_cyclonedx.md | 2 +- docs/spec/inspect/extend_spdx.md | 2 +- docs/spec/inspect/extend_swid.md | 2 +- docs/spec/instrument/hooks.md | 50 +++++++------- docs/spec/instrument/specification.md | 16 ++--- docs/spec/trace/events.md | 4 +- docs/spec/trace/extend_ocsf.md | 2 +- docs/spec/trace/extend_opentelemetry.md | 2 +- tests/test_doc_links.py | 90 +++++++++++++++++++++++++ 10 files changed, 133 insertions(+), 43 deletions(-) create mode 100644 tests/test_doc_links.py diff --git a/docs/spec/inspect/README.md b/docs/spec/inspect/README.md index 9d50c3a..d340758 100644 --- a/docs/spec/inspect/README.md +++ b/docs/spec/inspect/README.md @@ -24,7 +24,7 @@ Both methods follow the standard ACS request envelope and are written into the S ## Canonical schema -Every component graph is expressed in the canonical AgBOM document ([`agbom/document.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/agbom/document.json)). Components are typed; the type set in v0.1.0 is: +Every component graph is expressed in the canonical AgBOM document ([`agbom/document.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/agbom/document.json)). Components are typed; the type set in v0.1.0 is: | Type | Required fields | Optional fields | |---|---|---| @@ -37,13 +37,13 @@ Every component graph is expressed in the canonical AgBOM document ([`agbom/docu | `agent_capability` | `id`, `name`, `description` | `tools[]`, `mcp_servers[]`, `a2a_peers[]` | | `skill` | `id`, `name`, `description`, `definition` (`ref`, `digest`) | `declared_capabilities[]`, `tools[]`, `mcp_servers[]`, `a2a_peers[]`, `models[]`, `composed_skills[]` | -The full per-component schema is [`agbom/component.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/agbom/component.json). +The full per-component schema is [`agbom/component.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/agbom/component.json). Every component SHOULD carry `registration_provenance` (who declared it — framework / configuration / runtime discovery) so AgBOM mutations are traceable in the same lineage system as data flow. Deployments claiming **ACS-Provenance** MUST populate `registration_provenance` on every component. ## Output format mappings -The canonical document is the source of truth; serialized output is a deterministic derivation. The mapping rules live in [`inspect/format-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/inspect/format-mapping.json). +The canonical document is the source of truth; serialized output is a deterministic derivation. The mapping rules live in [`inspect/format-mapping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/inspect/format-mapping.json). | Standard | ACS extension | Status | |---|---|---| diff --git a/docs/spec/inspect/extend_cyclonedx.md b/docs/spec/inspect/extend_cyclonedx.md index 674d895..c12a2a2 100644 --- a/docs/spec/inspect/extend_cyclonedx.md +++ b/docs/spec/inspect/extend_cyclonedx.md @@ -1,6 +1,6 @@ # Extending CycloneDX -CycloneDX 1.6 is one of the three normative AgBOM serializations in v0.1.0. The canonical AgBOM document is the source of truth; CycloneDX output is derived deterministically from it. The mapping rules live in [`inspect/format-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/inspect/format-mapping.json). +CycloneDX 1.6 is one of the three normative AgBOM serializations in v0.1.0. The canonical AgBOM document is the source of truth; CycloneDX output is derived deterministically from it. The mapping rules live in [`inspect/format-mapping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/inspect/format-mapping.json). ## Component-type mapping diff --git a/docs/spec/inspect/extend_spdx.md b/docs/spec/inspect/extend_spdx.md index 7f3518c..67d4788 100644 --- a/docs/spec/inspect/extend_spdx.md +++ b/docs/spec/inspect/extend_spdx.md @@ -1,6 +1,6 @@ # Extending SPDX -SPDX 3.0 is one of the three normative AgBOM serializations in v0.1.0. The canonical AgBOM document is the source of truth; SPDX output is derived deterministically from it. The mapping rules live in [`inspect/format-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/inspect/format-mapping.json). +SPDX 3.0 is one of the three normative AgBOM serializations in v0.1.0. The canonical AgBOM document is the source of truth; SPDX output is derived deterministically from it. The mapping rules live in [`inspect/format-mapping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/inspect/format-mapping.json). ## Component-type mapping diff --git a/docs/spec/inspect/extend_swid.md b/docs/spec/inspect/extend_swid.md index 8bae9ae..3399c55 100644 --- a/docs/spec/inspect/extend_swid.md +++ b/docs/spec/inspect/extend_swid.md @@ -1,6 +1,6 @@ # Extending SWID -SWID (ISO/IEC 19770-2) is one of the three normative AgBOM serializations in v0.1.0. The canonical AgBOM document is the source of truth; SWID tags are derived deterministically from it. The mapping rules live in [`inspect/format-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/inspect/format-mapping.json). +SWID (ISO/IEC 19770-2) is one of the three normative AgBOM serializations in v0.1.0. The canonical AgBOM document is the source of truth; SWID tags are derived deterministically from it. The mapping rules live in [`inspect/format-mapping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/inspect/format-mapping.json). ## Component-type mapping diff --git a/docs/spec/instrument/hooks.md b/docs/spec/instrument/hooks.md index 9436fae..0271386 100644 --- a/docs/spec/instrument/hooks.md +++ b/docs/spec/instrument/hooks.md @@ -2,7 +2,7 @@ ACS v0.1.0 defines 19 native `steps/*` hooks plus the wrapped `protocols/MCP/*` namespace, the Inspect-pillar `agbom/*` methods, and the `system/ping` liveness method. This page catalogs each hook: when it fires, the canonical schema, the disposition contract, and the audit-chain implications. -The full per-hook payload schemas live under [`specification/v0.1.0/hooks/`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/). Common envelope rules — `request_id`, `timestamp`, `acs_version`, `metadata`, signature handling, replay protection — are documented in [Specification §3](./specification.md#3-wire-format) and [§10.3](./specification.md#103-replay-protection). +The full per-hook payload schemas live under [`specification/v0.1.0/hooks/`](https://github.com/GenAI-Security-Project/agent-control-standard/tree/main/specification/v0.1.0/hooks). Common envelope rules — `request_id`, `timestamp`, `acs_version`, `metadata`, signature handling, replay protection — are documented in [Specification §3](./specification.md#3-wire-format) and [§10.3](./specification.md#103-replay-protection). ## Overview @@ -36,7 +36,7 @@ The full per-hook payload schemas live under [`specification/v0.1.0/hooks/`](htt ## Common envelope -Every native hook uses the standard request envelope from [`request-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/request-envelope.json): +Every native hook uses the standard request envelope from [`request-envelope.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/request-envelope.json): ```json { @@ -59,13 +59,13 @@ Every native hook uses the standard request envelope from [`request-envelope.jso } ``` -The decision envelope shape is documented in [Specification §6](./specification.md#6-disposition-vocabulary) and [`response-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/response-envelope.json). +The decision envelope shape is documented in [Specification §6](./specification.md#6-disposition-vocabulary) and [`response-envelope.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/response-envelope.json). --- ## sessionStart -Schema: [`hooks/session-start.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/session-start.json). +Schema: [`hooks/session-start.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/session-start.json). Fires once per session, after the `handshake/hello` exchange completes and before any other `steps/*` hook for the same `session_id`. Establishes the audit chain root (`previous_hash: null`), session-level identity and policy bindings, and the initial `Intent` (when IBAC is the enforcement paradigm). @@ -79,7 +79,7 @@ A deployment that does not emit `sessionStart` MAY allow the Guardian to implici ## agentTrigger -Schema: [`hooks/agent-trigger.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/agent-trigger.json). +Schema: [`hooks/agent-trigger.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/agent-trigger.json). Fires when the agent is activated by an external triggering condition (event arrival, scheduled tick, A2A inbound message, user-initiated session, or system-issued activation). For A2A-mediated delegation, `trigger_type: "a2a_inbound"` carries the originating peer identity; for in-process subagent spawns, see [`subagentStart`](#subagentstart). @@ -91,7 +91,7 @@ Fires when the agent is activated by an external triggering condition (event arr ## turnStart -Schema: [`hooks/turn-start.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/turn-start.json). +Schema: [`hooks/turn-start.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/turn-start.json). Lightweight hook marking the start of an agent turn. Many policies key on per-turn state — "deny consequential actions in any turn after a turn that retrieved untrusted data," "limit tool-call count per turn," "reset cumulative-taint at turn boundary." Without an explicit turn boundary, every Guardian rolls its own heuristic for inferring turn breaks (usually pairing `userMessage` with the next `agentResponse`), and the heuristics don't agree under auto-continuation, planning loops, and multi-step ReAct cycles. @@ -105,7 +105,7 @@ Lightweight hook marking the start of an agent turn. Many policies key on per-tu ## userMessage -Schema: [`hooks/user-message.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/user-message.json). +Schema: [`hooks/user-message.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/user-message.json). Fires when external user input arrives, before it is presented to the agent's reasoning context. Provenance: `origin: user_input`. @@ -117,7 +117,7 @@ Fires when external user input arrives, before it is presented to the agent's re ## agentResponse -Schema: [`hooks/agent-response.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/agent-response.json). +Schema: [`hooks/agent-response.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/agent-response.json). Fires after the agent has produced a response, before it is delivered to the recipient (user, A2A peer, parent agent, or external system). Provenance: `origin: agent_generated` with `derived_from` set to whatever inputs the response is derived from. @@ -129,7 +129,7 @@ Fires after the agent has produced a response, before it is delivered to the rec ## knowledgeRetrieval -Schema: [`hooks/knowledge-retrieval.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/knowledge-retrieval.json). +Schema: [`hooks/knowledge-retrieval.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/knowledge-retrieval.json). Fires when the agent retrieves external knowledge (RAG, vector search, knowledge base lookup). Provenance: `origin: retrieved`, with `source_id` identifying the index or knowledge source. @@ -141,7 +141,7 @@ Fires when the agent retrieves external knowledge (RAG, vector search, knowledge ## memoryContextRetrieval -Schema: [`hooks/memory-context-retrieval.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/memory-context-retrieval.json). +Schema: [`hooks/memory-context-retrieval.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/memory-context-retrieval.json). Memory read — long-term, session-scoped, or user-scoped — into the agent's working context. Provenance: `origin: retrieved`, `source_id` identifies the memory store. @@ -153,7 +153,7 @@ Memory read — long-term, session-scoped, or user-scoped — into the agent's w ## memoryStore -Schema: [`hooks/memory-store.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/memory-store.json). +Schema: [`hooks/memory-store.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/memory-store.json). Memory write. The standard sink for cross-session influence; mediating it prevents memory poisoning. @@ -165,7 +165,7 @@ Memory write. The standard sink for cross-session influence; mediating it preven ## toolCallRequest -Schema: [`hooks/tool-call-request.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/tool-call-request.json). +Schema: [`hooks/tool-call-request.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/tool-call-request.json). Fires after the framework has parsed a tool call from the LLM's output, but before the tool is dispatched to its handler. The central enforcement point for IBAC, FIDES, CaMeL, and AARM. Argument-level provenance attached to each `ToolArgumentValue` lets Guardians reason about the lineage of individual arguments, so policy can target specific data flows rather than the call as a whole. @@ -179,7 +179,7 @@ Frameworks MUST fire `toolCallRequest` for every action that escapes the agent's ## toolCallResult -Schema: [`hooks/tool-call-result.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/tool-call-result.json). +Schema: [`hooks/tool-call-result.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/tool-call-result.json). Fires after tool execution, before the result is ingested into the agent's reasoning context. Provenance: `origin: tool_output`, with `derived_from` set to the originating `toolCallRequest`'s provenance ids when the tool's output is data-derived. @@ -191,7 +191,7 @@ Fires after tool execution, before the result is ingested into the agent's reaso ## preCompact -Schema: [`hooks/pre-compact.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/pre-compact.json). +Schema: [`hooks/pre-compact.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/pre-compact.json). Fires before context-window compaction. Compaction is the chokepoint where provenance can be laundered: when the runtime LLM compresses a long context window into a summary, the post-compaction text is new `agent_generated` content whose `derived_from` lineage spans every untrusted item that was in the pre-compaction context. Without an explicit hook, the framework has no clean place to attach the rule that *the compacted summary's lineage is the union of all summarized entries' lineage*. AARM cumulative-context tracking breaks across compaction without it, FIDES's monotonicity claim is unverifiable, and Guardians cannot enforce "don't compact across a trust boundary" policies. @@ -203,7 +203,7 @@ Fires before context-window compaction. Compaction is the chokepoint where prove ## postCompact -Schema: [`hooks/post-compact.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/post-compact.json). +Schema: [`hooks/post-compact.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/post-compact.json). Fires after compaction. Audit + provenance-binding hook. @@ -215,7 +215,7 @@ Fires after compaction. Audit + provenance-binding hook. ## subagentStart -Schema: [`hooks/subagent-start.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/subagent-start.json). +Schema: [`hooks/subagent-start.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/subagent-start.json). In-process delegation — a parent agent spawning a subagent within the same runtime, with no A2A boundary crossed — needs an explicit lifecycle event. A2A-mediated delegation already flows through [`agentTrigger`](#agenttrigger) with `trigger_type: a2a_inbound` on the subagent's side; `subagentStart` is for the same-runtime case. @@ -231,7 +231,7 @@ Each subagent has its own SessionContext and audit chain; the parent–child rel ## subagentStop -Schema: [`hooks/subagent-stop.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/subagent-stop.json). +Schema: [`hooks/subagent-stop.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/subagent-stop.json). **Payload:** `subagent_session_id`, `outcome` (`completed`, `failed`, `cancelled`), the subagent's `final_chain_hash`, optional `summary` of what was returned to the parent. The summary's `provenance` follows the standard monotonicity rule. @@ -241,7 +241,7 @@ Schema: [`hooks/subagent-stop.json`](https://github.com/afogel/ACS_official/blob ## skillRegister -Schema: [`hooks/skill-register.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/skill-register.json). +Schema: [`hooks/skill-register.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/skill-register.json). Fires when a skill enters the agent's available set, before it is eligible to load or run. This is the static vetting gate: the one point where a Guardian sees the whole skill definition before any of its actions execute. A payload split across a skill's actions is benign at each action and malicious only in composition, so it escapes per-action hooks like [`toolCallRequest`](#toolcallrequest); `skillRegister` is where the whole definition is inspected. @@ -255,7 +255,7 @@ A skill is the loadable, composed counterpart to the passive `agent_capability` ## skillLoad -Schema: [`hooks/skill-load.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/skill-load.json). +Schema: [`hooks/skill-load.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/skill-load.json). Fires when a registered skill activates into a session, before its actions run. Where `skillRegister` vets the artifact once and statically, `skillLoad` governs each activation in live context. It carries the load path, the ordered list of skills that led to the current load, so a Guardian can see and contain inter-skill cascades (skill A loads B loads C, each clean alone). @@ -271,7 +271,7 @@ When a load is triggered by another skill, `load_trigger` is `skill_composition` ## skillUnload -Schema: [`hooks/skill-unload.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/skill-unload.json). +Schema: [`hooks/skill-unload.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/skill-unload.json). Fires when a skill leaves the active set. Its enforcement value is low: by unload time the skill's actions have already run, so a Guardian typically observes rather than denies. It keeps the AgBOM's active inventory accurate, and repeated load/unload churn of the same skill is itself a signal worth tracing. @@ -283,7 +283,7 @@ Fires when a skill leaves the active set. Its enforcement value is low: by unloa ## turnEnd -Schema: [`hooks/turn-end.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/turn-end.json). +Schema: [`hooks/turn-end.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/turn-end.json). **Payload:** `turn_id`, `outcome` (`completed`, `deferred`, `error`), `step_count`, optional `summary`. @@ -293,7 +293,7 @@ Schema: [`hooks/turn-end.json`](https://github.com/afogel/ACS_official/blob/dev/ ## sessionEnd -Schema: [`hooks/session-end.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/session-end.json). +Schema: [`hooks/session-end.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/session-end.json). Session termination, audit finalization. The Guardian seals the chain at this point. @@ -305,7 +305,7 @@ Session termination, audit finalization. The Guardian seals the chain at this po ## agbom/snapshot -Schema: [`hooks/agbom-snapshot.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/agbom-snapshot.json). +Schema: [`hooks/agbom-snapshot.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/agbom-snapshot.json). Inspect-pillar method. Fires once per session, after `sessionStart` and before `agentTrigger` (the first content-bearing hook), and again after handshake-renegotiation. Carries the full AgBOM (the Observed Agent's component graph: models, MCP servers, A2A peers, tools, knowledge sources, memory stores, agent capabilities, skills). @@ -317,7 +317,7 @@ See the [Inspect pillar](../inspect/README.md) for the full AgBOM schema and ser ## agbom/changed -Schema: [`hooks/agbom-changed.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/agbom-changed.json). +Schema: [`hooks/agbom-changed.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/agbom-changed.json). Inspect-pillar method. Fires whenever a component is added, removed, or version-changed mid-session. Carries either a full snapshot or a diff (`added[]`, `removed[]`, `changed[]`). @@ -329,7 +329,7 @@ Inspect-pillar method. Fires whenever a component is added, removed, or version- ## system/ping -Schema: [`hooks/system-ping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/system-ping.json). See [Specification §13](./specification.md#13-liveness-system-methods). +Schema: [`hooks/system-ping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/system-ping.json). See [Specification §13](./specification.md#13-liveness-system-methods). > **Note:** `system/ping` is in the `system/*` namespace, not `steps/*`. It is not a hook in the enforcement sense: it carries no audit and bypasses signature requirements. diff --git a/docs/spec/instrument/specification.md b/docs/spec/instrument/specification.md index 120b9f3..be6fa4f 100644 --- a/docs/spec/instrument/specification.md +++ b/docs/spec/instrument/specification.md @@ -58,11 +58,11 @@ A worked example appears in [ACS in Action](../../topics/ACS_in_action_example.m Streaming and notifications are not supported in v0.1.0. Batching is permitted as standard JSON-RPC 2.0 — Guardians SHOULD accept array-shaped requests and return an array of correlated responses, but ACS does not add atomicity, ordering, or cross-request dependency semantics in v0.1. Each request in a batch is evaluated independently, in declared order, with each carrying its own `request_id` and (if signed) its own signature. A Guardian that does not support batching MUST return `-32600 Invalid Request` for array-shaped inputs so the Observed Agent can fall back to sequential requests. -The full envelope schemas are [`request-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/request-envelope.json) and [`response-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/response-envelope.json). +The full envelope schemas are [`request-envelope.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/request-envelope.json) and [`response-envelope.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/response-envelope.json). ## 4. Capability Negotiation Handshake -Required at session start, before any hook traffic. Wire method: `handshake/hello`. Schema: [`handshake.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/handshake.json) (`$defs/ClientHello` and `$defs/ServerHello`). +Required at session start, before any hook traffic. Wire method: `handshake/hello`. Schema: [`handshake.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/handshake.json) (`$defs/ClientHello` and `$defs/ServerHello`). **Observed Agent → Guardian Agent (ClientHello):** `acs_versions_supported`, `methods_implemented`, `transports_supported`, `max_payload_size_bytes`, `provenance_producer`, `wrapped_protocols`, `profiles_supported` (conformance profiles the client implements; see [Conformance](../conformance.md)). @@ -122,7 +122,7 @@ DEFER reasons: `insufficient_context`, `conflicting_policies`, `low_confidence`, ### 6.1 Decision result fields -The decision envelope ([`response-envelope.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/response-envelope.json)) carries a fixed set of fields that compose to support audit, observability, and cross-paradigm enforcement: +The decision envelope ([`response-envelope.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/response-envelope.json)) carries a fixed set of fields that compose to support audit, observability, and cross-paradigm enforcement: | Field | Required | Purpose | |---|---|---| @@ -169,7 +169,7 @@ Provenance attaches to data-bearing fields (`Message.content`, `KnowledgeRetriev - Under **`deterministic`**, the producer MUST attach a Provenance object to **every** data-bearing field in every hook payload it emits. Partial population within a producing session is non-conformant: provenance is all-or-nothing per session. This is the conformance bar for the **ACS-Provenance** profile. - Under **`none`**, the producer emits no Provenance objects. A Guardian whose policy requires provenance MUST refuse such a session at handshake time (§4) rather than accept provenance-free payloads. -The base hook payload schemas therefore mark `provenance` OPTIONAL so that ACS-Core (pure IBAC and other paradigms that need no information-flow tracking) validates. Deployments claiming ACS-Provenance validate payloads against the strict `*.acs-provenance.json` variant of each data-bearing hook, which restores `provenance` to the required set. When a Provenance object is emitted, all of its own required fields MUST be populated. Schema: [`provenance.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/provenance.json). +The base hook payload schemas therefore mark `provenance` OPTIONAL so that ACS-Core (pure IBAC and other paradigms that need no information-flow tracking) validates. Deployments claiming ACS-Provenance validate payloads against the strict `*.acs-provenance.json` variant of each data-bearing hook, which restores `provenance` to the required set. When a Provenance object is emitted, all of its own required fields MUST be populated. Schema: [`provenance.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/provenance.json). | Field | Required | Type | Notes | |---|---|---|---| @@ -216,7 +216,7 @@ The SessionContext container is intentionally not schematized in v0.1. The wire- ### 8.1 ContextEntry -Schema: [`context-entry.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/context-entry.json). Append-only entry in the audit chain. +Schema: [`context-entry.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/context-entry.json). Append-only entry in the audit chain. - **Required:** `entry_id`, `step_id`, `step_type`, `entry_hash`. - **SHOULD:** `request_hash` (lowercase-hex SHA-256 of the JCS-canonicalized request envelope params; without this the chain commits only to step metadata, not to request content, so deployments claiming the **ACS-Audit** profile MUST populate `request_hash`), `timestamp`, `provenance_summary`, `previous_hash` (required for every entry except the first). @@ -235,7 +235,7 @@ Conformant Guardians MUST compute `entry_hash` this way; otherwise chains comput ### 8.3 ProvenanceSummary -Schema: [`provenance-summary.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/provenance-summary.json). Optional. Condensed view of provenance facts at the entry level (what entered at this step) and at the session level (cumulative across the session). All fields are OPTIONAL — Guardians populate only what their policies consume. Available v0.1 fields: `origins_seen`, `entry_count`, `entry_count_by_origin`, `earliest_step_id_by_origin`, `max_lineage_depth`. v0.1 carries origin-derived aggregates only; trust-derived aggregates are computed Guardian-internally because v0.1 keeps trust classification in policy. The session-level summary is the monotonic aggregation of entry-level summaries. +Schema: [`provenance-summary.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/provenance-summary.json). Optional. Condensed view of provenance facts at the entry level (what entered at this step) and at the session level (cumulative across the session). All fields are OPTIONAL — Guardians populate only what their policies consume. Available v0.1 fields: `origins_seen`, `entry_count`, `entry_count_by_origin`, `earliest_step_id_by_origin`, `max_lineage_depth`. v0.1 carries origin-derived aggregates only; trust-derived aggregates are computed Guardian-internally because v0.1 keeps trust classification in policy. The session-level summary is the monotonic aggregation of entry-level summaries. ### 8.4 Intent @@ -265,7 +265,7 @@ Single-hop only in v0.1. Approvers MUST NOT return ASK. Quorum and recursive ASK ### 9.1 Intent extension via ASK (normative) -When a Guardian raises ASK because a request is outside `Intent.parsed`, the approver's grant MAY include an `intent_extension` field (see [`ask-details.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/ask-details.json)) containing capabilities to add to `Intent.parsed`. The extension's `scope` selects between `this_request` (capabilities apply only to the in-flight request) and `session` (capabilities are appended to `Intent.parsed` for the remainder of the session). +When a Guardian raises ASK because a request is outside `Intent.parsed`, the approver's grant MAY include an `intent_extension` field (see [`ask-details.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/ask-details.json)) containing capabilities to add to `Intent.parsed`. The extension's `scope` selects between `this_request` (capabilities apply only to the in-flight request) and `session` (capabilities are appended to `Intent.parsed` for the remainder of the session). On `scope: session`, the Guardian MUST: @@ -360,7 +360,7 @@ OPTIONAL for v0.1.0. Deterministic-only deployments are fully conformant. A liveness method is required for connection-health checks, transport-debugging, and timeout tuning. It carries no enforcement semantics and is not part of the audit chain. -**Method:** `system/ping`. Schema: [`hooks/system-ping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/hooks/system-ping.json). +**Method:** `system/ping`. Schema: [`hooks/system-ping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/hooks/system-ping.json). **Request payload.** Standard ACS envelope with `method: "system/ping"` and `payload: { "echo": "" }`. diff --git a/docs/spec/trace/events.md b/docs/spec/trace/events.md index 9d3e29d..17490ff 100644 --- a/docs/spec/trace/events.md +++ b/docs/spec/trace/events.md @@ -8,7 +8,7 @@ Trace emission is the subject of the **ACS-Trace** [conformance profile](../conf Each ACS step produces a span whose `name` and required attributes are fixed by the table below. Decisions are recorded as span events on the parent step span, not as separate spans, so the enforcement verdict and the action it gates share a parent. -The full mapping lives in [`trace/otel-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/trace/otel-mapping.json). See [Extending OpenTelemetry](./extend_opentelemetry.md) for a deeper integration guide. +The full mapping lives in [`trace/otel-mapping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/trace/otel-mapping.json). See [Extending OpenTelemetry](./extend_opentelemetry.md) for a deeper integration guide. | ACS step | OTel span name | Required attributes | |---|---|---| @@ -36,7 +36,7 @@ When Provenance is attached to a hook payload, the resulting span MUST carry `ac Each ACS step is representable as an OCSF event in the class shown below (OCSF 1.5+). Required class-specific attributes are populated from the ACS payload. -The full mapping lives in [`trace/ocsf-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/trace/ocsf-mapping.json). See [Extending OCSF](./extend_ocsf.md) for the deeper integration guide. +The full mapping lives in [`trace/ocsf-mapping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/trace/ocsf-mapping.json). See [Extending OCSF](./extend_ocsf.md) for the deeper integration guide. | ACS step | OCSF class | Class UID | |---|---|---| diff --git a/docs/spec/trace/extend_ocsf.md b/docs/spec/trace/extend_ocsf.md index 4647826..b392159 100644 --- a/docs/spec/trace/extend_ocsf.md +++ b/docs/spec/trace/extend_ocsf.md @@ -2,7 +2,7 @@ The Open Cybersecurity Schema Framework (OCSF) integration enables standardized security-event logging for AI agent activity. ACS-shaped events drop directly into existing SIEM pipelines without bespoke parsers. -ACS events map to OCSF 1.5+ event classes. The normative class assignments and the disposition → `severity_id` mapping live on the [Trace Events](./events.md) page; the machine-readable mapping is at [`trace/ocsf-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/trace/ocsf-mapping.json). This page describes how to assemble the events themselves and provides representative wire examples. +ACS events map to OCSF 1.5+ event classes. The normative class assignments and the disposition → `severity_id` mapping live on the [Trace Events](./events.md) page; the machine-readable mapping is at [`trace/ocsf-mapping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/trace/ocsf-mapping.json). This page describes how to assemble the events themselves and provides representative wire examples. ## Class assignments at a glance diff --git a/docs/spec/trace/extend_opentelemetry.md b/docs/spec/trace/extend_opentelemetry.md index 05547e0..90c1446 100644 --- a/docs/spec/trace/extend_opentelemetry.md +++ b/docs/spec/trace/extend_opentelemetry.md @@ -2,7 +2,7 @@ ACS reuses OpenTelemetry as the primary observability transport. Each ACS step becomes a span; each Guardian decision becomes a span event on the parent step span. The result is an end-to-end view of agent behavior — reasoning, retrieval, tool calls, decisions — already shaped to fit existing OTel-based tooling. -The normative span-name and required-attribute table is on the [Trace Events](./events.md) page. The machine-readable mapping lives at [`trace/otel-mapping.json`](https://github.com/afogel/ACS_official/blob/dev/specification/v0.1.0/trace/otel-mapping.json). This page describes how to *use* the mapping in practice. +The normative span-name and required-attribute table is on the [Trace Events](./events.md) page. The machine-readable mapping lives at [`trace/otel-mapping.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/trace/otel-mapping.json). This page describes how to *use* the mapping in practice. ## Span hierarchy diff --git a/tests/test_doc_links.py b/tests/test_doc_links.py new file mode 100644 index 0000000..54069de --- /dev/null +++ b/tests/test_doc_links.py @@ -0,0 +1,90 @@ +"""Guards against dead and off-namespace links inside the published docs. + +Nothing else in the repository checks a doc link. `mkdocs build --strict` validates +internal references only, and CI runs no link checker at all. `tests/conftest.py` +deliberately treats `("a", "href")` as non-fetching, correctly, because that guard is +about resources a browser fetches on load rather than links a human clicks. + +Those three true facts left a hole, and something fell through it: 44 links across 9 +files under `docs/spec/` pointed at `github.com/afogel/ACS_official`, a maintainer's +personal fork of this project from before the OWASP donation. The fork has no `dev` +branch, so every one of the 44 returned HTTP 404, 25 of them on the published Hooks +page, until the donation commit that carried them in was traced and fixed. + +Guard A is a positive allowlist rather than a blocklist of known-bad hosts, because a +blocklist only catches repositories someone has already flagged as a problem. The dead +links here were never flagged. A positive allowlist fails closed on any new foreign +repository, including one nobody has looked at yet, which is the property this defect +proves the repository needs. + +Guard B checks the other direction: a link into the schema's own published namespace +must still point at a schema that exists, under the exact URL the schema's own $id +declares. That catches drift from a rename or move as readily as it catches a typo. +""" +import json +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +DOCS = ROOT / "docs" +SPECIFICATION = ROOT / "specification" + +# The only GitHub repositories any doc may link to. Add an entry here only as a +# deliberate decision: the point of a positive allowlist is that a new foreign +# repository fails until someone does that on purpose. +ALLOWED_GITHUB_REPOS = frozenset({ + "GenAI-Security-Project/agent-control-standard", # this project + "prowler-cloud/py-ocsf-models", # OCSF Python models, cited in extend_ocsf.md + "ocsf/examples", # OCSF example events, cited in extend_ocsf.md +}) + +# The namespace every published schema's $id is served from. See tools/publish_schemas.py. +SCHEMA_BASE = "https://genai-security-project.github.io/agent-control-standard/schema/" + +GITHUB_REPO = re.compile(r"https?://(?:www\.)?github\.com/([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)") +# Stops at markdown link/prose delimiters rather than at a fixed extension, so a +# directory link or a link with a query string is still captured whole. +SCHEMA_LINK = re.compile(re.escape(SCHEMA_BASE) + r"([^\s)\]]+)") + + +def _doc_lines(): + """Yield (path, line number, line text) for every line of every doc under docs/.""" + for path in sorted(DOCS.rglob("*.md")): + for lineno, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1): + yield path, lineno, line + + +def test_no_doc_links_to_a_github_repository_outside_the_allowlist(): + violations = [] + for path, lineno, line in _doc_lines(): + for match in GITHUB_REPO.finditer(line): + repo = f"{match.group(1)}/{match.group(2)}" + if repo not in ALLOWED_GITHUB_REPOS: + violations.append( + f"{path.relative_to(ROOT)}:{lineno}: links to {repo}, " + f"not on the allowlist ({match.group(0)})" + ) + assert not violations, "Foreign GitHub repository links found:\n" + "\n".join(violations) + + +def test_schema_namespace_links_match_a_published_id(): + violations = [] + for path, lineno, line in _doc_lines(): + for match in SCHEMA_LINK.finditer(line): + url = match.group(0) + tail = match.group(1) + schema_path = SPECIFICATION / tail + if not schema_path.is_file(): + violations.append(f"{path.relative_to(ROOT)}:{lineno}: {url} has no schema at {tail}") + continue + try: + declared = json.loads(schema_path.read_text(encoding="utf-8")).get("$id") + except json.JSONDecodeError as exc: + violations.append(f"{path.relative_to(ROOT)}:{lineno}: {url} schema is invalid JSON ({exc})") + continue + if declared != url: + violations.append( + f"{path.relative_to(ROOT)}:{lineno}: {url} does not match " + f"the schema's own $id ({declared!r})" + ) + assert not violations, "Schema links that do not match a published $id:\n" + "\n".join(violations)