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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ jobs:
# a cancelled job should stop, not finish its remaining steps.
if: ${{ !cancelled() }}
run: bun test
- name: Verify the provision census matches the pinned corpus
# ir/census/provisions.yaml is generated from spec/acs and committed
# so its diff is reviewable. A submodule bump or a sources.yaml edit
# that nobody regenerated it for is a stale count, and a stale
# count is exactly what the census exists to make impossible.
if: ${{ !cancelled() }}
run: bun run ir census --check --quiet

# The network half, deliberately separate. Both of these shallow-clone AGT
# at agt.lock's PINNED ref -- an immutable commit, so neither can be broken
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ This is a demonstration and a measurement, not a product. The section [What this
| Policy | `policy/lib/`, `policy/manifest.yaml`, `agt.lock` | AGT's stock policy bundle, byte-identical to the pinned upstream commit, plus one configuration file. |
| Translation table | `mapping.yaml` | AGT intervention points to ACS methods. AGT verdicts to ACS decisions. How each ACS field is derived. |
| Specification | `spec/acs/` | The ACS specification, pinned as a git submodule. |
| Normative IR | `ir/` | The spec's requirement catalog and censuses. Reads `spec/acs` and nothing else here; shares no code with the parts above. |

## What this project is, and is not

Expand Down Expand Up @@ -439,6 +440,9 @@ Everything above runs with no configuration. These are the variables each proces
| [`docs/shaping/acs-reference-impl-slices.md`](docs/shaping/acs-reference-impl-slices.md) | The ten slices, the risk table, and what each slice measured. Authoritative for slice scope |
| `slices/v1/` to `slices/v10/` | One README per slice: what it delivered, what it did not, and the names it froze |
| `docs/demos/v1-runbook.md` to `v9-runbook.md` | Real captured output for each slice's demo |
| [`docs/shaping/normative-ir-shaping.md`](docs/shaping/normative-ir-shaping.md) | The Normative IR work stream: requirements, shape E, affordances. Authoritative for the IR's requirements |
| [`docs/shaping/normative-ir-slices.md`](docs/shaping/normative-ir-slices.md) | The IR's eight slices. Authoritative for IR slice scope |
| `ir/slices/v1/` | One README per shipped IR slice |
| [`spec/acs/docs/spec/conformance.md`](spec/acs/docs/spec/conformance.md) | The ACS profiles and the ACS-Core baseline this tree is measured against |
| [`SECURITY.md`](SECURITY.md) | How to report a security issue |

Expand Down
13 changes: 13 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 35 additions & 23 deletions docs/shaping/normative-ir-shaping.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,35 +119,47 @@ Sibling to [`acs-reference-impl-shaping.md`](./acs-reference-impl-shaping.md). S

Surveyed at ACS `c259f57` (v0.1.0, `version.txt` = `0.1.0`) — the pinned `spec/acs` submodule, tracking `GenAI-Security-Project/agent-control-standard`, mirrored at the user's fork `afogel/ACS_official`.

### Normative corpus and its density
🟡 **Re-surveyed at ACS `6fce2a0` (v0.1.2), the submodule's current pin, by V1's census** (`bun run ir census`; [`ir/slices/v1/`](../../ir/slices/v1/README.md)). Everything below still describes `c259f57` unless marked. What moved between the two commits, for the corpus this document reasons about:

| File | RFC 2119 keyword occurrences |
| Change at v0.1.2 | Effect on this document |
|---|---|
| `docs/spec/instrument/specification.md` | 111 |
| `docs/spec/instrument/hooks.md` | 27 |
| `docs/spec/conformance.md` | 22 |
| `docs/spec/trace/extend_opentelemetry.md` | 8 |
| `docs/spec/trace/events.md` | 7 |
| `docs/spec/inspect/README.md` | 7 |
| `docs/concepts/agents.md` | 6 |
| `docs/concepts/README.md` | 3 |
| `docs/topics/ACS_in_action_example.md` | 2 |
| `docs/spec/instrument/extend_mcp.md` | 2 |
| `docs/concepts/session-lifecycle.md` | 2 |
| `docs/concepts/intent.md` | 2 |
| `docs/spec/inspect/extend_cyclonedx.md` | 1 |
| `docs/concepts/trust.md` | 1 |
| `docs/concepts/identity.md` | 1 |
| `docs/acs.md` | 1 |
| **Total** | **203** |

By keyword: `MUST` 70, `MAY` 44, `SHOULD` 34, `MUST NOT` 23, `OPTIONAL` 21, `RECOMMENDED` 7, `REQUIRED` 4. No `SHALL` / `SHALL NOT` / `SHOULD NOT` anywhere.
| `## 9. Approver Model` heading added (`daf81ac`) | **Fact 1's defect is fixed upstream** and upstream Finding 1 in the slices doc is closed. Its consequence stands and is now demonstrated: the three §9 provisions' section anchors changed, which is exactly the drift a section-number key would suffer. The lines cited below as `specification.md:257`/`:259` are now `:262`/`:264`; `:241` is now `:244` |
| Two new pages, `docs/identity/overview.md` and `docs/identity/standards.md`, carrying 5 keyword occurrences in table cells | Corpus total is **208**, not 203. Both pages say of themselves *"It does not propose a wire format or normative requirements yet"*; their MUSTs restate §6.4 and their RECOMMENDED / REQUIRED quote RFC 6749. The source census declares them informative, so the unbound count that V2 burns down from is **197** |
| §5's hook table gained `skillRegister`, `skillLoad`, `skillUnload` rows; `conformance.md` gained a paragraph stating that nobody verifies a conformance claim in v0.1.0 | No keyword delta. The conformance paragraph is a candidate Exclusion and independent support for R0's motivation |
| `response-envelope.json` now makes `result` a `oneOf` over `AcsResult` and `handshake.json#/$defs/ServerHello`; every schema `$id` moved to the `genai-security-project.github.io` base; `acs_schema.json` moved into `v0.1.0/` | The *structural* specimen in the V2 table now has a schema constraint to cite by JSON Pointer (R5.2) rather than a prose sentence. Pinned subschema hashes (R2.8) must be taken at `6fce2a0`, not `c259f57` |
| Schema count | The "48 JSON Schema files" claim below was a miscount at either commit. There are **44** under `specification/v0.1.0/` at v0.1.2: 29 under `hooks/` (23 base plus 6 strict `*.acs-provenance.json`), 10 top-level, 2 `agbom/`, 1 `inspect/`, 2 `trace/` |

### Normative corpus and its density

| File | `c259f57` (v0.1.0) | 🟡 `6fce2a0` (v0.1.2) |
|---|---|---|
| `docs/spec/instrument/specification.md` | 111 | 111 |
| `docs/spec/instrument/hooks.md` | 27 | 27 |
| `docs/spec/conformance.md` | 22 | 22 |
| `docs/spec/trace/extend_opentelemetry.md` | 8 | 8 |
| `docs/spec/trace/events.md` | 7 | 7 |
| `docs/spec/inspect/README.md` | 7 | 7 |
| `docs/concepts/agents.md` | 6 | 6 |
| `docs/concepts/README.md` | 3 | 3 |
| 🟡 `docs/identity/overview.md` | — | 3 (informative) |
| `docs/topics/ACS_in_action_example.md` | 2 | 2 |
| `docs/spec/instrument/extend_mcp.md` | 2 | 2 |
| `docs/concepts/session-lifecycle.md` | 2 | 2 |
| `docs/concepts/intent.md` | 2 | 2 |
| 🟡 `docs/identity/standards.md` | — | 2 (informative) |
| `docs/spec/inspect/extend_cyclonedx.md` | 1 | 1 |
| `docs/concepts/trust.md` | 1 | 1 |
| `docs/concepts/identity.md` | 1 | 1 |
| `docs/acs.md` | 1 | 1 |
| **Total** | **203** | **208** |

By keyword at `c259f57`: `MUST` 70, `MAY` 44, `SHOULD` 34, `MUST NOT` 23, `OPTIONAL` 21, `RECOMMENDED` 7, `REQUIRED` 4. No `SHALL` / `SHALL NOT` / `SHOULD NOT` anywhere. 🟡 At `6fce2a0` the five additions are `MUST` +3, `RECOMMENDED` +1, `REQUIRED` +1, all in the two informative identity pages.

`concepts/provenance.md` and `concepts/intent.md` are cited as *"(normative)"* from `specification.md` §7 and §8.4 while carrying 2 and 1 keyword occurrences. **Keyword count is not a proxy for normative weight**, which is why R1.1 makes corpus membership a declaration and provision discovery a separate operation.

### What already exists, and must not be duplicated

`specification/v0.1.0/` ships **48 JSON Schema files**: `request-envelope.json`, `response-envelope.json`, `handshake.json`, `provenance.json`, `provenance-summary.json`, `context-entry.json`, `ask-details.json`, `defer-details.json`, `modifications.json`, 26 under `hooks/` (including 6 strict `*.acs-provenance.json` variants), 2 under `agbom/`, `inspect/format-mapping.json`, and `trace/{otel,ocsf}-mapping.json`.
`specification/v0.1.0/` ships **48 JSON Schema files** (🟡 corrected to **44** in the re-survey above; the breakdown here was miscounted): `request-envelope.json`, `response-envelope.json`, `handshake.json`, `provenance.json`, `provenance-summary.json`, `context-entry.json`, `ask-details.json`, `defer-details.json`, `modifications.json`, 26 under `hooks/` (including 6 strict `*.acs-provenance.json` variants), 2 under `agbom/`, `inspect/format-mapping.json`, and `trace/{otel,ocsf}-mapping.json`.

The JSON-Schema layer of the four-layer architecture is **already built and shipped**. The IR cites it (R5), never restates it.

Expand All @@ -159,7 +171,7 @@ The JSON-Schema layer of the four-layer architecture is **already built and ship

| # | Fact | Consequence |
|---|---|---|
| 1 | `specification.md` has **no `## 9.` heading**; §9.1 and §9.2 exist and are hyperlinked from §6 and §8.4, but §9's preamble sits under §8.6 | Section anchors are **not** a reliable key. A binding keyed on `#8…` would mis-attribute three provisions, one of which (`Approvers MUST NOT return ASK`) is a named security invariant. Confirms R2.1's "never a section number" |
| 1 | `specification.md` has **no `## 9.` heading**; §9.1 and §9.2 exist and are hyperlinked from §6 and §8.4, but §9's preamble sits under §8.6. 🟡 **Fixed upstream at v0.1.2** (`daf81ac` adds `## 9. Approver Model`) | Section anchors are **not** a reliable key. A binding keyed on `#8…` would mis-attribute three provisions, one of which (`Approvers MUST NOT return ASK`) is a named security invariant. Confirms R2.1's "never a section number". 🟡 The upstream fix is the demonstration: those three provisions' section anchors changed between the two pins while their text did not |
| 2 | 🟡 **`attr_list` is enabled but disqualified** — measured in [X3](./spike-marker-span.md). Of its four paragraph forms, three render the braces as **visible page text**; only an own-line `{: #id }` creates an anchor, and it attaches to the whole block (`<p id>` / `<td id>`) | Two independent disqualifications: block granularity cannot address §6.1's three-provision cell or §10.3's four-provision sentence, and a misplaced attribute list publishes `{ #acs-req-017 }` on the live site. Raw `<a id>` survived all 8 tested positions with no such failure mode. **This corrects the first pass, which claimed attr_list markers render as anchors** |
| 3 | Normative content lives in **tables and registries with no RFC 2119 keyword**: §6's *Required fields* column, §6.1's *Required* column, §7's *Required* column, §10.1's algorithm-registry status column, §17/§17.1's error-code registry, §7.2's channel-to-trust mapping, the `DEFER` reason enum, `timeout_decision` default `deny`, `skew_window_ms` RECOMMENDED default `300000` | A keyword-anchored census is **necessary but not sufficient**. Also: markers must work inside table cells (E2.3) |
| 4 | Conformance is **tiered** across 7 profiles, declared per-session in the handshake | A verdict without profile scope is meaningless. Profile is a first-class field |
Expand Down
6 changes: 4 additions & 2 deletions docs/shaping/normative-ir-slices.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ So V1 is the wide census (Phase 0), V2–V6 are the deep vertical on ~20 semanti

**Reference numbers to reproduce** (measured in X3, at `c259f57`): paragraph 145, list item 33, table cell 17, blockquote 8, total 203; zero in headings, code fences, or inline code spans. If V1's scan disagrees with those, V1's scan is wrong.

🟡 **Shipped** ([`ir/slices/v1/`](../../ir/slices/v1/README.md)). The scan reproduces X3 exactly at `c259f57` (203 in 132 blocks, 145 / 33 / 17 / 8). Against the current pin, `6fce2a0` (v0.1.2), it reports **208 in 134 blocks**, 145 / 33 / **22** / 8: the five additions are table cells in the two new `identity/` pages, which the source census declares informative because they say of themselves that they propose no normative requirements yet. So the burn-down V2 starts from is **197 unbound**, with 11 excluded by source status. The corpus is 38 documents: 19 normative, 18 informative, 1 editorial (`concepts/README.md`). All eight footers parsed, 22 pillar entries, none dangling.

**Wires to future slices:** `N52` also feeds `U32` (V3) and `U34` (V6); those columns render empty until then.

---

## V2: Twenty provisions, marked and indexed

**Demo:** `acs-ir markers apply && acs-ir extract && acs-ir render` produces a provision index of twenty provisions. Each carries its opaque ID, node type, level, bound actor, activating profile, evidence class, and verbatim text. Re-running `acs-ir census` shows the unbound count fall from 203 to 183.
**Demo:** `acs-ir markers apply && acs-ir extract && acs-ir render` produces a provision index of twenty provisions. Each carries its opaque ID, node type, level, bound actor, activating profile, evidence class, and verbatim text. Re-running `acs-ir census` shows the unbound count fall from 203 to 183 (🟡 from 197 to 177 at the v0.1.2 pin, see V1).

**Specimen selection is the substance of this slice.** Twenty provisions chosen for **semantic diversity, not document order**, per the source's insistence. The kinds, mapped to concrete provisions (from the shaping doc's specimen table):

Expand Down Expand Up @@ -576,6 +578,6 @@ Not slice work, but discovered by it and worth reporting to the ACS maintainers

| # | Finding | Source |
|---|---|---|
| 1 | `specification.md` has **no `## 9.` heading**. §9.1 and §9.2 exist and are linked from §6 and §8.4, but §9's preamble — approver authentication, Guardian identity verification, *"Approvers MUST NOT return ASK"* — is stranded under §8.6 *Chain head publication* | Shaping survey |
| 1 | ~~`specification.md` has **no `## 9.` heading**. §9.1 and §9.2 exist and are linked from §6 and §8.4, but §9's preamble — approver authentication, Guardian identity verification, *"Approvers MUST NOT return ASK"* — is stranded under §8.6 *Chain head publication*~~ 🟡 **Fixed upstream in v0.1.2** (`daf81ac`, *Give the approver model its own section heading*). Nothing to report | Shaping survey |
| 2 | Three `concepts/` `(normative)` callouts are obligations on the Guardian, which `concepts/README.md:14`'s altitude rule places in the pillars | [X5](./spike-provision-taxonomy.md) |
| 3 | `conformance.md` and `specification.md` §7 state the ACS-Provenance all-or-nothing rule in near-identical prose — a restatement pair the `README.md:33` migration would resolve | X3, X5 |
40 changes: 40 additions & 0 deletions ir/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# ACS Normative IR

The requirement catalog for the Agent Control Standard: every normative provision in the pinned spec with a permanent ID, a declared type, and (where testable) a machine-readable predicate, plus the tooling that keeps prose and catalog in step.

**Shaping:** [`docs/shaping/normative-ir-shaping.md`](../docs/shaping/normative-ir-shaping.md) is authoritative for requirements, the shape (E), and the affordances. [`docs/shaping/normative-ir-slices.md`](../docs/shaping/normative-ir-slices.md) is authoritative for slice scope. One README per shipped slice lives under [`slices/`](./slices/).

## The boundary

This tree reads `spec/acs` (the pinned submodule) and nothing else in the repository (R8.2). No import here reaches `packages/`, `hosts/`, `policy/`, or `mapping.yaml`, and nothing outside `ir/` imports from it. It is a workspace of its own so it can be lifted into the spec repository later (R8.3) without carrying reference-implementation choices with it.

## Run

```
bun install
bun run ir census # writes ir/census/provisions.yaml and prints the report
bun run ir census --check # exits 1 if the committed census is stale; what CI runs
```

`acs-ir census --corpus <dir>` points the census at another ACS checkout, which is how the tests compare the pinned corpus with the commit the shaping survey measured.

## Layout

| Path | Owner | What it is |
|---|---|---|
| `census/sources.yaml` | authored | The source census (S11): every document under `spec/acs/docs/`, its normative status, and what makes it normative. The corpus is declared here, never inferred from a grep (R1.1). |
| `census/provisions.yaml` | generated | The provision census (S11): every RFC 2119 occurrence, its block type, and whether it is bound, excluded with a reason, or still unbound (R1.3, R1.6). Never hand-edited. |
| `src/corpus.ts` | code | Locates the submodule, lists `docs/**/*.md`, reads the commit and version. |
| `src/markdown-blocks.ts` | code | Line-level block classifier: paragraph, list item, table cell, blockquote, heading, code fence. |
| `src/census/` | code | N10 to N14 and N19: the census runner, the source census check, the keyword sweep, the callout scan, and the footer-seeded dependency edges. |
| `src/render/census.ts` | code | N52: the four V1 report panels. |
| `src/main.ts` | code | The `acs-ir` command line. |
| `test/` | tests | Unit tests on a fixture corpus, plus the pinned corpus held to the survey's reference numbers. |

## What exists so far

| Slice | Status | Delivers |
|---|---|---|
| V1 | shipped | The corpus, inventoried: both censuses, every occurrence still unbound and listed. |
| V2 | next | Twenty provisions marked with invisible anchors and indexed; the generated/authored seam. |
| V3 to V8 | planned | Staleness propagation, spec-lint CI, compiled predicates with the differential oracle, the conformance report, the full conversion, upstreaming. |
Loading
Loading