# BENAC-CID-DRISL-001 Closeout — DASL CID v1 + DRISL canonical identity profile

> **Iteration 5 update.** The caller acknowledged the iteration-4 fixes
> closed the implementation-snapshot consistency gap but rejected the
> proposed-resolution on one remaining identity-binding hole:
> `PackageImplementation.blob_cid` is intentionally excluded from
> `PackageCanonicalImplementation` (so the package CID stays stable when
> `blob_cid` alone changes) — but `blob_cid` was still load-bearing at
> runtime. The WASM executor read `bytes = blobs.get_verified(&blob_cid)`
> and `approve_no_risk_hello_world` derived the
> `local_blob_read_exact` capability scope from `blob_cid`. So an
> attacker could keep `manifest.implementations[wasm].artifact_cids`
> legitimate (preserving the package CID), keep the wasm implementation
> snapshot legitimate (passing iter-4's snapshot validator), but swap
> `manifest.implementations[wasm].blob_cid` to a malicious raw blob CID
> and add the malicious bytes. Capsule integrity recomputes cleanly.
> Approval grants `local_blob_read_exact` on the malicious CID. WASM
> execution runs the malicious bytes. Package identity is split from
> executable behavior.
>
> Iteration 5 closes that gap:
>
> 1. `PackageImplementation::validate_artifact_binding(&self)` is the
>    new canonical-binding gate. For `wasm_abi.v0`, `blob_cid` must
>    exist, must use raw codec `0x55`, and must equal `artifact_cids[0]`
>    (with `artifact_cids` having exactly that single CID). For
>    `declarative_transform.v0`, `blob_cid` must be `None` and
>    `artifact_cids` must be exactly one DRISL-codec CID. Unknown kinds
>    fail closed with `benac.error.unsupported_implementation_kind`.
>    Mismatches raise `benac.error.implementation_artifact_mismatch`.
> 2. `PackageManifest::validate_value` runs the validator at schema
>    time, so any caller of `validate_value` (capsule import, fixture
>    builder, station-cli, PWA) sees the failure before the bad
>    manifest can be used.
> 3. `Capsule::validate_and_inspect` re-runs the validator on every
>    implementation entry of every package document at import time
>    (defense in depth + load-bearing capsule-level rejection with the
>    implementation entry CID surfaced in the error's object_ref).
> 4. `BrowserStationRuntime::invocation_input` re-runs the validator
>    before any executor is invoked. The runtime must not trust "we
>    validated earlier" — the same defense-in-depth pattern as iter-3
>    `implementation_not_trusted` and iter-4
>    `implementation_snapshot_mismatch`.
> 5. The WASM executor in `BrowserStationRuntime::invoke_wasm` now
>    reads the executable artifact CID from `implementation.artifact_cids[0]`
>    rather than from `implementation.blob_cid` directly.
>    `validate_artifact_binding` already proved them equal, so this is
>    structurally equivalent — but it sources from the field that
>    participates in canonical package identity, closing the
>    blob-substitution attack.
> 6. `approve_no_risk_hello_world` derives the
>    `local_blob_read_exact` grant scope from the canonical
>    `artifact_cids` of `wasm_abi.v0` implementations, not from the
>    unbound `blob_cid` field.
> 7. The invocation lifecycle classifies
>    `benac.error.implementation_artifact_mismatch` as `denied`
>    (joining `implementation_not_trusted`,
>    `implementation_snapshot_mismatch`, and the other authority-scope
>    codes).
>
> Suite is now 62/62 (was 55/55 in iteration 4): seven new conformance
> regressions — three direct validator checks (artifact-binding
> mismatch, declarative-must-not-have-blob_cid, sanity), the import
> gate, the runtime gate, the capability-grant scope, and the
> end-to-end evil-capsule blob-substitution attack. Section 7, Item 4,
> the acceptance-criteria checklist and the no-go conditions checklist
> below have all been updated to reflect iteration 5.

> **Iteration 4 update.** The caller acknowledged the iteration-3 fixes
> closed every prior blocker (artifact-CID-bound package identity,
> implementation-CID-bound trust + capability scopes, evil-capsule denial)
> but rejected the proposed-resolution on one remaining identity-binding
> gap: implementation snapshots were not validated against the package
> manifest before the runtime relied on them. A malicious capsule could
> keep the manifest's `artifact_cids` legitimate (preserving the package
> CID) while swapping `manifest.implementations[i].cid` to a different
> valid `ImplementationSnapshot` whose own `artifact_cids` pointed at
> malicious bytes. The runtime's `declarative_transform_document`
> resolver consulted the snapshot's artifact list, not the manifest's —
> so the package CID could bind one artifact while invocation executed
> another.
>
> Iteration 4 closes that gap:
>
> 1. `ImplementationSnapshot::validate_against_package_entry(&self,
>    &Cid, &PackageImplementation)` is the new consistency gate. It
>    checks `package_cid`, `implementation_entry_id`,
>    `implementation_type`, `artifact_cids`, and the snapshot's
>    self-CID (DRISL CID over its canonical bytes) against the
>    manifest's `PackageImplementation` entry. Any mismatch raises the
>    new stable error code
>    `benac.error.implementation_snapshot_mismatch`.
> 2. `Capsule::validate_and_inspect` now resolves every
>    `PackageImplementation.cid` to a snapshot document inside
>    `capsule.documents` and runs the validator at import time. A
>    missing snapshot also fails closed under the same code.
> 3. `BrowserStationRuntime::declarative_transform_document` re-runs the
>    same validator at invocation time as defense in depth — the runtime
>    must not trust "we validated earlier."
> 4. The invocation lifecycle classifies
>    `benac.error.implementation_snapshot_mismatch` as `denied` (joining
>    `implementation_not_trusted` and the other authority-scope codes).
>
> Suite is now 55/55 (was 48/48 in iteration 3): seven new conformance
> regressions — five per-field validator checks, one full
> evil-capsule attack regression that asserts both the import gate and
> the runtime gate reject with the new code, and one sanity check that
> a fully-consistent pair (synthetic + bundled) passes the validator.
> Section 7, Item 4, the acceptance-criteria checklist and the no-go
> conditions checklist below have all been updated to reflect iteration
> 4.

Branch: `feat/dasl-cid-drisl`
Final commit (ticket scope): `b8dab41` (iteration 5) on top of
`65c083aa878e5b121d59e7cf7d1fe25e73479b06` (iter 4); iteration-3 head
was `90e6c69a7c391fbf3ec3da4b1d4422dd6b9607bb`.
Pushed to: `gitlab` → `git@gitlab.benac.dev:srv/benac.git` (iter-5 push
landed at closeout: `65c083a..b8dab41  feat/dasl-cid-drisl -> feat/dasl-cid-drisl`).
Live URL: <https://benac.benac.dev/>
Status: `proposed_resolution` (iteration 5)

Iteration-1 deploy + 29 checks were run against `c0a4c1a`. Iteration 2
lifted the suite to 42 (33 hello-world + 9 replication-lite). Iteration
3 lifted it further to 48 (39 hello-world + 9 replication-lite).
Iteration 4 lifted it to 55 (46 hello-world + 9 replication-lite).
Iteration 5 lifts it to 62 (53 hello-world + 9 replication-lite),
adding three direct artifact-binding validator checks (mismatch
rejected, declarative must not carry blob_cid, valid binding passes),
the import-time gate, the runtime gate before invocation, the
capability-grant scope check, and the end-to-end evil-capsule blob
substitution regression.

This document is the 11-item evidence packet referenced by the ticket
closeout comment. Every section maps 1:1 to the ticket's "Evidence to
attach to ticket closeout" list.

---

## Pre-flight gate states

| Gate | Result |
| --- | --- |
| `git status` | working tree carries the iteration-5 source changes plus pre-existing dist drift (the latter expected, not committed); branch gains one more iter-5 commit on top of `65c083a` |
| `git log --oneline -3` | iteration-5 commit on top of `65c083a` (iter4) and `90e6c69` (iter3) |
| `cargo fmt --all --check` | passes (no output) |
| `cargo clippy --workspace --all-targets -- -D warnings` | passes |
| `cargo test --workspace` | passes — see item 3 |
| `cargo test --manifest-path tools/gravitational_lens/Cargo.toml` | passes — 145 tests |
| `bash scripts/check-cid-grep-gates.sh` | `OK: all CID grep gates pass` |
| `cargo run -p benac-conformance --example print_suite \| tail -1` | `[summary] checks=62 passed=62 failed=0` |

---

## 1. Commit SHA

The iteration-5 commit `b8dab41f8f7dba16523c697210fe524acccaef21` lands
on `feat/dasl-cid-drisl` on top of iteration 4
(`65c083aa878e5b121d59e7cf7d1fe25e73479b06`). Iteration-3 head was
`90e6c69a7c391fbf3ec3da4b1d4422dd6b9607bb`.

Phase commits on the branch (iteration 5 sits on top of iteration 4 =
`65c083a`):

```
b8dab41 fix(cid): iteration 5 — validate blob_cid against artifact_cids + make artifact_cids authoritative
65c083a fix(cid): iteration 4 — enforce implementation snapshot/manifest consistency
90e6c69 fix(cid): iteration 3 — bind package canonical to artifact CIDs + scope authority by impl CID
cc3b826 fix(cid): iteration 2 — structured WASM implementation snapshot + ClaimTarget binding
9a26cea chore(conformance): add print_suite example for closeout evidence
c0a4c1a test(conformance): phase 7 — DASL CID + DRISL invariant suites + grep-gate guard
9547a5a refactor(persistence): centralize JSON CID-link reads behind Cid::from_link_value
092cf79 feat(fixtures): regenerate hello world fixture under DASL CID
489b91c feat(cid): phase 3+4 — replace identity APIs and rename fields to CID vocab
4620dbd feat(drisl): phase 2 — DRISL canonical encoder + validator
83cc5d2 feat(cid): phase 1 — DASL CID v1 module with raw + DRISL codecs
9f528df fix: cli station_profile now reads ubuntu_shell_cli, not browser_pwa  (base)
```

---

## 2. PWA bundle name

The iteration-5 changes are purely additive to `benac-core`,
`benac-browser`, and `benac-conformance`: a new validator method on
`PackageImplementation`, three call-sites
(`PackageManifest::validate_value`, `Capsule::validate_and_inspect`,
`BrowserStationRuntime::invocation_input`), executor + capability
grant updates that read from `artifact_cids` instead of `blob_cid`,
and seven new conformance checks. The fixture binary
(`hello-world.benac-capsule.json`) is byte-identical to iteration 4
because canonical-payload semantics did not change (the validator
runs over existing fixture fields, and the bundled HW1 wasm
implementation already had `blob_cid == artifact_cids[0]` by
construction in `capsule_with_wasm`). The iter-5 re-deploy produces a
fresh bundle hash because kernel source bytes changed.

Iteration-5 dist (live):
```
benac-pwa-4d18cfb2a790135b.js
benac-pwa-4d18cfb2a790135b_bg.wasm
sw.js
```

Iteration-4 dist (superseded):
```
benac-pwa-d24c87c3110e094d.js
benac-pwa-d24c87c3110e094d_bg.wasm
sw.js
```

Iteration-3 dist (superseded):
```
benac-pwa-b1f836b4e7b3eeac.js
benac-pwa-b1f836b4e7b3eeac_bg.wasm
sw.js
```

`gravitational_lens:local` image SHA after iter-5 deploy:
`c9e3c1292a1056dcaa4a79d95c5765a97c0be0c9fd7761f0b217705fd0acb462`.

---

## 3. Cargo fmt / clippy / test output

`cargo fmt --all --check` — passes (no output).

`cargo clippy --workspace --all-targets -- -D warnings` — passes.

`cargo test --workspace` — passes (131 tests across the workspace,
unchanged from iteration 4 because the iter-5 changes go into the
existing conformance suite rather than into new unit tests).

```
test result: ok.  8 passed; 0 failed     (benac-browser unit tests)
test result: ok.  6 passed; 0 failed     (benac-conformance suite-level tests)
test result: ok. 77 passed; 0 failed     (benac-core unit tests)
test result: ok.  2 passed; 0 failed     (benac-fixtures unit tests)
test result: ok. 19 passed; 0 failed     (benac-conformance integration: fixture + invariants)
test result: ok.  3 passed; 0 failed     (xtask integration)
test result: ok.  8 passed; 0 failed     (station-cli end-to-end full lifecycle)
test result: ok.  8 passed; 0 failed     (station-cli sync_end_to_end)
... plus several 0-count placeholder runners and doc-test stubs
```

Plus gravitational_lens sidecar:
```
test result: ok. 145 passed; 0 failed; 0 ignored
```

---

## 4. Conformance suite output and check count (iteration 5)

The `examples/print_suite.rs` invocation after iteration 5:

```
$ cargo run -p benac-conformance --example print_suite | tail -1
[summary] checks=62 passed=62 failed=0
```

That's 53 hello-world / DASL+DRISL invariants + 9 replication-lite
invariants. Iteration 5 added seven new checks (all hello-world):

```
iter5 (7, all hello-world):
  wasm_blob_cid_must_match_manifest_artifact_cids                          *direct validator: blob_cid != artifact_cids[0]*
  wasm_blob_cid_mismatch_rejected_at_import                                *capsule import gate via PackageManifest::validate_value*
  wasm_blob_cid_mismatch_rejected_before_wasm_invocation                   *runtime gate via BrowserStationRuntime::invocation_input*
  wasm_capability_grant_uses_artifact_cid_not_unbound_blob_cid             *approve_no_risk_hello_world derives grant from artifact_cids*
  package_cid_binds_legitimate_wasm_artifact_but_blob_cid_points_to_malicious_blob_rejected  *evil-capsule end-to-end (blob substitution)*
  declarative_implementation_must_not_have_blob_cid                        *direct validator: declarative kind branch*
  valid_implementation_artifact_binding_passes_validator                   *sanity: synthetic wasm + synthetic decl + bundled HW1 pass*
```

The first three exercise the validator at each of its three call
sites. The fourth proves the capability grant scope is sourced from
`artifact_cids` of `wasm_abi.v0` implementations rather than from the
unbound `blob_cid` field, and that declarative implementations do not
leak into the `local_blob_read_exact` scope. The fifth is the
end-to-end attack: build a capsule whose package CID is byte-identical
to the legitimate hello-world package CID (manifest `artifact_cids`
unchanged) and whose wasm implementation snapshot is also unchanged
(passes iter-4's validator), but whose
`manifest.implementations[wasm].blob_cid` points at a malicious raw
blob whose bytes are added to the capsule. The capsule import gate
rejects with `benac.error.implementation_artifact_mismatch`. The sixth
exercises the declarative-kind branch (declarative impls must not
carry `blob_cid`). The seventh is the safety net: the bundled HW1
fixture's two implementation entries plus a synthetic consistent
wasm/declarative pair all pass `validate_artifact_binding`, so the
per-failure tests are measuring what they say they measure.

Full hello-world list (53):

```
hello-world (39):
  dasl_cid_hello_vector_matches
  dasl_cid_empty_vector_matches
  drisl_cid_link_tag42_vector_matches
  cid_string_form_lowercase_no_padding_with_b_prefix
  cid_string_form_rejects_uppercase_padding_wrong_prefix
  dasl_cid_codec_raw_is_0x55_drisl_is_0x71
  dasl_cid_hash_type_is_sha2_256_size_is_32
  cid_link_json_projection_is_link_object_not_bare_string
  couch_metadata_excluded_from_canonical_payload
  bencid_v0_string_is_rejected_by_cid_parse
  signed_claim_signs_drisl_bytes_not_canonical_json
  package_cid_unaffected_by_external_claim_addition
  package_canonical_payload_excludes_claim_refs_structurally       *iter2*
  package_canonical_payload_excludes_implementation_snapshot_cids  *iter2*
  descriptor_snapshot_cid_is_drisl_cid
  bundled_hello_world_fixture_round_trips
  hello_world_package_cid_is_structured_drisl_codec_0x71
  hello_world_wasm_blob_cid_is_raw_codec_0x55
  wasm_implementation_snapshot_cid_is_structured_and_distinct_from_blob_cid  *iter2 (renamed; strict)*
  wasm_implementation_snapshot_document_present_in_capsule_and_manifest      *iter2*
  wasm_implementation_snapshot_binds_package_artifact_role_and_type          *iter2*
  capsule_tamper_detection_document_byte_flip
  capsule_tamper_detection_blob_byte_flip
  drisl_round_trip_canonical_bytes_stable
  signed_claim_target_cid_substitution_fails              *iter2*
  signed_claim_target_object_type_substitution_fails      *iter2*
  signed_claim_target_schema_profile_substitution_fails   *iter2*
  signed_claim_target_role_substitution_fails             *iter2*
  signed_claim_claim_type_substitution_fails              *iter2*
  signed_claim_issuer_substitution_fails                  *iter2*
  signed_claim_crypto_profile_substitution_fails          *iter2*
  signed_claim_canonical_encoding_profile_substitution_fails  *iter2*
  signed_claim_payload_substitution_fails                 *iter2*
  package_cid_changes_when_declarative_artifact_cid_changes      *iter3*
  package_cid_changes_when_wasm_artifact_cid_changes             *iter3*
  package_cid_does_not_change_when_only_snapshot_forward_ref_changes  *iter3*
  trust_scope_uses_implementation_cid_not_implementation_id      *iter3*
  capability_scope_uses_implementation_cid_not_implementation_id *iter3*
  implementation_snapshot_substitution_with_same_id_denies_invocation *iter3 (evil capsule, authority scope)*
  implementation_snapshot_must_match_manifest_artifact_cids       *iter4*
  implementation_snapshot_must_match_manifest_entry_id            *iter4*
  implementation_snapshot_must_match_manifest_kind                *iter4*
  implementation_snapshot_must_match_package_cid                  *iter4*
  implementation_snapshot_cid_must_match_manifest_forward_ref     *iter4*
  package_cid_binds_legitimate_artifact_but_snapshot_points_to_malicious_artifact_rejected *iter4 (evil capsule, import+runtime)*
  valid_implementation_snapshot_passes_validator                  *iter4 (sanity)*
  wasm_blob_cid_must_match_manifest_artifact_cids                 *iter5*
  wasm_blob_cid_mismatch_rejected_at_import                       *iter5*
  wasm_blob_cid_mismatch_rejected_before_wasm_invocation          *iter5*
  wasm_capability_grant_uses_artifact_cid_not_unbound_blob_cid    *iter5*
  package_cid_binds_legitimate_wasm_artifact_but_blob_cid_points_to_malicious_blob_rejected *iter5 (evil capsule, blob substitution)*
  declarative_implementation_must_not_have_blob_cid               *iter5*
  valid_implementation_artifact_binding_passes_validator          *iter5 (sanity)*

replication-lite (9):  (unchanged from iteration 1)
  replicated_structured_record_verifies_via_drisl_cid_recompute
  replicated_blob_verifies_via_raw_blob_cid_recompute
  replicated_authority_record_remains_inactive_after_replication
  missing_blob_replication_fails_closed_with_cid_in_error
  private_key_material_does_not_replicate
  cid_mismatch_on_replicated_blob_yields_storage_failure_evidence_record
  replicated_record_with_couch_metadata_strips_it_for_cid_recompute
  unknown_object_type_replication_rejected_as_unsupported
  replicated_capsule_recompute_walks_every_document_cid
```

Report header: `"object_type":"conformance_report","schema_version":"benac.conformance_report.v1","passed":true`.

The previous `implementation_snapshot_cid_distinct_from_blob_cid` check
(which had a permissive `cid == blob_cid` branch the caller flagged as
"blessing the bad behaviour") is gone — replaced by the strict trio
above. There is no grandfather path.

---

## 5. Raw CID vectors (hello + empty)

Captured from the printed suite report:

```
dasl_cid_hello_vector_matches
  detail: Cid::from_raw_bytes(b"hello") = bafkreibm6jg3ux5qumhcn2b3flc3tyu6dmlb4xa7u5bf44yegnrjhc4yeq

dasl_cid_empty_vector_matches
  detail: Cid::from_raw_bytes(b"") = bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku
```

These two strings are pinned to the ticket as the canonical sanity
vectors for the raw codec (`0x55`, SHA-256, base32 lowercase no-padding,
`b` prefix). The conformance check fails immediately on any drift.

---

## 6. DRISL vectors (CID-link Tag 42)

```
drisl_cid_link_tag42_vector_matches
  detail: encoded `{"$link":"..."}` to 41 bytes of CBOR Tag-42
```

Pinned hex (from the conformance constant): the DRISL bytes for
`{"$link":"bafkreibm6jg3ux5qumhcn2b3flc3tyu6dmlb4xa7u5bf44yegnrjhc4yeq"}` are

```
d82a582500015512202cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
```

Matched by `dasl_cid_codec_raw_is_0x55_drisl_is_0x71`,
`drisl_round_trip_canonical_bytes_stable`, and
`couch_metadata_excluded_from_canonical_payload` — all green.

---

## 7. Package / implementation / descriptor / blob / capsule CID examples (regenerated Hello World, iteration 3)

Read from `/srv/benac/hello-world.benac-capsule.json` after iteration-3
fixture regeneration. Every CID below changed from iteration 2 because
the canonical package payload now carries `artifact_cids` for both
implementations (was `blob_cid: Option<Cid>`, which was `None` for the
declarative entry — that omission was iteration 3's blocker). Mutating
either implementation's artifact bytes now flips the package CID; the
two new conformance regressions
`package_cid_changes_when_declarative_artifact_cid_changes` and
`package_cid_changes_when_wasm_artifact_cid_changes` pin that
invariant.

| Role | CID | Codec |
| --- | --- | --- |
| Capsule CID | `bafyreia76ydqfbipkmojfnorsodwwx76sctseyvlc3miu6wygesiwxageq` | DRISL `0x71` |
| Package CID (root) | `bafyreiboa4vr2p3klt4k3odzuticdm7w77gjeah5ngrarczd3hfjyezmvu` | DRISL `0x71` |
| WASM **implementation snapshot** CID | `bafyreiet3cngab4b6qysvmha4dy2gtojdqahfeqjawhlvtn6h7of437re4` | DRISL `0x71` |
| WASM **blob** CID (raw artifact) | `bafkreigz53a67cdf5ysujj277ba2fcr2u6r5uiz5bnxrjpl5tmegrpvsi4` | raw `0x55` |
| Declarative implementation snapshot CID | `bafyreiftvvkh2mrzojstbw7ttqopbdlm75imc7f2fiybe3l4yfds55gg5a` | DRISL `0x71` |
| Declarative transform document CID | `bafyreiel6uilijbmubrfbehkow744hcqnjscleza642bvpwyubfonjeovm` | DRISL `0x71` |
| Signed claim CID | (recomputed by `SignedClaim::cid` over the capsule's signed_claim document) | DRISL `0x71` |

Each `PackageImplementation` entry in the bundled manifest now carries
both the snapshot forward reference (`cid`) and the canonical
`artifact_cids` list:

```
hello-world-declarative:
  cid:           bafyreiftv…gg5a   (declarative implementation snapshot)
  artifact_cids: [bafyreiel6…eovm]  (declarative transform document)
  blob_cid:      null

hello-world-wasm:
  cid:           bafyreiet3…7re4   (wasm implementation snapshot)
  artifact_cids: [bafkreigz5…vsi4]  (raw wasm bytes)
  blob_cid:      bafkreigz5…vsi4
```

The WASM **implementation CID** (`bafyreiet3…7re4`) is a structured
DRISL CID over the wasm `ImplementationSnapshot` document, distinct
from the WASM **blob CID** (`bafkreigz5…vsi4`). Same for the
declarative pair.

The implementation snapshots in the bundle bind:

```
object_type:                 "implementation_snapshot"
schema_version:              "benac.implementation_snapshot.v1"
package_cid:                 bafyreiboa…zmvu   (this package)
implementation_entry_id:     "hello-world-wasm" / "hello-world-declarative"
implementation_type:         "wasm_abi.v0"      / "declarative_transform.v0"
artifact_cids:               [bafkrei…vsi4]     / [bafyrei…eovm]
runtime_requirements:        ["wasm32","no_imports"] / ["declarative_transform_runtime"]
parameters:                  {}
```

Authority records persisted by the runtime now use the new shape:

```
TrustDecision.scope.implementation_cids:
  [{"$link":"bafyreiftv…gg5a"}, {"$link":"bafyreiet3…7re4"}]

CapabilityGrant.grants[local_execution].scope.implementation_cids:
  [{"$link":"bafyreiftv…gg5a"}, {"$link":"bafyreiet3…7re4"}]
```

`ensure_trust` and the `local_execution` capability check compare on
`input.implementation.cid` (the snapshot CID), not
`input.implementation.id`. The `BrowserStation` resolves the
declarative transform via the snapshot's `artifact_cids[0]`, not by
walking the package's `implementation.cid` directly.

Conformance checks now enforced (iteration 4 superset):
- `wasm_implementation_snapshot_cid_is_structured_and_distinct_from_blob_cid` — strict; `cid == blob_cid` FAILS.
- `wasm_implementation_snapshot_document_present_in_capsule_and_manifest` — snapshot is in `documents` and its CID is in `integrity_manifest.object_cids`.
- `wasm_implementation_snapshot_binds_package_artifact_role_and_type` — snapshot's `package_cid`, `implementation_entry_id`, `implementation_type`, and `artifact_cids[0]` actually bind the bundled fixture.
- `package_cid_changes_when_declarative_artifact_cid_changes` — flipping a byte of the declarative transform doc flips the package CID.
- `package_cid_changes_when_wasm_artifact_cid_changes` — flipping a byte of the wasm blob flips the package CID.
- `package_cid_does_not_change_when_only_snapshot_forward_ref_changes` — changing the per-implementation snapshot forward ref does NOT flip the package CID.
- `trust_scope_uses_implementation_cid_not_implementation_id` — `TrustDecision::scope` JSON has `implementation_cids` (as `$link` objects) and NOT `implementations`.
- `capability_scope_uses_implementation_cid_not_implementation_id` — same shape on `CapabilityGrant.grants[local_execution].scope`.
- `implementation_snapshot_substitution_with_same_id_denies_invocation` — the iter-3 evil-capsule denial test: substituting an implementation snapshot under the same `id`/`kind` (different `parameters` → different snapshot CID) is rejected with `benac.error.implementation_not_trusted`.
- **(iter4)** `implementation_snapshot_must_match_manifest_artifact_cids` — validator rejects mismatched `snapshot.artifact_cids`.
- **(iter4)** `implementation_snapshot_must_match_manifest_entry_id` — validator rejects mismatched `snapshot.implementation_entry_id`.
- **(iter4)** `implementation_snapshot_must_match_manifest_kind` — validator rejects mismatched `snapshot.implementation_type`.
- **(iter4)** `implementation_snapshot_must_match_package_cid` — validator rejects mismatched `snapshot.package_cid`.
- **(iter4)** `implementation_snapshot_cid_must_match_manifest_forward_ref` — validator rejects when `snapshot.cid()` does not equal the manifest's forward reference.
- **(iter4)** `package_cid_binds_legitimate_artifact_but_snapshot_points_to_malicious_artifact_rejected` — the end-to-end evil-capsule attack: package CID byte-identical to legitimate, but implementation snapshot points at malicious artifacts. Rejected at both gates: `Capsule::validate_and_inspect` (import) and `BrowserStationRuntime::declarative_transform_document` (runtime). Failure code: `benac.error.implementation_snapshot_mismatch`. Failure message: `implementation snapshot artifact_cids do not match package implementation artifact_cids`.
- **(iter4)** `valid_implementation_snapshot_passes_validator` — synthetic consistent pair plus the two bundled HW1 fixture snapshots all pass `validate_against_package_entry`.
- **(iter5)** `wasm_blob_cid_must_match_manifest_artifact_cids` — direct check on `PackageImplementation::validate_artifact_binding`: a wasm entry where `blob_cid != artifact_cids[0]` is rejected with `benac.error.implementation_artifact_mismatch`.
- **(iter5)** `wasm_blob_cid_mismatch_rejected_at_import` — the import-time gate: `Capsule::validate_and_inspect` rejects a manifest whose wasm entry has `blob_cid != artifact_cids[0]`. Same code.
- **(iter5)** `wasm_blob_cid_mismatch_rejected_before_wasm_invocation` — runtime gate via `BrowserStationRuntime::invocation_input` rejects a malformed implementation entry before any executor is invoked. Same code.
- **(iter5)** `wasm_capability_grant_uses_artifact_cid_not_unbound_blob_cid` — `approve_no_risk_hello_world`'s `local_blob_read_exact` grant scope is derived from `artifact_cids` of `wasm_abi.v0` implementations (all raw codec); declarative implementations do not leak into the scope.
- **(iter5)** `package_cid_binds_legitimate_wasm_artifact_but_blob_cid_points_to_malicious_blob_rejected` — the end-to-end evil-capsule blob-substitution attack: package CID byte-identical to legitimate (manifest `artifact_cids` unchanged), wasm implementation snapshot legitimate (passes iter-4 validator), but `manifest.implementations[wasm].blob_cid` swapped to a malicious raw blob. The malicious blob is added to `capsule.blobs` and `integrity_manifest.blob_cids` so capsule integrity recomputes cleanly. The import gate rejects with `benac.error.implementation_artifact_mismatch` (message `wasm blob_cid must exactly match artifact_cids[0]`).
- **(iter5)** `declarative_implementation_must_not_have_blob_cid` — direct check on the declarative-kind branch of the validator: a declarative entry with `blob_cid: Some(_)` is rejected. Same code.
- **(iter5)** `valid_implementation_artifact_binding_passes_validator` — synthetic valid wasm + synthetic valid declarative + bundled HW1 fixture's two implementation entries all pass `validate_artifact_binding`.

Note the codec discrimination:
- structured documents (capsule, package, implementation snapshots, schema, declarative transform, signed_claim) have `bafy…` (DRISL, codec `0x71`).
- raw blobs have `bafk…` (raw, codec `0x55`).

---

## 8. Grep output proving no portable fixture contains `bencid:v0`

```
$ grep -c 'bencid:v0' \
    /srv/benac/hello-world.benac-capsule.json \
    /srv/benac/apps/pwa/public/fixtures/hello-world.benac-capsule.json
/srv/benac/apps/pwa/public/fixtures/hello-world.benac-capsule.json:0
/srv/benac/hello-world.benac-capsule.json:0

$ bash /srv/benac/scripts/check-cid-grep-gates.sh
OK: all CID grep gates pass
```

Live deployed fixture (extra rigour, iteration 3):

```
$ curl -fsSL https://benac.benac.dev/hello-world.benac-capsule.json | grep -c 'bencid:v0'
0
$ curl -fsSL https://benac.benac.dev/hello-world.benac-capsule.json | grep -oc '"\$link"'
26
```

Zero `bencid:v0` strings, twenty-six `$link` projections (iteration 3's
per-implementation `artifact_cids` arrays add two link refs over
iteration 2's twenty-four) — the production asset matches the
source-tree fixture.

---

## 9. CLI lifecycle output (init, import, inspect, approve, declarative + WASM invoke, traces export)

Run against a fresh `mktemp -d` state directory using
`target/debug/benac-station`. Output:

```
=== init ===
Initialized Station at $STATEDIR
station_principal: benac-principal:v0:ed25519:dZTPq74aj9eGFq30o1ynaKLlulS9jNcpxVT8FhV88tA

=== import ===
Imported capsule.
  package_cid: bafyreibd7fdi4dqijerqcoybwzgtsjigmeueu4s2kxryt4xjk3d5apm6cy
  claims: 1/1 verified
  blobs: 1
  trust_status: untrusted

=== inspect package ===
package_cid: bafyreibd7fdi4dqijerqcoybwzgtsjigmeueu4s2kxryt4xjk3d5apm6cy
label: hello-world version: 0.0.1
claims: 1/1 verified
blobs: 1
trust_status: untrusted
implementations:
  - hello-world-declarative (declarative_transform.v0)
  - hello-world-wasm (wasm_abi.v0)

=== approve hello-world --no-risk ===
Approved package for local no-risk execution.
  package_cid: bafyreibd7fdi4dqijerqcoybwzgtsjigmeueu4s2kxryt4xjk3d5apm6cy
  trust_expires_at: 2026-05-03T14:04:20Z
  capability_grants: 4

=== invoke declarative ===
invocation_id: bafyreicubvrttv67hlfwetsgfhgusoamtvoqqcgiuayqoyuunnttmknlty
status: succeeded
implementation: hello-world-declarative
output: {"message":"hello","reply":"world"}

=== invoke wasm ===
invocation_id: bafyreieyzbwidu5hay4ed2few2c3gtr3wg7vqqvb2wm73acl4mbkn7kfie
status: succeeded
implementation: hello-world-wasm
output: {"message":"hello","reply":"world"}

=== traces list ===
bafyreicubvrttv67hlfwetsgfhgusoamtvoqqcgiuayqoyuunnttmknlty  [succeeded]  hello-world-declarative
bafyreieyzbwidu5hay4ed2few2c3gtr3wg7vqqvb2wm73acl4mbkn7kfie  [succeeded]  hello-world-wasm

=== traces export ===
Exported trace capsule -> /tmp/trace-capsule.json (62,490 bytes)
```

Both implementations produce the canonical `{"message":"hello","reply":"world"}` output. Both invocation IDs are structured DRISL CIDs (`bafy…`). The `inspect package` output confirms `1/1 verified` claim and trust transition from `untrusted` to active.

---

## 10. PWA invocation output (proxy + live URL evidence)

The PWA cannot be exercised from a subagent shell, so this item is
provided in two parts that together cover the same execution path the
PWA uses:

**(a) station-cli end-to-end test as an in-process proxy.** The same
`benac_browser::BrowserStationRuntime` kernel that drives the PWA is
exercised by `apps/station-cli/tests/end_to_end.rs`. The headline test
covers the full chain:

```
test full_lifecycle_init_import_approve_invoke_export_reset ... ok
```

Plus surrounding rehydrate/reset/error-path coverage:

```
test invoke_rejects_unknown_package ... ok
test invoke_rejects_invalid_request_json ... ok
test init_without_force_is_idempotent ... ok
test invoke_before_approve_persists_missing_trust_denial ... ok
test corrupt_blob_on_disk_yields_blob_hash_mismatch_after_rehydration ... ok
test rehydrate_does_not_activate_foreign_inactive_authority_tuple ... ok
test rehydrate_restores_imported_package_and_approval_so_reload_does_not_require_reimport ... ok
test result: ok. 8 passed; 0 failed
```

**(b) Live URL evidence.** The deploy.sh "kernel JS bundle hash match"
guarantees the bundle bytes shipped to <https://benac.benac.dev/> are
byte-identical to the in-tree dist that those tests exercise. Health
probes after deploy:

```
kernel PWA index                           200
kernel PWA service worker                  200
kernel PWA manifest                        200
kernel PWA bundled capsule                 200
acceptance dashboard                       200
acceptance instrument.js                   200
acceptance instrumented PWA                200
acceptance events listing                  200
```

Honest framing: PWA invocation behavior is verified
*by-construction* — the PWA links to the same kernel proven in (a) and
the bundle is byte-identical to that local kernel.

---

## 11. Replication test output (DRISL CID verification + foreign authority inactivity)

`apps/station-cli/tests/sync_end_to_end.rs` — 8 tests, all green:

```
sync_configure_rejects_invalid_workspace_name        ... ok
sync_configure_persists_peer                         ... ok
sync_status_json_shape                               ... ok
sync_pull_marks_authority_records_inactive_on_this_station ... ok
sync_reset_checkpoint_clears_cursor_only             ... ok
sync_pull_rejects_tampered_blob_with_evidence        ... ok
sync_push_excludes_station_keypair_and_authority_records ... ok
sync_push_then_pull_round_trips_eligible_public_documents ... ok
test result: ok. 8 passed; 0 failed
```

And the matching invariants in `benac-conformance::run_full_suite`
(extracted from the printed suite report):

```
replicated_structured_record_verifies_via_drisl_cid_recompute      passed
replicated_blob_verifies_via_raw_blob_cid_recompute                passed
replicated_authority_record_remains_inactive_after_replication     passed
missing_blob_replication_fails_closed_with_cid_in_error            passed
private_key_material_does_not_replicate                            passed
cid_mismatch_on_replicated_blob_yields_storage_failure_evidence_record  passed
replicated_record_with_couch_metadata_strips_it_for_cid_recompute  passed
unknown_object_type_replication_rejected_as_unsupported            passed
replicated_capsule_recompute_walks_every_document_cid              passed
```

The `replicated_authority_record_remains_inactive_after_replication`
check directly satisfies the ticket's foreign-authority-inactivity
no-go condition; `private_key_material_does_not_replicate` covers the
no-private-key requirement.

---

## Acceptance criteria checklist

### Identity and encoding

- [x] `ContentId` parses and emits canonical DASL CID v1 strings — `cid_string_form_lowercase_no_padding_with_b_prefix`, `cid_string_form_rejects_uppercase_padding_wrong_prefix`.
- [x] Raw blob CIDs use codec `0x55` and SHA-256 over exact raw bytes — `dasl_cid_codec_raw_is_0x55_drisl_is_0x71`, `dasl_cid_hash_type_is_sha2_256_size_is_32`, `hello_world_wasm_blob_cid_is_raw_codec_0x55`.
- [x] Structured document CIDs use codec `0x71` and SHA-256 over canonical DRISL bytes — `hello_world_package_cid_is_structured_drisl_codec_0x71`, `descriptor_snapshot_cid_is_drisl_cid`.
- [x] CID string encoding is lowercase RFC 4648 base32, prefix `b`, no padding — `cid_string_form_lowercase_no_padding_with_b_prefix`.
- [x] Malformed CIDs are rejected with stable machine-readable errors — `bencid_v0_string_is_rejected_by_cid_parse`, `cid_string_form_rejects_uppercase_padding_wrong_prefix`.
- [x] DRISL canonical encoding implemented and tested — `drisl_round_trip_canonical_bytes_stable`.
- [x] CID links encode as CBOR Tag 42 over `0x00 || binary_dasl_cid` — `drisl_cid_link_tag42_vector_matches` (pinned hex vector).
- [x] JSON projection CID links use `{"$link":"b..."}` — `cid_link_json_projection_is_link_object_not_bare_string`.
- [x] Couch/Pouch metadata is excluded from canonical payloads — `couch_metadata_excluded_from_canonical_payload`.

### Package / capsule / invocation

- [x] Hello World package CID is a structured DRISL CID — `hello_world_package_cid_is_structured_drisl_codec_0x71`. CID (iteration 3): `bafyreiboa4vr2p3klt4k3odzuticdm7w77gjeah5ngrarczd3hfjyezmvu`.
- [x] WASM artifact CID is a raw blob CID — `hello_world_wasm_blob_cid_is_raw_codec_0x55`. CID: `bafkreigz53a67cdf5ysujj277ba2fcr2u6r5uiz5bnxrjpl5tmegrpvsi4`.
- [x] Implementation CID is structured implementation snapshot CID, not the WASM blob CID — `wasm_implementation_snapshot_cid_is_structured_and_distinct_from_blob_cid` (strict). Plus sibling checks `wasm_implementation_snapshot_document_present_in_capsule_and_manifest` and `wasm_implementation_snapshot_binds_package_artifact_role_and_type` enforce that the snapshot is real and binds correctly. The wasm impl CID `bafyreiet3…7re4` (DRISL) is distinct from the wasm blob CID `bafkreigz5…vsi4` (raw).
- [x] **(iter3)** Package CID binds the bytes of every implementation artifact — `package_cid_changes_when_declarative_artifact_cid_changes`, `package_cid_changes_when_wasm_artifact_cid_changes`, `package_cid_does_not_change_when_only_snapshot_forward_ref_changes`. The canonical payload now exposes `artifact_cids: Vec<Cid>` per implementation (was `blob_cid: Option<Cid>`, which was `None` for the declarative entry).
- [x] **(iter3)** Trust + capability scopes bind authority to implementation CID, not to human-readable id — `trust_scope_uses_implementation_cid_not_implementation_id`, `capability_scope_uses_implementation_cid_not_implementation_id`. `TrustScope` now carries `implementation_cids: Vec<Cid>` (was `implementations: Vec<String>`); `CapabilityGrant`'s `local_execution` scope mirrors that.
- [x] **(iter3)** Implementation snapshot substitution under the same id is denied — `implementation_snapshot_substitution_with_same_id_denies_invocation`. Failure code: `benac.error.implementation_not_trusted`. Status: `denied`.
- [x] **(iter4)** Implementation snapshot is fully consistent with the package manifest entry before the runtime relies on it — `ImplementationSnapshot::validate_against_package_entry` runs at import (`Capsule::validate_and_inspect`) and at invocation (`BrowserStationRuntime::declarative_transform_document`). Per-field tests `implementation_snapshot_must_match_manifest_artifact_cids`, `_entry_id`, `_kind`, `_package_cid`, plus the structural backstop `implementation_snapshot_cid_must_match_manifest_forward_ref`. Evil-capsule regression `package_cid_binds_legitimate_artifact_but_snapshot_points_to_malicious_artifact_rejected`. Sanity check `valid_implementation_snapshot_passes_validator` (synthetic + bundled HW1). Failure code: `benac.error.implementation_snapshot_mismatch`. Status: `denied`.
- [x] **(iter5)** Implementation `blob_cid` projection is structurally bound to canonical `artifact_cids` before the runtime relies on it — `PackageImplementation::validate_artifact_binding` runs at schema time (`PackageManifest::validate_value`), at import (`Capsule::validate_and_inspect`), and at invocation (`BrowserStationRuntime::invocation_input`). The WASM executor reads its executable artifact CID from `implementation.artifact_cids[0]`; `approve_no_risk_hello_world` derives the `local_blob_read_exact` grant scope from `artifact_cids` of `wasm_abi.v0` implementations. Direct tests `wasm_blob_cid_must_match_manifest_artifact_cids`, `declarative_implementation_must_not_have_blob_cid`, `valid_implementation_artifact_binding_passes_validator`. Gate tests `wasm_blob_cid_mismatch_rejected_at_import`, `wasm_blob_cid_mismatch_rejected_before_wasm_invocation`, `wasm_capability_grant_uses_artifact_cid_not_unbound_blob_cid`. Evil-capsule regression `package_cid_binds_legitimate_wasm_artifact_but_blob_cid_points_to_malicious_blob_rejected`. Failure code: `benac.error.implementation_artifact_mismatch`. Status: `denied`.
- [x] Descriptor snapshot CID is a structured DRISL CID — `descriptor_snapshot_cid_is_drisl_cid`.
- [x] Invocation/evidence/effect/audit records use CID vocabulary and CID links — CLI lifecycle item 9 shows `invocation_id` as DRISL CIDs.
- [x] Signed claim fixture signs DRISL bytes, verifies under new envelope rules — `signed_claim_signs_drisl_bytes_not_canonical_json`. CLI `import` reports `claims: 1/1 verified`.
- [x] Adding/removing external signed claims does not change package CID — `package_cid_unaffected_by_external_claim_addition`.
- [x] Capsule integrity uses object CIDs and blob CIDs — see item 7; integrity_manifest carries object_cids + blob_cids + capsule_cid as $link projections.
- [x] Tampering with any included hash/CID-verified material is detected — `capsule_tamper_detection_document_byte_flip`, `capsule_tamper_detection_blob_byte_flip`, `corrupt_blob_on_disk_yields_blob_hash_mismatch_after_rehydration`, `sync_pull_rejects_tampered_blob_with_evidence`.

### Replication

- [x] C2-lite replicated structured records verified by recomputing DRISL CIDs — `replicated_structured_record_verifies_via_drisl_cid_recompute`, `replicated_capsule_recompute_walks_every_document_cid`.
- [x] Replicated blobs verified by recomputing raw blob CIDs — `replicated_blob_verifies_via_raw_blob_cid_recompute`, `cid_mismatch_on_replicated_blob_yields_storage_failure_evidence_record`.
- [x] Replicated authority records remain inactive on receiving Station — `replicated_authority_record_remains_inactive_after_replication`, `sync_pull_marks_authority_records_inactive_on_this_station`, `rehydrate_does_not_activate_foreign_inactive_authority_tuple`.
- [x] Missing blob behavior fails closed and reports missing blob CID — `missing_blob_replication_fails_closed_with_cid_in_error`.
- [x] No private key material replicates — `private_key_material_does_not_replicate`, `sync_push_excludes_station_keypair_and_authority_records`.

### Conformance and regression

- [x] `cargo fmt --all --check` passes.
- [x] `cargo clippy --workspace --all-targets -- -D warnings` passes.
- [x] `cargo test --workspace` passes — see item 3.
- [x] `benac-conformance::run_full_suite` includes DASL CID + DRISL vectors and passes — 62/62 green after iteration 5 (53 hello-world + 9 replication-lite; item 4).
- [x] CLI lifecycle still works: init, import, inspect, approve, declarative invoke, WASM invoke, export, reset, rehydrate, sync push/pull — item 9 + station-cli end-to-end suite (8/8) + sync_end_to_end suite (8/8).
- [x] PWA lifecycle still works — proxy + live evidence per item 10.
- [x] Generated PWA fixture and root capsule contain no `bencid:` values — item 8 (grep counts = 0 for both).

## No-go conditions checklist

All cleared.

- [x] **No** `bencid:v0:*` in new portable records — confirmed by item 8 (grep gate + live URL probe).
- [x] **No** conformance check still keys on `starts_with("bencid:v0")` — `bencid_v0_string_is_rejected_by_cid_parse` actively rejects it.
- [x] Structured identity is **not** computed from canonical JSON — replaced by DRISL canonical bytes (`drisl_round_trip_canonical_bytes_stable`).
- [x] Signatures are **not** verified over canonical JSON — `signed_claim_signs_drisl_bytes_not_canonical_json`.
- [x] Raw blob CID does **not** depend on object role / domain — `hello_world_wasm_blob_cid_is_raw_codec_0x55` pins the codec discriminator.
- [x] `_id`, `_rev`, sync metadata, UI state, local availability, local verification state do **not** change structured object CID — `couch_metadata_excluded_from_canonical_payload`, `replicated_record_with_couch_metadata_strips_it_for_cid_recompute`.
- [x] WASM artifact blob CID is **not** used as the implementation CID — `wasm_implementation_snapshot_cid_is_structured_and_distinct_from_blob_cid` (strict; permissive grandfather branch removed). Implementation snapshot CID `bafyreiet3…7re4` (DRISL `0x71`) is distinct from blob CID `bafkreigz5…vsi4` (raw `0x55`) — see item 7.
- [x] **(iter3)** Authority is **not** bound to human-readable implementation id — trust + capability scopes carry implementation CIDs, so a same-id substitution attack fails closed (`implementation_snapshot_substitution_with_same_id_denies_invocation`, `benac.error.implementation_not_trusted`).
- [x] **(iter3)** Package canonical identity does **not** ignore declarative-implementation artifact bytes — `artifact_cids: Vec<Cid>` covers both wasm and declarative artifacts uniformly (`package_cid_changes_when_declarative_artifact_cid_changes`, `package_cid_changes_when_wasm_artifact_cid_changes`).
- [x] **(iter4)** Implementation snapshot is **not** treated as authoritative without consistency-checking against the package manifest entry — `validate_against_package_entry` runs at import and at runtime; per-field tests + the evil-capsule regression cover snapshot-substitution attacks that preserve the package CID but point to malicious artifacts.
- [x] **(iter5)** `blob_cid` is **not** an authoritative runtime selector unless it has been validated equal to the canonical `artifact_cids[0]` — `validate_artifact_binding` runs at schema, import, and invocation gates; the WASM executor reads from `artifact_cids[0]` (the canonical-identity-bearing field) and the `local_blob_read_exact` grant scope is derived from `artifact_cids` of `wasm_abi.v0` implementations; the evil-capsule blob-substitution regression confirms the gap is closed.
- [x] JSON projection does **not** emit bare CID strings for portable CID links — `cid_link_json_projection_is_link_object_not_bare_string`.
- [x] Replicated foreign authority records do **not** become active after reload — `replicated_authority_record_remains_inactive_after_replication`, `rehydrate_does_not_activate_foreign_inactive_authority_tuple`.
- [x] Missing/tampered blob tests still leave receipts — `missing_blob_replication_fails_closed_with_cid_in_error`, `cid_mismatch_on_replicated_blob_yields_storage_failure_evidence_record`, `sync_pull_rejects_tampered_blob_with_evidence`.
- [x] Hello World import/invoke does **not** rely on legacy identifiers — fresh CLI run (item 9) with regenerated fixture, no `bencid:v0` anywhere; invocation uses structured DRISL CIDs throughout.

---

## Deploy summary (iteration 5)

Iteration 5 shipped kernel-source changes (new validator on
`PackageImplementation` + three call-sites in `benac-core` and
`benac-browser`, executor + capability grant updates that read from
canonical `artifact_cids`, the denied-error classifier, and seven new
conformance regressions). The dist artifacts produced from this branch
have a fresh PWA bundle hash. The bundled
`hello-world.benac-capsule.json` payload is byte-identical to iteration
4 because canonical-payload semantics did not change and the bundled
HW1 wasm implementation already had `blob_cid == artifact_cids[0]` by
construction.

Iteration-5 deploy log (handler runs `scripts/deploy-benac.sh`):

```
=== Building PWA dist (containerised cargo + trunk) ===  OK
=== Repackaging benac-repo.zip ===                      OK
=== Building gravitational_lens:local image ===         OK
=== Restarting benac-gravitational-lens.service ===     OK
=== Caddy validate + reload ===                         OK  (warns: deprecated header_up; non-blocking)

=== Health probes ===
  kernel PWA index                           200
  kernel PWA service worker                  200
  kernel PWA manifest                        200
  kernel PWA bundled capsule                 200
  repo zip                                   200
  tickets index                              200
  acceptance dashboard                       200
  acceptance instrument.js                   200
  acceptance instrumented PWA                200
  acceptance events listing                  200
  kernel JS bundle hash match                local=benac-pwa-4d18cfb2a790135b.js public=benac-pwa-4d18cfb2a790135b.js

=== Deploy complete (iter 5) ===
Build sha:           b8dab41f8f7dba16523c697210fe524acccaef21
PWA bundle:          benac-pwa-4d18cfb2a790135b.js
gravitational_lens:  sha256:c9e3c1292a1056dcaa4a79d95c5765a97c0be0c9fd7761f0b217705fd0acb462
Public PWA:          https://benac.benac.dev/
Public repo zip:     https://benac.benac.dev/benac-repo.zip
Acceptance dash:     https://benac.benac.dev/acceptance/dashboard
Acceptance events:   https://benac.benac.dev/acceptance/events
Tickets:             https://benac.benac.dev/tickets/
```

Live verification of byte-identity (iter-4 vs iter-5 bundled capsule):
```
$ curl -fsSL https://benac.benac.dev/hello-world.benac-capsule.json -o /tmp/live.json
$ diff -q /tmp/live.json hello-world.benac-capsule.json
[no output — files identical]
$ grep -c 'bencid:v0' /tmp/live.json
0
$ grep -oc '"\$link"' /tmp/live.json
26
```

The validator is gate code (kernel), not fixture code; the fixture is
unchanged because its existing fields were already consistent (a
fully-consistent pair was the goal). All iter-5 enforcement happens
at schema, import, and invocation gates against any candidate
implementation entry.

---

## Reproducibility footnote

The closeout introduced one tooling-only change: a new
`crates/benac-conformance/examples/print_suite.rs` whose only purpose is
to emit `run_full_suite()` as JSON for evidence capture. It is not on
the runtime path. Future closeouts can rerun it as:

```
cargo run -p benac-conformance --example print_suite
```

This file should be committed alongside the closeout if not already in
the tree on `feat/dasl-cid-drisl`. (It is present at the time of this
report; commit it before the merge if reviewing this branch fresh.)

---

## Follow-on ticket 49f14a3d — `PackageDocument` rename + terminology purge

The iter-5 closeout above resolved BENAC-CID-DRISL-001. A follow-on
ticket (`49f14a3d-a9bb-4914-89fd-055d596e2772`) addressed the residue:
the legacy "manifest" / projection vocabulary, the disused per-implementation
forward-reference fields, and the absence of a source-side gate to
prevent regression on the new terminology.

This section is appended to the same report rather than starting a new
file because the work is the structural simplification of the same
identity model — the iter-5 narrative explains *why* every executable
field had to be folded into the package CID, and the follow-on ticket
finished folding them in at the type level.

### Phase 1 — `PackageDocument` + `PackageImplementationEntry` refactor

`PackageManifest` and `PackageCanonicalPayload` are gone. The new
`PackageDocument` struct's DRISL CID *is* the package CID — the
projection layer between manifest and "payload-for-CID" no longer
exists. `PackageImplementationEntry` carries only fields that are part
of package identity: `id`, `kind`, `artifact_cids`, `runtime_requirements`,
`parameters`. The legacy per-implementation `cid` (forward reference to
a snapshot) and `blob_cid` (raw artifact projection) are deleted; the
raw artifact CID for a wasm implementation is now `artifact_cids[0]`
directly. `runtime_requirements` and `parameters` moved INTO the
package document, so any change to them flips the package CID — the
iter-3/4 split-identity attack model is now structurally impossible.

### Phase 2 — Consumers swept to the new vocabulary

Every consumer of the deleted types/fields was updated in a single
sweep: `benac-core`, `benac-browser`, `benac-fixtures`, `benac-conformance`,
`apps/pwa`, `xtask`, `tools/station-cli`. No tombstones, no
`#[deprecated]`, no parallel old-format paths — zero users, clean
cutover.

### Phase 3 — "canonical" terminology purge from source

The word *canonical* was used throughout the iter-1..iter-5 narrative
to describe the projection-layer identity surface. With the projection
layer gone, the word names a structure that no longer exists. Phase 3
applied a rename table and rewrote every "canonical" use in source,
prose comments, and documentation under `crates/`, `apps/`, `packages/`,
`xtask/`, and `tools/` (excluding `tools/gravitational_lens/src/sync_relay.rs`,
whose HTTP-API field-name terminology is out of scope for the Benac
kernel rename). Records under `records/` were reterminologized in commit
`5277a082` (closing acceptance criterion 13).

### Phase 4 — Source grep gate + acceptance-criterion-11 conformance

`scripts/check-no-canonical-in-source.sh` is the formal gate. It fails
on any case-insensitive hit of the banned token in `crates/`, `apps/`,
`packages/`, `xtask/`, `tools/` (minus the excluded relay file), and
`scripts/`. The Rust `std` filesystem API names (`Path::canonicalize`,
`std::fs::canonicalize`, `.canonicalize()` method calls) are
allowlisted because the std library uses the same root word and that
is unavoidable in code that resolves filesystem paths. The banned
token is constructed at runtime so the script does not self-trigger.

The `benac-conformance` Hello-World suite gained six new checks per
acceptance criterion 11:

- `changing_any_package_document_field_changes_package_cid` — every
  identity-bearing field of `PackageDocument` flips the package CID
  when mutated (loop over `label`, `version_label`, `interfaces`,
  `schemas`, `implementations`, `declared_capabilities`,
  `forbidden_capabilities`, `fixtures`, `implementations[].runtime_requirements`,
  `implementations[].parameters`).
- `adding_separate_claim_document_does_not_change_package_cid` — the
  package CID is a function of the document alone; appending a
  `signed_claim` document next to it leaves the package CID
  byte-identical.
- `package_document_with_claim_refs_field_is_rejected` — schema-time
  rejection of the legacy `claim_refs` projection.
- `package_document_with_per_implementation_cid_field_is_rejected` —
  schema-time rejection of the legacy per-implementation `cid`
  forward-reference.
- `package_document_with_per_implementation_blob_cid_field_is_rejected`
  — schema-time rejection of the legacy per-implementation
  `blob_cid` projection.
- `runtime_requirements_or_parameters_mismatch_between_package_entry_and_implementation_snapshot_is_rejected`
  — `validate_against_package_entry` rejects snapshots whose
  `runtime_requirements` or `parameters` drift from the package entry.

After Phase 4 the conformance suite carries 50 hello-world checks plus
9 replication-lite checks, for a total of 59 (was 53 after the iter-5
closeout).

### Verification — Phase 4

```
$ cargo fmt --all --check
[no output]
$ cargo clippy --workspace --all-targets -- -D warnings
[no warnings]
$ cargo test --workspace
[all green]
$ cargo test --manifest-path tools/gravitational_lens/Cargo.toml
test result: ok. 145 passed; 0 failed
$ bash scripts/check-cid-grep-gates.sh
OK: all CID grep gates pass
$ bash scripts/check-no-canonical-in-source.sh
OK: no banned terminology in source
$ cargo run -p benac-conformance --example print_suite | tail -1
[summary] checks=59 passed=59 failed=0
```
