Skip to content

Sign the provenance chain, open the FrameKind vocabulary - #87

Merged
macanderson merged 2 commits into
mainfrom
worktree-provenance-attestation
Aug 28, 2026
Merged

Sign the provenance chain, open the FrameKind vocabulary#87
macanderson merged 2 commits into
mainfrom
worktree-provenance-attestation

Conversation

@macanderson

@macanderson macanderson commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Closes #92 — its Definition of done checklist is fully ticked and verified.

Addresses a review of the protocol surface. Four of the seven technical items raised turned out to be already handled — tokenizer identity (ADR 0003 defines a byte-based accounting unit, deliberately not a tokenizer), bitemporal time (valid_from/valid_to/recorded_at all present), egress granularity (EgressScope's four-class vocabulary with per-scope consent), and GOVERNANCE.md itself. This PR is the remainder.

1. Provenance attestation — SPEC.md §6.5 (F6–F9), ADR 0010

F5 makes provenance tamper-evident. It does not make it evidence. A digest proves the bytes have not changed since someone wrote that number down; the digest and the frame it describes come from the same unauthenticated party, so a provider willing to fabricate a frame fabricates its digest too and every §6.2 check passes.

RecordAttestation already declared the right shape at the record layer — and the workspace carried no cryptographic dependency at all, so nothing implemented or verified it.

contextgraph_types::attest adds:

  • A hash chain over provenance links, folded source-first. A set of per-link digests binds each link's content but not the chain's shape; truncating a chain to hide a derivation step was undetectable.
  • A frame commitment binding the chain head to the full (provider_id, frame_id, content_digest) triple. Without the identity binding the scheme is a forgery primitive: two frames citing the same source share a chain head, so a signature over the head alone lifts off one frame and staples onto a fabricated one.
  • An RFC 6962 Merkle root with inclusion proofs, so one frame can be proven a member of a signed answer without disclosing its siblings. The "duplicate the last leaf" shortcut is rejected — it admits two leaf sets with the same root, fine for a checksum and disqualifying for evidence.
  • Offline verification returning a named verdict. "Altered after signing" and "truncated key" call for opposite responses.

Design notes: the encoding is length-prefixed rather than JCS (a provenance link is six optional strings; JCS would add a dependency on a conforming JSON canonicalizer for no gain, and length prefixes make the encoding injective — without them uri:"ab",range:"c" collides with uri:"a",range:"bc"). Cryptography is behind an off-by-default attestation feature, so contextgraph-types still resolves to serde alone by default. frame_commitment is public so HSM/KMS holders sign the bytes themselves — the protocol specifies the preimage, not key custody.

contextgraph-types/tests/attestation_vectors.rs publishes byte vectors for cross-language reconciliation; a diff there is a wire-breaking change.

2. FrameKind is an open vocabulary — ADR 0011

Rust-semver breaking, wire-compatible. The closed enum contradicted the spec's own §13 U2, which already required a receiver to accept an unrecognised kind and not crash. It did worse than not degrade — a 1.1 frame failed to deserialize on a 1.0 host, and in the NDJSON binding that fails the envelope.

Unknown(String) follows the EgressScope precedent and preserves the original string, so a relaying host re-emits it byte-identically. #[serde(other)] cannot do this — it discards the value, so a 1.0 host would silently rewrite a 1.1 frame it was merely passing through. #[non_exhaustive] makes every future kind addition non-breaking. The cost is Copy.

On the wire the only behavioural change is that a frame which previously failed to parse now parses — strictly more compatible, so contextgraph/1 is intact.

TS/Python SDKs gain KnownFrameKind + KNOWN_FRAME_KINDS; Go already typed Kind as string. The JSON Schema keeps its closed enum (it is a documented authoring lint, not the interop contract) with a $comment saying so at the one place a reader would misread it.

3. score semantics — SPEC.md §6.6 (F10)

F1 gave score a range, not a scale. Now stated as provider-local and ordinal. Mandating calibration was considered and rejected as unenforceable — §7 could make budget honesty checkable because token cost is a function of bytes both sides observe, and relevance has no such anchor; a provider could satisfy any calibration rule we wrote while its numbers stayed meaningless.

F10 forbids cross-provider thresholds and presenting raw scores as cross-provider relevance. It does not forbid ordering — order_by_value genuinely needs a total order — but requires the host to own it as policy. fold_to_edges is now public so a host with its own reranker gets the placement without the ranking.

4. GOVERNANCE.md — the consent boundary

The host enforces local consent, not organizational policy.

Fleet RBAC, central policy distribution, and aggregated cross-machine audit are out of scope. Written down now because the pressure runs one way: every individual request to let the host "just" read a policy file is reasonable, and collectively they make "conformant" mean "connected to someone's control plane."

5. Doc fixes

  • The version-compatibility example gave contextgraph/1.0 and contextgraph/1.0 as two interoperating versions — in seven files, the residue of a global rename.
  • Stale pre-freeze language in docs/registry.md / docs/index.md describing a freeze that already happened, plus a dead GOVERNANCE.md#the-path-to-contextgraph10 anchor.
  • README: "The canonical architecture" → the machine-checked-conformance claim; title said "draft v0.1.0" while the protocol had frozen and the crates shipped 1.0.0.

Verification

cargo clippy --workspace --all-targets -- -D warnings clean; cargo fmt --check clean. contextgraph-types 132 unit + 5 reference-vector tests, contextgraph-host 136, contextgraph-conformance full suite including the ~150s integration run — zero failures. Dependency tree confirmed: serde only by default, + sha2 + ed25519-dalek under the feature.

Not verified here: the TypeScript SDK was not typechecked (no local toolchain; npx tsc failed). CI should cover it.

5. The crate major-version bump (was the draft blocker)

This PR was held in draft pending the crate major-version decision — #92. That is now applied, so it leaves draft.

The crates go to 2.0.0 while the protocol version stays contextgraph/1.0: the FrameKind change is breaking in Rust and invisible on the wire, which is the first case where docs/stability.md's two axes actually disagree. All nine workspace members inherit the bump; the internal contextgraph-* floors move >=1.0.0>=2.0.0, because contextgraph-host 2.0.0 does not compile against contextgraph-types 1.x and the old floor admitted a resolvable-but-unbuildable pair. The Python and TypeScript SDK manifests move in lockstep — FrameKind widens in both type systems, so an exhaustive switch relying on never-narrowing stops type-checking — as does create-contextgraph-provider, whose TypeScript default pin had additionally fallen a major behind its own manifest at ^0.1.0.

docs/stability.md needed correcting to permit its own bump: it said a breaking redesign required "both contextgraph/2.0 and crate version 2.0.0" — two lines after declaring the axes independent. It now states the implication in the direction that holds. MIGRATION.md §5 gives the three call-site shapes to fix; each is a compile error, so the compiler enumerates the work and nothing changes silently at runtime.

Re-verified after the bump: cargo check/test/clippy -D warnings clean on contextgraph-types + contextgraph-host (136 host, 132 types under --features attestation, 5 reference vectors, 0 failures); contextgraph-conformance, contextgraph-trace, contextgraph-mcp-bridge, contextgraph-mcp-server all check clean at the new floors; cargo fmt --check clean; Cargo.lock in sync.

Summary by Sourcery

Strengthen provenance evidence and forward compatibility while separating the Rust crate major version from the unchanged contextgraph/1.0 wire protocol.

New Features:

  • Add optional provenance attestation with hash-chain commitments, Ed25519 verification, Merkle roots, inclusion proofs, named verdicts, and cross-language reference vectors.
  • Open the FrameKind vocabulary across Rust, TypeScript, and Python while preserving unknown wire values for forward-compatible relaying.
  • Expose frame placement independently from ranking for hosts using custom ranking policies.

Bug Fixes:

  • Correct version-compatibility examples, stale freeze references, and obsolete governance links across the documentation.
  • Clarify schema and documentation that the FrameKind enum is an authoring lint rather than the wire interoperability contract.

Enhancements:

  • Define provider-local ordinal score semantics and document cross-provider ranking as host policy rather than a protocol guarantee.
  • Establish the host's consent boundary as local consent enforcement, excluding organizational fleet policy from the protocol implementation.

Build:

  • Bump the workspace and SDK packages to 2.0.0 while retaining the contextgraph/1.0 wire protocol.
  • Add optional cryptographic dependencies for attestation while preserving the default serde-only dependency set.

Documentation:

  • Add ADRs and migration guidance covering provenance attestation, open FrameKind handling, and independent crate/protocol versioning.
  • Update README positioning and protocol documentation to reflect the frozen contextgraph/1.0 release.

Tests:

  • Add provenance attestation unit tests and normative byte-vector fixtures covering chains, commitments, Merkle proofs, signatures, and verification verdicts.
  • Expand FrameKind tests to cover unknown-kind deserialization and byte-preserving round trips.

Three changes the protocol's own guarantees already implied but nothing
enforced, plus the doc corrections found alongside them.

**Provenance attestation (SPEC.md §6.5, F6-F9; ADR 0010).** A digest is
tamper-evident only to someone who already trusts whoever recorded it: the
digest and the frame it describes come from the same unauthenticated party,
so a provider willing to fabricate a frame fabricates its digest too and
every §6.2 check passes. `contextgraph_types::attest` adds a source-first
hash chain over provenance links, a frame commitment binding that head to
the full (provider_id, frame_id, content_digest) identity - without which a
signature can be lifted off one frame and stapled onto a fabricated one -
and an RFC 6962 Merkle root with inclusion proofs for selective disclosure.
Verification is offline and returns a named verdict, never a boolean.

Cryptography sits behind an off-by-default `attestation` feature so the
crate keeps its zero-deps-beyond-serde promise; ProvenanceAttestation itself
always compiles, because a host must be able to relay what it cannot check.
attestation_vectors.rs publishes the byte vectors other languages reconcile
against - a normative encoding without published vectors is a rule two
implementations can both believe they follow.

**FrameKind is now an open vocabulary (ADR 0011).** Rust-semver breaking,
wire-compatible. The closed enum contradicted §3.1's no-flag-day promise and
§13 U2: a kind added in a later 1.x did not degrade on a 1.0 host, it failed
to deserialize. Unknown(String) preserves the original string so a relaying
host re-emits it byte-identically - which #[serde(other)] cannot do - and
#[non_exhaustive] makes every future addition non-breaking.

**score semantics (SPEC.md §6.6, F10).** F1 gave score a range, not a scale,
and the host ranked across providers as if it were commensurable. Now stated
as provider-local and ordinal. Calibration was rejected as unenforceable:
budget honesty is checkable because cost is a function of bytes both sides
observe, and relevance has no such anchor.

**GOVERNANCE.md** states the consent boundary - the host enforces local
consent, not organizational policy - before it is contested.

Fixed: the version-compat example gave `contextgraph/1.0` and
`contextgraph/1.0` as two interoperating versions, in seven files; stale
pre-freeze language and a dead GOVERNANCE anchor; README's "canonical
architecture" and "draft v0.1.0" title.
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

SCR-003 DoD check passed — every linked issue's definition of done is fully checked.

@sourcery-ai

sourcery-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR adds an optional, wire-specified provenance evidence system with offline Ed25519 verification and Merkle inclusion proofs; makes FrameKind forward-compatible across minor protocol versions; clarifies provider-local score handling and the local-consent boundary; and updates ADRs, SDKs, schema, changelog, and compatibility documentation. Review the Rust-semver major-version decision, cross-language attestation vectors and cryptographic verification behavior, and note that TypeScript typechecking and host/conformance attestation integration remain follow-up or CI responsibilities.

Sequence diagram for offline provenance attestation verification

sequenceDiagram
    participant Provider
    participant Host
    participant Auditor

    Provider->>Provider: provenance_chain_head(links)
    Provider->>Provider: frame_commitment(provider_id, frame)
    Provider->>Provider: sign_commitment(commitment, signing_key_seed)
    Provider-->>Host: ContextFrame + ProvenanceAttestation
    Host->>Host: verify_frame_attestation(provider_id, frame, attestation, public_key)
    Host-->>Auditor: AttestationVerdict
    Auditor->>Host: root_from_proof(commitment, proof)
    Host-->>Auditor: Merkle root match or mismatch
Loading

State diagram for forward-compatible FrameKind handling

stateDiagram-v2
    [*] --> WireKind
    WireKind --> KnownKind: known base string
    WireKind --> UnknownKind: unrecognized string
    KnownKind --> Reemit: serialize
    UnknownKind --> Reemit: serialize original string
    Reemit --> [*]
Loading

Flow diagram for provenance evidence construction

flowchart LR
    Links["Ordered provenance links"] --> Chain[provenance_chain_head]
    Chain --> Commitment["frame_commitment(provider_id, frame)"]
    Commitment --> Merkle["merkle_root(commitments)"]
    Commitment --> Sign[sign_commitment]
    Merkle --> Sign
    Sign --> Attestation["Detached ProvenanceAttestation"]
Loading

File-Level Changes

Change Details Files
Added optional offline provenance attestation primitives and normative wire algorithms.
  • Implemented length-prefixed provenance encoding, source-first hash chains, frame commitments, and RFC 6962 Merkle roots with inclusion proofs.
  • Added detached Ed25519 signing and verification with named verdicts for mismatches, malformed inputs, unknown algorithms, and bad signatures.
  • Kept wire attestation types available without cryptographic dependencies; gated hashing/signature operations behind the attestation feature.
  • Published cross-language reference vectors and extensive tamper, identity-binding, signature, and proof tests.
contextgraph-types/Cargo.toml
contextgraph-types/src/attest.rs
contextgraph-types/src/lib.rs
contextgraph-types/tests/attestation_vectors.rs
Cargo.toml
Cargo.lock
Opened the FrameKind vocabulary while preserving unknown wire values across relays.
  • Replaced the closed Rust enum with known variants plus Unknown(String), custom serialization, parsing helpers, and #[non_exhaustive].
  • Updated host call sites and exported fold_to_edges so ranking and placement can be separated.
  • Updated TypeScript and Python SDK types with known-kind registries; documented the JSON Schema enum as authoring-only.
contextgraph-types/src/frame.rs
contextgraph-host/src/provider.rs
contextgraph-host/src/compose.rs
contextgraph-host/src/ingest.rs
contextgraph-host/src/lib.rs
contextgraph-conformance/src/lib.rs
sdk/typescript/src/types.ts
sdk/python/contextgraph_sdk/types.py
schema/contextgraph-envelope.schema.json
Defined provider-local score semantics and documented host-owned cross-provider ranking policy.
  • Specified scores as ordinal and provider-local, prohibiting cross-provider thresholds and raw-score relevance claims.
  • Documented order_by_value as an explicit reference-host policy and exposed fold_to_edges for independently ranked frames.
SPEC.md
contextgraph-host/src/compose.rs
Recorded the protocol consent boundary and clarified the corresponding architectural decisions.
  • Established that hosts enforce local consent while fleet RBAC, central policy distribution, and aggregated audit remain outside the protocol.
  • Added ADRs for provenance attestation and open FrameKind vocabulary, including protocol-versus-crate versioning consequences and deferred follow-up work.
GOVERNANCE.md
docs/adr/0010-provenance-attestation.md
docs/adr/0011-open-frame-kind-vocabulary.md
Corrected release positioning, compatibility documentation, and stale governance references.
  • Updated the changelog, README, protocol documentation, and compatibility examples for the frozen contextgraph/1.0 family.
  • Removed stale pre-freeze wording and repaired governance anchors and schema guidance.
CHANGELOG.md
README.md
SPEC.md
docs/index.md
docs/overview.md
docs/protocol-advantages.md
docs/protocol-surface.md
docs/registry.md

Possibly linked issues

  • #unknown: The PR directly implements key issue requirements: normative unknown-kind handling, preservation, reserved namespaces, and deprecation rules.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

The FrameKind vocabulary opens in this PR (ADR 0011): a new Unknown(String)
variant, #[non_exhaustive], no longer Copy, and frame_kind_name becomes
fn(&FrameKind) -> &str. That is breaking in Rust and invisible on the wire,
so the crates take a major and the protocol version stays contextgraph/1.0 —
the first case where the two axes in docs/stability.md actually disagree, and
the reason they are documented as independent.

All nine workspace crates inherit the bump from workspace.package. The
internal contextgraph-* floors move from ">=1.0.0" to ">=2.0.0": host 2.0.0
does not compile against types 1.x, so the old floor would have let cargo
resolve a pair that cannot build. The Python and TypeScript SDK manifests
move in lockstep — FrameKind widens in both type systems, so an exhaustive
switch relying on never-narrowing stops type-checking — as does
create-contextgraph-provider, whose TypeScript default pin had additionally
fallen a major behind its own manifest at ^0.1.0.

docs/stability.md said a breaking redesign required "both contextgraph/2.0
and crate version 2.0.0", which forbade this bump two lines after declaring
the axes independent. It now states the implication in the one direction that
holds: a wire break needs a crate major, but a crate major does not need a
wire break. MIGRATION.md §5 gives the three call-site shapes to fix, each a
compile error rather than a silent runtime change.

Verified: cargo check/test/clippy -D warnings clean on types + host (136 host,
132 types under --features attestation, 5 reference vectors); conformance,
trace, mcp-bridge and mcp-server all resolve and check at the new floors;
cargo fmt --check clean; Cargo.lock in sync.

Closes #92
@macanderson
macanderson marked this pull request as ready for review August 28, 2026 04:22

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @macanderson, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 16 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@macanderson
macanderson merged commit c8bed9b into main Aug 28, 2026
24 of 26 checks passed
@macanderson
macanderson deleted the worktree-provenance-attestation branch August 28, 2026 04:26
macanderson added a commit that referenced this pull request Aug 30, 2026
… its manifest (#106)

* ci(sdk): typecheck both typed SDKs, and catch a pin that drifted from its manifest

Two things the SDK directory shipped on a claim rather than a run.

The TypeScript type changes in PR #87 were never typechecked — no local
toolchain, and `npx tsc` failed in that environment. `sdk (typescript) is a
conformant implementation` does run `tsc` as a side effect of `npm run build`,
so the coverage was not zero, but it arrives only after a full
`cargo build --workspace --bins` and lasts only as long as the build script
stays `tsc`. `sdk (typescript) typechecks` asks the question directly. The
Python SDK ships `py.typed`, which promises downstream typecheckers that its
annotations are meant to be believed, and nothing checked them; `sdk (python)
typechecks` runs `mypy --strict`. Both are clean on this tree.

The scaffolder's `DEFAULT_SDK` table names versions of two packages it does not
own, and it sat at `^0.1.0` against a shipped `1.0.0` for an unknown length of
time. The scaffold job could not have caught it: it overrides both published
pins with local paths so CI never depends on a publish, which is the right call
for that job. `check-sdk-version-pins.py` is the guard that belongs elsewhere —
offline, stdlib only, no registry call.

The rule is same major, not equality, because the pins are deliberately ranges:
a patch release should not need an edit here, and a major is what a scaffold
cannot survive (ADR 0011 widened `FrameKind` in 2.0.0, so a `1.x` pin generates
code against a retired vocabulary). ADR 0012 carries the reasoning, including
why `sdk/go` and `schema/reference-vectors.ndjson` are out of scope.

Closes #94, Closes #98

Signed-off-by: macanderson <mac@oxagen.sh>

* ci(sdk): resolve the typecheck job's compiler from the lockfile

`npm ci` rather than `npm install`, so the job runs typescript 5.9.3 as
`package-lock.json` pins it instead of whatever `^5.9.0` resolves to today.
The mypy job beside it is pinned for the same reason and this one was not,
which Sourcery caught against #94's definition of done. The conformance jobs
keep `npm install`: they ask whether the SDK still behaves, not whether one
compiler release still accepts it.

Refs #94

Signed-off-by: macanderson <mac@oxagen.sh>

---------

Signed-off-by: macanderson <mac@oxagen.sh>
macanderson added a commit that referenced this pull request Aug 30, 2026
…#114)

* feat(contextgraph-types): implement record_hash and RecordAttestation

The lifecycle profile has always defined `record_hash` as the sha256 over
the RFC 8785 (JCS) canonicalization of a record with its own `record_hash`
removed (LH1), and `RecordAttestation` as a detached Ed25519 signature over
it (LC3). Both were prose and a struct. The only hashing code in the
workspace was a private helper inside the conformance suite, so the suite
proved the fixtures agreed with the suite; and the attestation fixture
carried 49 bytes of DER-shaped filler where a signature belongs, with no
key published, so no implementation could reproduce or refute it.

`contextgraph_types::record_attest` makes the rule callable, behind two new
off-by-default features. `record-hash` adds RFC 8785 canonicalization
(delegated to serde_json_canonicalizer, whose numbers route through ryu-js
— JCS number serialization is ECMAScript Number::toString, and its exponent
thresholds are where reimplementations diverge in silence).
`record-attestation` adds Ed25519 on top. A frame-only consumer pays for
neither, and the crate's zero-dependency default is unchanged.

The signed message is domain-separated: "contextgraph/attest/1/record"
followed by the digest's 32 raw bytes. A frame commitment is domain-bound
by construction; a record_hash is a plain SHA-256 over a JSON document that
any number of unrelated systems also compute, so signing it raw would let
one signature mean whatever the presenter says it means. Verification
recomputes the record's hash rather than reading the stored member, so
editing a record and rewriting its hash to match a stolen signature is
caught as a mismatch instead of passing.

Evidence: the canonicalizer is checked against RFC 8785's own vectors —
§3.2.4's byte listing, §3.2.3's sorting data, and Appendix B's IEEE 754
number table. The twelve fixtures' hashes are unchanged, which is what
shows this reproduces the existing rule rather than redefining it.
tests/fixtures/ now publishes the canonical preimage text of every fixture,
a real signature, and the test key that produced it; the conformance suite
recomputes all of it through the library. schema/validate-examples.py
checks the vectors from Python, without a JCS library, so an implementer
who has neither Rust nor a canonicalizer can still rely on them.

A CI job builds and runs each feature combination — until now every feature
this crate has was off by default and no job turned any of them on, so the
attestation code added in #87 compiled nowhere in CI.

Also corrects LH2, which said JCS sorts members by code point. RFC 8785
§3.2.3 sorts by UTF-16 code unit, and the two orders differ for a
supplementary character.

Closes #96

Signed-off-by: macanderson <mac@oxagen.sh>

* fix(contextgraph-types): gate the RecordAttestation import, renumber the ADR to 0017

The import is used only by the signing and verifying code, so a default
build — every CI job except the new feature matrix — failed
`-D warnings` on `unused_imports`. Gated to `record-attestation`, with
the one ungated doc link that named the type rewritten as an explicit
path so it still resolves with the import absent.

The local check that reported this clean was `rg -c '^(error|warning)'`
over cargo's output. Cargo colourises when it thinks it is talking to a
terminal, so the escape sits before the word and `^error` matches
nothing — a filter that cannot see the errors, reported as silence. The
loop also mis-quoted `--features X` as one argument, so three of the
five combinations never ran at all. Re-verified by exit code.

ADR 0012 renumbered to 0017: PR #106 adds a differently-named
docs/adr/0012-*.md, and two files with different names merge cleanly
into a tree holding two ADR 0012s with nothing to catch it. Numbers are
now allocated centrally. docs/GUIDE.md's decision log gains the entry,
along with 0009, 0010 and 0011, which had been missing since they landed
— adding a row to an index while leaving it knowingly incomplete is not
a fix.

Refs #96

Signed-off-by: macanderson <mac@oxagen.sh>

* docs(guide): complete the ADR decision log

The index stopped at 0008; 0009, 0010, 0011 and 0012 had each landed
without a row. Adding only 0017 would have left a table that jumps from
0008 to 0017 and still misses four decisions, so all five are in.

Refs #96

Signed-off-by: macanderson <mac@oxagen.sh>

* fix(contextgraph-types): use as_chunks for the fixed-width hex pairs

Rust 1.98's clippy adds `chunks_exact_to_as_chunks`, warn-by-default, so
`-D warnings` turned red on every hex decoder here — including
`attest.rs`'s `from_hex`, which predates this branch. CI pins
`dtolnay/rust-toolchain@stable`, so the toolchain moved under a tree
nobody had changed; the pre-existing site is fixed here because the job
cannot go green while it stands.

`as_chunks::<2>()` is also the better shape: the length check above each
loop already rules out a remainder, and a fixed-size chunk lets the
compiler see both indexes are in bounds.

The two copies of the hex decode in the conformance suite collapse into
one `hex32` helper that also checks the length it assumes.

A note on how the earlier local run missed this: cargo replays a cached
clippy result for an unchanged crate, so a `clippy` that had passed
before the lint existed kept reporting success. /tmp/verify.sh now
touches every source first.

Refs #96

Signed-off-by: macanderson <mac@oxagen.sh>

---------

Signed-off-by: macanderson <mac@oxagen.sh>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crate major-version bump for the FrameKind break (types + host)

1 participant