Skip to content

Design: a signed artifact is validated as strictly as it is built — one receipt contract for pkgs/id artifacts #3231

Closed
5 of 5 issues completed

Description

@dezren39

Design-architect pass 28 (2026-10-31), audit at origin/main 357ec7fb. Full design doc: files/designs/code-id-artifact-receipt-validation.md in the keep-the-four-repos-healthy goal workspace. NO user/review — every origin carries explicit acceptance with verified probe evidence, and the judgment calls below are the architect's recorded decisions.

Problem

pkgs/id has three signed-statement types, and two state the same contract in their docs — documents.rs: "The builder and the decoder both apply them, so a signed document always passes them on receipt"; chain.rs (ChainExt::validate): the same sentence for signed events. The third type, artifacts (pkgs/id/src/artifact.rs), never applied it to itself: its receipt gates check() (envelope) and parsed() (the four known kinds) each enforce some of the rules the crate's own producers and sibling validators obey and silently omit others — one omission per field, filed one at a time. Four filings in two days (three in the hours after pass 27 assessed #3146 as a single): #3146 (friend_remove.requests never inspected), #3187 (home_declared.world_id only is_empty-checked; duplicate endpoints pass), #3192 (document/tag text accepts non-NFC every sibling path refuses), #3196 (check() accepts a duplicated after frontier chain.rs refuses as DuplicateAfter).

The rules (recorded decisions)

  1. Receipt is construction. parsed() refuses a known-kind body no in-tree producer could build; check() refuses an envelope no legitimate signer could build — Refusal::Forbidden naming the rule, existing per-arm style. Unknown kinds untouched (stored, never applied, envelope-only — the module's deliberate forward-compatibility rule).
  2. A signed list names each thing once. Duplicates refused, never silently deduped on receipt (the signed bytes are evidence; rewriting falsifies them and breaks the hash). Covers after, friend_remove.requests, and home_declared servers by endpoint. sign_after refuses a duplicated after at build time — the builder/decoder symmetry the sibling types document.
  3. Text in a signed body follows the crate's text rules. document.media_type, tag.name, tag.value must be NFC via the same unicode_normalization::is_nfc the three sibling validators import. world_id gets MAX_WORLD_ID_BYTES = 64 (aligned with MAX_WORLD_NAME_BYTES): 1–64 bytes, not whitespace-only, no control (Cc) or format (Cf) characters.

Field pins: requests ≤ MAX_FRIEND_REQUESTS = 3 (the sole producer Directory::friendship_requests, directory.rs:1710–1721, structurally builds at most 3, sorted+deduped), each entry exactly 32 lowercase hex characters (the envelope-ID shape), no duplicates. Junk requests entries are not inert — Directory::apply extends the permanent removed_requests void guard with every string (directory.rs:1698–1705).

Phases (adopt the origins — no parallels)

Alternatives rejected (in the doc)

Fixing the four independently (rules diverge — the drift is how the family arose: the artifact arms already mirror the sibling caps while missing the NFC half); validating at application time instead of parsed() (the log would still store and serve junk; parsed() is the single gate insert/open/pull all pass); normalizing on receipt (breaks hash/signature — refusal is the only honest outcome); folding into #3068 (JS/TS shape contract — different gate, different failure mode; cross-referenced, neither gates the other — this answers #3187's triage note); pinning the full a-z0-9-_ world-name charset for world_id now (WorldCore::new accepts any non-blank ID — charset stays an open question, below).

Risks (in the doc)

ArtifactLog::open() replays through the same gates, so tightened rules apply to stored chains — legitimate chains unaffected (no producer emits these shapes), refusal-on-open with the line named is existing behaviour; MAX_FRIEND_REQUESTS = 3 is a producer invariant (constant and producer change together); Cf refusal in world_id is safe for machine-chosen IDs ("home"/"lobby" today).

Open questions (in the doc, never as issues)

world_id charset (validate_world_name's a-z0-9-_?) — needs evidence of how declared() consumers resolve it; document.content internal text NFC (chain's all-text rule vs the documents.rs media-type-only precedent); unknown-kind kind-string bounds vs forward compatibility; full builder symmetry for sign. The neighbouring plain-output rendering family (#2839, #3111, #3089, world/util.rs sanitizers) governs printing untrusted text, not storing it — not adopted; #3187's Cf pin is deliberately aligned with it.

Acceptance criteria

Activity

  1. added
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/normalOrdinary priority (the default)
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    epicMeta-issue grouping multiple items (org Project Type=Epic)
    kind/designA design proposal (design-architect): multi-step, phased, decision-bearing
    on Oct 31, 2026
  2. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Design-architect pass 28 (2026-10-31) — agent note: this design was originated this pass from an audit of pkgs/id/src/artifact.rs against origin/main 357ec7f. Its phases ADOPT the four origins as sub-issues — no parallels filed. Phase 1 = #3196 + #3146 (list integrity: duplicate after refused in check() and in sign_after; friend_remove requests capped at MAX_FRIEND_REQUESTS=3, 32-lowercase-hex envelope IDs, no duplicates), Phase 2 = #3192 + #3187 (text fields: NFC via unicode_normalization::is_nfc for document media_type and tag name/value; world_id 1–64 bytes under MAX_WORLD_ID_BYTES, not whitespace-only, no Cc/Cf; duplicate endpoints refused), Phase 3 = #3232 (closing field-by-field audit + contract statement in the artifact.rs module doc, gated on Phases 1–2, status/blocked, triage packet enqueued to repo-health-code-pilot this pass). Design doc: files/designs/code-id-artifact-receipt-validation.md in the keep-the-four-repos-healthy goal workspace; register entry added (status: proposed). NO user/review — every origin carries explicit acceptance with verified probes and the judgment calls (uniqueness-refused-not-deduped, world_id bound/class pins with charset left open, #3068 boundary) are the architect's recorded decisions in the doc. Pass 27 assessed #3146 as a single #3068-family instance; #3187/#3192/#3196 filed since are the growth that originates this design. This answers #3187's triage note: the fix does NOT belong to #3068's contract — different gate (Rust signed statements vs JS/TS parsed JSON), cross-referenced in the doc.

  3. added
    effort/lLarge: multi-day or open-ended; expect staged check-ins
    and removed
    task/triageQueue packet kind: verify, reproduce, and route this item
    on Oct 31, 2026
  4. 65 remaining items

  5. added
    queue/claimedQueue worker lifecycle state
    and removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    on Nov 4, 2026
  6. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  7. added
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    status/parkedDeliberately paused with a recorded resume condition
    and removed
    queue/claimedQueue worker lifecycle state
    on Nov 5, 2026
  8. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Triage verdict — task 9325 (2026-11-05)

    Parent scope is implemented, but closure is pending the active audit PR; no duplicate packet or PR created.

    🤖 Agent note · parked

  9. removed
    status/parkedDeliberately paused with a recorded resume condition
    queue/parkedQueue task is parked (waiting on CI, time, or an event)
    work/assignedQueue worker lifecycle state
    on Nov 5, 2026
  10. dezren39 commented on Nov 5, 2026

    @dezren39
    MemberAuthor

    Task 9325 completed as already_done by lane code-c (repo-queue worker): issue #3231 is CLOSED (stateReason COMPLETED, closed 2026-11-05T01:49:55Z) following merged PR #5626 (merged 2026-11-05T01:49:54Z), verified with fresh gh reads at pickup. No further triage work remains for task 9325; stale parked/work labels were removed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

app/idPackage/app: id (pkgs/ or apps/ in this repo)effort/lLarge: multi-day or open-ended; expect staged check-insepicMeta-issue grouping multiple items (org Project Type=Epic)expediteImportant - do this very soon, before anything not expeditedkind/designA design proposal (design-architect): multi-step, phased, decision-bearingmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)source/design-architectFiled by the repo design architect jobtask/triageQueue packet kind: verify, reproduce, and route this item

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions