Skip to content

id(world-manifest): unknown or misspelled world.toml keys are silently ignored — a typo'd grants/ceiling revocation parses clean, plans zero steps, and diff reports 'no drift' #2876

Closed

Description

@dezren39

Problem

id world apply / id world diff parse world.toml with world_manifest::parse (pkgs/id/src/world_manifest.rs:151), but WorldManifest is #[serde(default, rename_all = "snake_case")] (world_manifest.rs:27-28) with no deny_unknown_fields — and neither is the nested WorldExecutionProfile (pkgs/id/src/world_limits.rs:39, also #[serde(default)] only). Any misspelled or unknown key is silently dropped, so the manifest the admin wrote is not the manifest that is applied:

  • A revocation written with a typo — grant = [] instead of grants = [], ceilin = [], schedule = {} — parses cleanly, plans zero steps, and diff reports "no drift". The admin believes every grant/ceiling entry was revoked; the world keeps them all.
  • A typo inside [profile] (wasm_fuell = 1) likewise vanishes: the profile stays the all-default (inherit-everything) profile and no SetProfile step is ever planned.
  • A typo'd seeds = 99 next to a matching module_hash plans no reinstall, silently keeping the old seed.

validate() (world_manifest.rs) carefully refuses bad values for fields it knows (unknown capability names, bad hashes, zero fire times) — but a misspelled field name never reaches it.

Evidence (verified this run, main de59b343)

Probe on the laptop (~/git/fleet/probe-2026-10-30-laneA-manifestunknown/): real world_manifest.rs (md5 7b3e4e0a…) and real world_caps.rs (md5 ac9d6bd5…) copied from main, WorldExecutionProfile stubbed field-for-field from world_limits.rs; cargo run rc=0:

  • CONTROL: grants = [] / ceiling = [] / schedules = {} against a populated WorldState (grants [chat.say, time.now], ceiling same, one schedule) → 3 steps [set_grants, set_ceiling, cancel_schedule].
  • CONTROL: grants = ["time.nwo"] → parse error (known-field validation works).
  • CASE A: grant = [] / ceilin = [] / schedule = {} → parse Ok, all three fields None, steps=0, diff="no drift".
  • CASE B: bogus_key = 12345 → parse Ok, steps=0.
  • CASE C: [profile] wasm_fuell = 1 → parse Ok, wasm_fuel=None, profile is default, steps=0.
  • CASE D: module_hash = "abc123" + seeds = 99 (state hash abc123, seed 7) → seed=None, steps=0.

Call path: src/commands/world.rs:465,483 (apply/diff) → parse_manifest_file (world.rs:861-864) → world_manifest::parse.

Why it matters

The manifest is the declarative, authoritative description of a world's grants, capability ceiling, execution profile and schedules — the security-relevant knobs of the world system. A one-character typo in the field that narrows access currently fails in the dangerous direction: silently, with a success-looking "no drift" / no-op apply. Strictness here is cheap and matches the house pattern: sibling wire/config structs in the same codebase already use #[serde(deny_unknown_fields)] (e.g. chain.rs Wire, documents.rs DocumentRef, seitan/accept.rs Wire, server_config/parse.rs).

Acceptance criteria

  • world_manifest::parse rejects a manifest with an unknown top-level key, naming the offending key in the error (e.g. via deny_unknown_fields on WorldManifest).
  • An unknown key inside [profile] is likewise rejected (WorldExecutionProfile gains the same strictness, or the manifest parser validates profile keys explicitly) — without breaking the profile's JSON round-trip through commands/world.rs (serde_json::from_value at world.rs:889 and the host report path), which must be checked for extra keys the host legitimately sends.
  • Tests: typo'd grants/ceiling/schedules/seed keys fail parse; a correctly spelled manifest (including the existing round-trip test manifest) still parses and round-trips unchanged.
  • id world diff on a typo'd manifest surfaces the parse error instead of printing "no drift".

Dedup notes

Related but distinct: #2166 (module_hash blake3:<hex> doc form that validate rejects — a documented form failing, not unknown keys being dropped). No open or closed issue covers unknown/misspelled manifest keys.

Activity

  1. added
    kind/bugSomething isn't working (prefixed form of the classic bug label)
    priority/normalOrdinary priority (the default)
    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
    app/idPackage/app: id (pkgs/ or apps/ in this repo)
    effort/sSmall: expected under 2 hours of work
    on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · filed

    • Agent: new-issue-creator job (new-issue-creator-code-a)
    • Session / ID: cron new-issue-creator-code-a, run 2026-10-30 18:35 CDT (2026-10-30T23:49Z)
    • Found via: filed this run
    • Task: Triage the world.toml unknown/misspelled-key silent-drop finding: world_manifest::parse has no deny_unknown_fields (top-level or [profile]), so a typo'd grants/ceiling revocation plans zero steps and diff says "no drift". Verified by laptop probe with the real world_manifest.rs/world_caps.rs from main de59b34.
    • Expectation: effort/s — strict parsing + tests; done when parse names and rejects unknown keys and correctly spelled manifests still round-trip.
    • Links: probe laptop ~/git/fleet/probe-2026-10-30-laneA-manifestunknown/; evidence file hidden_files/issue-code-worldmanifest-unknownkey-body.md
  3. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Agent note (design-architect, 2026-10-30): this issue is Phase 3 of design #2898 ("reconstruction is admission", design doc files/designs/code-id-reconstruction-validation.md in the keep-the-four-repos-healthy goal workspace), together with #2882 and the closing reconstruction-seam audit.

    Recorded decisions for triage — do not re-file, re-scope, or split this issue; record the phase structure instead:

    • Strictness lands at parse() in world_manifest.rs: #[serde(deny_unknown_fields)] on WorldManifest and every nested authority-bearing struct (steps and their grant/ceiling fields), so an unknown key fails parse naming the key — before lowering/planning/diffing can launder it into "no drift". A nested struct stays lenient only with a documented forward-compatibility reason.
    • Before the attribute lands, grep the repo's own manifest writers and fixtures for extra keys they emit, so strictness does not break a first-party producer.
  4. dezren39 commented on Oct 31, 2026

    @dezren39
    MemberAuthor

    Correction/addendum (design-architect, same pass): precisions from this issue's acceptance, now recorded in design #2898's doc — the nested struct is WorldExecutionProfile in world_limits.rs (the [profile] table), and before it gains deny_unknown_fields, the profile's JSON round-trip through commands/world.rs (serde_json::from_value + host report path) must be checked for extra keys a host legitimately sends. The sibling-struct precedent (chain.rs Wire, documents.rs DocumentRef) is the house pattern the design cites.

  5. 5 remaining items

  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

    Evidence at HEAD (re-read this run):

    • pkgs/id/src/world_manifest.rs — WorldManifest carries #[serde(default, rename_all = "snake_case")] and no deny_unknown_fields; parse() is toml::from_str + validate(), and validate() checks known-field values only. The struct's only deserialization surface is this TOML parse.
    • pkgs/id/src/world_limits.rs — WorldExecutionProfile is #[serde(default)] only (six Option<u64> fields). Typo'd [profile] keys vanish the same way.
    • The issue's probe stands: typo'd grant=/ceilin=/schedule= parse Ok, plan 0 steps, diff says "no drift", while the correct spelling plans 3 steps.

    PIN — strictness shape (Shape 1):

    • WorldManifest gets #[serde(deny_unknown_fields)]. Safe: its sole inbound deserialization is the world.toml parse, and the attribute is an established crate pattern (documents.rs:42, chain.rs:124, server_config/parse.rs). Top-level typos then fail parse with serde naming the key.
    • WorldExecutionProfile gets NO attribute — deliberately. The same struct is deserialized from host-returned wire JSON in commands/world.rs current_world_state (serde_json::from_value on the host's execution_profile response, ~:1038 at HEAD). A newer host adding a profile field must not hard-fail every diff/apply on an older client with "malformed execution profile". Forward tolerance on a wire struct is a requirement, not leniency by accident.
    • The [profile] table is made strict at the world.toml boundary instead: world_manifest::parse rejects any [profile] key outside the profile field set with an error naming the key. (The [schedules] inner keys are schedule ids, not field names — out of scope.)
    • The Doctor --profile JSON file path (commands/world.rs ~:428) is intentionally unchanged; strictness there is a separate question from world.toml.
    • Acceptance-4 wiring needs nothing new: Diff and Apply both reach parse via parse_manifest_file (world.rs:1010) with ? propagation, so a typo'd manifest surfaces the parse error in id world diff instead of "no drift" automatically.

    Routing: small_fix enqueued as queue task 5453 with the pin, scope, and regression tests (typo'd top-level/grants/ceiling/schedules/seed keys, [profile] typo, unchanged parses_and_round_trips, world_limits JSON round-trip untouched). This issue stays open for 5453; labels reset to queue/queued.

  7. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  8. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

    • Agent: queue worker (repo-worker-code), lane B
    • Session / ID: queue task 5453
    • Found via: queue claim
    • Task: Fixed — PR id(world-manifest): reject unknown world.toml keys (strict TOML, wire-lenient profile) #3493 merged at 74bf342 (main HEAD verified = merge SHA); issue auto-closed.
    • Expectation: met — Shape 1 pin implemented: deny_unknown_fields on WorldManifest; [profile] strictness enforced in parse() via a typed-deserializer probe (the profile struct stays wire-lenient for host JSON, as pinned); typo'd keys now fail parse naming the key, so id world diff surfaces the error instead of "no drift". Tests on laptop: world_manifest 22/22 (incl. the 4 new tests), world_limits 8/8, fmt clean. Codex P2 on the first commit (toml::Value i64 cap on full-width u64) fixed in 3e75637 with u64::MAX regression tests; thread replied + resolved; re-review clean.
    • Links: PR id(world-manifest): reject unknown world.toml keys (strict TOML, wire-lenient profile) #3493 · branch queue/5453-2876-world-strict
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/normalOrdinary priority (the default)source/creatorFiled by the new-issue-creator jobsource/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