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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,30 @@
User-visible changes to the spec and SDKs. Versioning rules:
[VERSIONING.md](VERSIONING.md).

## Unreleased

- **§12.1 admits incremental mediation.** Previously a host had to
assemble the complete response before `post_model_call` with no
exception, so a host mediating a stream incrementally (e.g. per ACS
§18.1) could not make a coherent conformance claim. §12.1 now
carries an exception: a host declaring `buffered_output: false` MAY
evaluate incrementally under a bounded-exposure accounting
discipline (verdict-covered release, terminating deny that withholds
the unreleased remainder, fail-closed residue at end of stream,
§6.1-gated durability covering withheld-but-permitted content). The
capability stays declaration-only: the §12.1a declaration and §13.3
claim MUST state the exposure bound, and conformance vectors for the
accounting discipline are future work. §12.1 also now distinguishes
an errored model call (handled per §6.1, no `post_model_call`) from
the `stream_incomplete` shape, which is for hosts that cannot
buffer. Additive; no version-surface change.
- **§12.1a defines the caller and pins released-content identity.**
The caller is any consumer outside the host's enforcement boundary,
observers, callbacks, and preview channels included, and the content
released once the verdict permits MUST be the verdicted
(post-transform) content — a host MUST NOT rewrite content between
the verdict and its release.

## 0.1.0-alpha.4 — tag `v0.1.0-alpha.4`

- **.NET: the NuGet package ships the native library.** The nupkg now
Expand Down
7 changes: 5 additions & 2 deletions conformance/CLAIMS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ provider MUST disclose whether the provider is **content-derived**
approval binding and record correlation are only as strong as that
property (§10.1). A claim with
`buffered_output: false` MUST state that a `deny` at `output` cannot
retract already-streamed content (§12.1a).
retract already-streamed content (§12.1a); one whose host mediates
incrementally under the §12.1 exception MUST also state the exposure
bound its accounting discipline enforces.

| Framework | Adapter version | Spec | Capabilities | Profiles | Identity provider | SDK | Report | Notes |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
Expand All @@ -47,7 +49,8 @@ artefacts, in the PR:
the claim states records/approvals are identity-unbound;
custom provider → content-derived or not; `buffered_output: false`
→ the claim states a deny at `output` cannot retract streamed
content.
content, plus the exposure bound when the host mediates
incrementally under the §12.1 exception.

Acceptance is by CODEOWNERS review (`conformance/` owner). The
reviewer checks: the report matches the declared surface tuple; the
Expand Down
10 changes: 7 additions & 3 deletions conformance/HARNESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,13 @@ coercion class the core's raw-text scan (§10.2) exists to reject; see
`true` (the host buffers caller-bound output until the `output`
combined verdict permits), and a host that streams to its caller
without buffering declares `buffered_output: false` in its surface and
claim. The CTK drives hosts with mocked I/O and cannot exercise
streaming egress, so no vector carries this capability — the
declaration exists to make the retraction limitation visible (§13.3).
claim. A declaring host may also mediate `post_model_call`
incrementally under the §12.1 exception; its claim then states the
exposure bound (§12.1a). The CTK drives hosts with mocked I/O and
cannot exercise streaming egress, so no vector carries this capability
— the declaration exists to make the retraction limitation visible
(§13.3). Vectors exercising the §12.1 accounting discipline against a
mocked stream are future work.

Non-finite floats (NaN/Infinity) and lone surrogates cannot be
expressed in a JSON vector at all — those §4.4 marshalling guards are
Expand Down
2 changes: 1 addition & 1 deletion docs/PRODUCTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ the [operations runbook](OPERATIONS.md) covers day-2 concerns
| 5 | **Record persistence** | `InterceptionRecord`s are the audit trail and are payload-free by construction (§10.3). Configure a record sink; the in-memory buffer is drop-oldest with a `records_dropped` counter — alert on it (see OPERATIONS). Persist `result_labels` with produced data and resurface them per §5.4. |
| 6 | **Approval-channel redaction** | The `ApprovalRequest` carries the context as presented to the resolver and MAY be redacted (§9). Use the emitter's approval-redactor seam; the request identity is computed over the *redacted* context, so the binding covers exactly what the approver saw. Document redaction in `extensions.<host>.redacted`. |
| 7 | **Payload bounds** | Contexts are canonicalized, hashed, and deep-copied per interceptor per emission. Apply the RECOMMENDED bounds (5 MiB serialized, depth 128, §12.3); whatever limit you enforce, breach MUST yield `deny host_error:context_invalid`, never a crash or truncation. |
| 8 | **Output streaming** | If you stream output to the caller, buffer until the `output` combined verdict permits (§12.1a) — or declare `buffered_output: false` and state in your claim that a deny at `output` cannot retract streamed content. |
| 8 | **Output streaming** | If you stream output to the caller, buffer until the `output` combined verdict permits (§12.1a) — or declare `buffered_output: false` and state in your claim that a deny at `output` cannot retract streamed content. A declaring host may also evaluate `post_model_call` incrementally under the §12.1 bounded-exposure exception; state the exposure bound in the claim. |
| 9 | **Value domain** | String-encode 64-bit identifiers at the adapter boundary (§4.4). JavaScript hosts cannot observe big integers at all (`JSON.parse` rounds first); Go hosts must decode with `json.Number`. |
| 10 | **Zero-interceptor state** | An `enforce`-mode emission with nothing registered fails closed (`host_error:no_interceptor`, §7). A deliberate passthrough is an explicit allow-all interceptor — deploy one consciously or not at all. |

Expand Down
2 changes: 1 addition & 1 deletion docs/THREAT-MODEL.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ SDKs; **GAP** = no automated verification exists.
| TM-08 | Identity collision via canonicalization divergence: two SDKs (or two values) canonicalize differently, breaking approval binding and audit correlation | S, R | In | §10.2 RFC 8785 via single Rust core; closed required+conditional preimage; all bindings delegate. Non-I-JSON values fail closed: in-memory check + raw-text scan for integer literals serde-class parsers coerce (beyond u64/i64) | `golden` (11 fixtures asserted in Rust/Python/TS/.NET/Go); `sdk/rust/core/src/canonical.rs` JCS + scan tests; AH-CTK-090 (beyond 2⁵³), AH-CTK-091 (beyond u64, `bigint_json` harnesses) |
| TM-09 | Audit-record payload leakage: records or failure messages exfiltrate context data into audit storage | I | In | §10.3 payload-free verdict projection (`transform.value` dropped, messages truncated); failure verdicts carry exception *type* only (including Go panic recovery); host-synthesized remediation details are value-free by rule (§6.3/§14) — the I-JSON and envelope rejections name the path and constraint, never the value | Record shape: `spec/schema/interception-record.schema.json`; projection tests per SDK; `canonical.rs` value-free-detail tests (`envelope_details_are_value_free`, raw-scan not-contains assertions); Go panic tests. Residual: an interceptor can still deliberately place payload in its own bounded `reason` |
| TM-10 | Exfiltration/SSRF via `evidence.verification_pointers`: attacker-supplied URIs dereferenced by host or audit tooling | I | In (host obligation) | §5.3/§14: host MUST NOT dereference; propagate opaque | **GAP** — prose only; no test, no scheme allow-list guidance |
| TM-11 | Streaming egress before interception: partial model output reaches the caller before `output` (or `post_model_call`) is evaluated | E, I | In | §12.1 covers model→host streaming (assemble before `post_model_call`, else fail closed `host_error:streaming_unsupported`). §12.1a covers host→caller egress: buffer until the `output` combined verdict permits (MUST), or declare `buffered_output: false` — conformant, but the claim MUST state that a deny at `output` cannot retract streamed content (§13.3) | §12.1 negative path: **GAP** (no vector). §12.1a is declaration-only by construction — mocked CTK I/O cannot exercise egress; visibility is via the claim, not a vector |
| TM-11 | Streaming egress before interception: partial model output reaches the caller before `output` (or `post_model_call`) is evaluated | E, I | In | §12.1 covers model→host streaming (assemble before `post_model_call`, else fail closed `host_error:streaming_unsupported`; a `buffered_output: false` host MAY instead mediate incrementally under the §12.1 bounded-exposure exception, whose claim states the exposure bound). §12.1a covers host→caller egress: buffer until the `output` combined verdict permits (MUST), or declare `buffered_output: false` — conformant, but the claim MUST state that a deny at `output` cannot retract streamed content (§13.3) | §12.1 negative path: **GAP** (no vector). §12.1a is declaration-only by construction — mocked CTK I/O cannot exercise egress; visibility is via the claim, not a vector |
| TM-12 | Resource exhaustion: unbounded `target`/`messages` canonicalized, hashed, and deep-copied per interceptor per emission — multiplied by parallel profiles' per-interceptor snapshots | D | In | §12.3 RECOMMENDED bounds (5 MiB / depth 128) with a normative failure mode: breach of whatever limit the host or core enforces MUST yield `deny host_error:context_invalid` and MUST NOT crash or truncate; the identity path enforces the depth default fail-closed. The record itself is bounded: §10.3 payload-free projection (transform.value dropped, messages truncated) plus the §5.3 10240-byte evidence cap | Depth: `sdk/rust/core/src/canonical.rs` depth tests. Evidence cap: AH-CTK-092 + per-SDK gate tests. Projection: AH-CTK-093. Serialized-size default: still host-side (RECOMMENDED, not emitter-enforced) |
| TM-13 | Label-flow loss: `result_labels` from non-winning permit verdicts discarded, or §5.4 persistence/resurfacing not honoured | I | In | §7.3 unions: the combined verdict carries the first-seen-ordered label union across every permit verdict in the emission (all profiles, including approval substitutions) | Union half: AH-CTK-086 + per-SDK union tests. §5.4 persistence/`source_labels` resurfacing across emissions: **GAP** — no vector |
| TM-14 | Supply-chain compromise of the artefacts: squatted names, mutable CI actions, unpinned deps | T, S | In | Distribution `agent-hooks-sdk` published on PyPI/crates.io (squatted `agent-hooks` avoided); GitHub Actions pinned by commit SHA; `Cargo.lock` committed; CodeQL enabled | Name claims live (registry state); pins in `.github/workflows/*.yml`. Lockfile enforcement (`--locked` CI builds), dependency automation (Dependabot across all ecosystems), and SBOM + provenance attestation in the release pipeline are in place. Published a1 alphas implement a superseded draft (see SECURITY.md) |
Expand Down
54 changes: 53 additions & 1 deletion spec/AGENT-HOOKS-0.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -1135,20 +1135,70 @@ emitting `post_model_call`. A host that cannot assemble MUST emit
`deny` self-verdict with `host_error:streaming_unsupported`, and MUST NOT
incorporate the partial response.

Assembly that fails because the model call itself errored is an
errored model call, not this shape: there is no complete response to
evaluate, the host handles the failure as it handles any errored
action — nothing partial egresses or persists (§6.1) — and
`post_model_call` is not the vehicle for reporting the provider's
error. The `stream_incomplete` shape above is for a host that cannot
buffer the stream it received.

**Exception — incremental mediation.** A host that declares
`buffered_output: false` (§12.1a, §13.1) MAY instead evaluate the
stream incrementally, emitting `post_model_call` more than once per
response, each emission over an assembled prefix or window of it,
provided it satisfies a bounded-exposure accounting discipline:

1. no part of the response is released to the caller beyond the
declared exposure bound ahead of a verdict covering it;
2. a `deny` terminates the stream and withholds everything not yet
released, including content an earlier emission permitted;
3. content that no emission evaluated MUST fail closed at end of
stream with `host_error:streaming_unsupported` rather than settle
clean — the same failure mode as the non-assembling host above;
4. durable incorporation (§6.1) is gated by the same discipline as
release: content withheld at termination — including content an
earlier emission permitted but the host had not yet released — or
that no emission evaluated MUST NOT be persisted.

Each such emission is an ordinary `post_model_call` under §4–§7; the
discipline governs what the host does with the verdicts, not the
emission contract. ACS §18.1 ("Incremental stream mediation",
[agent-control-spec](https://github.com/responsibleai/agent-control-spec))
is one implementation of such a discipline. This exception is
capability-shaped, not vector-backed: conformance vectors exercising
the accounting discipline are future work, and until they exist the
surface is visible only through the declaration and claim (§12.1a,
§13.3).

### 12.1a Streaming to the caller

A host that streams output to its caller MUST buffer the stream and
MUST NOT release any part of it to the caller until the `output`
emission's combined verdict permits, UNLESS the host declares the
capability `buffered_output: false` in its conformance surface (§13.1).

For this section the *caller* is any consumer outside the host's
enforcement boundary — host-registered observers, callbacks, and
preview channels included — not only the far end of the connection.
The content released once the verdict permits MUST be the verdicted
content (the transformed value when the combined verdict is
`transform`); a host MUST NOT rewrite content between the verdict and
its release.

A host declaring `buffered_output: false` remains conformant, but a
`deny` at `output` then cannot retract content already streamed; the
declaration makes that limitation visible in the conformance claim
(§13.3) rather than leaving it implied. The CTK drives hosts with
mocked I/O and therefore cannot exercise streaming egress; this
capability is declaration-only.

A host that additionally mediates incrementally under the §12.1
exception MUST state its exposure bound in the declaration: which
content can reach the caller ahead of the verdict covering it, and how
much (e.g. "none — release is watermark-gated" or "each chunk egresses
on arrival; evaluation runs behind the stream").

### 12.2 Parallel tool calls

When a model response carries N tool calls, the host MAY invoke the
Expand Down Expand Up @@ -1246,7 +1296,9 @@ plus the CTK report, recorded in `conformance/CLAIMS.md`. A claim with
`identity_provider: null` MUST state that its approvals and records are
identity-unbound. A claim with `buffered_output: false` MUST state
that a `deny` at `output` cannot retract already-streamed content
(§12.1a).
(§12.1a), and, when the host mediates incrementally under the §12.1
exception, MUST state the exposure bound its accounting discipline
enforces (§12.1a).

---

Expand Down
Loading