Skip to content

id(artifact): parsed() accepts non-NFC text in document and tag — media_type, tag name and value bypass the NFC rule documents.rs and tag_claims.rs both enforce #3192

Closed

Description

@dezren39

Problem

SignedArtifact::parsed() (pkgs/id/src/artifact.rs) accepts non-NFC text in the two known kinds that carry free text, while every sibling text path in the same crate refuses non-NFC:

  • DOCUMENT arm (artifact.rs:356-371): checks only media_type empty / >128 bytes and content size. A decomposed media type such as text/cafe\u{301} (e + combining acute U+0301) is accepted and stored.
  • TAG arm (artifact.rs:372-385): checks only subject hash, name 1–64 bytes, value ≤256 bytes. A decomposed tag name (aute\u{301}ur) or value (cafe\u{301}) is accepted and stored.

The sibling rules that these kinds bypass:

  • documents.rs DocumentRef::check() (documents.rs:105-121) refuses a media type that is not NFC — its doc comment states the rule and it is pinned by test_the_media_type_is_one_to_128_bytes_in_nfc. Same 128-byte cap as the artifact arm, minus the NFC half.
  • tag_claims.rs check_text() (tag_claims.rs:146-160), used by check_op for tag.set, requires NFC for name and value.
  • chain.rs (chain.rs:198, chain.rs:367) requires NFC for event kinds and text strings.

Why it matters

An artifact is a signed, stored, propagated statement: parsed() is the trust-boundary check before a known kind is applied (directory.rs publishes/applies Kind::Document and Kind::Tag). Two spellings of the same media type / tag name — NFC and decomposed — are different byte strings, so lookups, equality, and tag-name matching split on a difference no user can see: a document published with a decomposed media type does not match the NFC spelling a reader asks for, and a decomposed tag name forms a separate tag from the composed one. The event/chain side of the same system canonicalizes on NFC; the artifact side silently does not, so the same logical statement is valid in one store and refused in the other.

Evidence (verified this run)

Acceptance criteria

  • parsed() refuses a document whose media_type is not NFC, with a Refusal::Forbidden naming the rule, pinned by a parsed()-level test using a decomposed spelling.
  • parsed() refuses a tag whose name or value is not NFC, likewise pinned.
  • Ordinary NFC documents and tags (existing artifact tests) parse exactly as today.
  • The check is shared with, or explicitly mirrors, the existing NFC rules in documents.rs / tag_claims.rs so the three validators cannot drift again (e.g. one helper, or a test that walks all three).

Activity

  1. added
    queue/queuedHas a queued task in the Supabase agent queue
    source/creatorFiled by the new-issue-creator job
    task/triageQueue packet kind: verify, reproduce, and route this item
    priority/lowNice to have; do when convenient
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    effort/sSmall: expected under 2 hours of work
    on Oct 31, 2026
  2. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

  3. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    🤖 Design-architect pass 28 (2026-10-31) — agent note: this issue is ADOPTED as a phase of design #3231 ("a signed artifact is validated as strictly as it is built", design doc: files/designs/code-id-artifact-receipt-validation.md in the keep-the-four-repos-healthy goal workspace), linked as a sub-issue of #3231 and labelled source/design-architect. Triage: do NOT re-file, re-scope or split this issue — record the phase structure instead. Phase mapping: Phase 1 (list integrity) = #3196 + #3146; Phase 2 (text fields) = #3192 + #3187; Phase 3 (closing audit, gated on Phases 1–2) = #3232. The design records the pins that span the phases: duplicates in a signed list are REFUSED, never silently deduped on receipt; text fields use the crate's existing rules (unicode_normalization::is_nfc; world_id under MAX_WORLD_ID_BYTES=64, not whitespace-only, no Cc/Cf); unknown artifact kinds stay stored-but-never-applied and envelope-only. Each origin keeps its own acceptance criteria and its already-queued triage packet — no new packet was created for it this pass.

  4. added
    work/assignedQueue worker lifecycle state
    and removed on Nov 1, 2026
  5. 37 remaining items

  6. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    event: picked_up
    task: 5660 (repo-health-code-pilot, repo-worker-code — OpenChamber scheduled lane)
    state: Claimed via atomic queue claim; resuming at awaiting_ci for PR #4331 opened by this queue task.
    revalidation: Fresh metadata: #3192 is OPEN; PR #4331 is OPEN at prior pushed head 6f3e272. Required checks “basic: guard PR check list” and “native arm64: world snapshot determinism” are still QUEUED; local artifact tests passed 30/30 per checkpoint.
    next: Watch checks for at most five minutes; if still pending, park and resume on fresh CI state.

  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 2, 2026
  8. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    agent-note:v1
    event: parked
    task: 5660 (repo-health-code-pilot, repo-worker-code — OpenChamber scheduled lane)
    state: Parked for 20 minutes after the five-minute CI watch.
    artifact: PR #4331, current head 6f3e272.
    ci: Required checks “basic: guard PR check list” and “native arm64: world snapshot determinism” remained pending; CodeRabbit and full nix check were skipped. Targeted local artifact tests passed 30/30.
    resume: Revalidate PR/check state; merge only when required checks are green.
    bookkeeping: Sandbox backlog and sweep log deferred to fleet reconciliation.

  9. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  10. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  11. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · parked

  12. dezren39 commented on Nov 2, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done — PR #4331 merged

  13. 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 2, 2026
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/sSmall: expected under 2 hours of workkind/bugSomething isn't working (prefixed form of the classic bug label)priority/lowNice to have; do when convenientsource/creatorFiled by the new-issue-creator jobsource/design-architectFiled by the repo design architect jobtask/small-fixQueue packet kind: bounded fix with reproduction and targeted tests

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions