Skip to content

spec: WebhookSchema marks secret and headers as credential fields, and the kernel registry derives the meta read-door redaction for any marked type — webhook first, with a ratchet for unmarked credential-named fields (ruling B+ on #24966) #24994

Closed

Description

@objectstack-fleet

Ruled: 6304303975 · letter B+ · 2026-10-20T23:43Z

Filing gate ③ — the maintainer's ruling B+ on #24966 (batch #379 item 2; first 「同意」 to B, then 「B+ 可换……是不是长期更合理的方案。类似的问题都可以这样处理。」, recorded as 6304303975), filed by the director seat (summon #45 continuation 2, session_01Lg6GQ4xjqmvEzYv78d6H7i) and re-scoped to B+ in the same act. ⛔ Not a claim. Disclosure: the maintainer's ruling on #9387 (5323310017) applies to this card, its PR and every comment: location, rule and abstract reach only; ⛔ no probe, reproduction or served value.

Reader who acts: the domain:spec seat. One PR (M), @objectstack/spec patch changeset, Clause-②: no (narrowing) (the /meta read doors return less for webhook), contract review at CONTRACT_REVIEW_TIER. #24966 is pm:blocked on this card and closes when it lands.

Dedupe: the 19 open domain:spec cards' titles read in this act: 0 name a webhook, a redactor or a credential marker; #24966's own dedupe (webhook redactor, metadata-type redaction, secret-bearing headers, meta read door) found only itself.

What the ruling binds (origin/main e4d604efd39f)

The shape: declare the fact on the schema, derive the behaviour once in the kernel (the maintainer's standing word on 6304303975: 「类似的问题都可以这样处理」).

  1. The marker, on the declaration. WebhookSchema (packages/spec/src/automation/webhook.zod.ts:136) marks secret and headers as credential fields with a schema-level marker in the repo's existing zod .meta({ … }) convention (the marker's key and vocabulary are the spec seat's to name; the shape distinguishes a credential VALUE from a credential MAP, so headers is marked as a whole map). Nothing else about the schema changes.
  2. The derivation, once, in the registry. getMetadataTypeRedactor(type) in packages/spec/src/kernel/metadata-type-redaction.ts (:126 today: extra registration, then built-in) gains a third resolution: a redactor DERIVED from the type's registered schema (getMetadataTypeSchema(type), metadata-type-schemas.ts:299). It walks the declared shape, drops every marked field (not masked) and serves its cleared form (an empty string for a value, an empty map for a map), and names them in redactedKeys. Pure, no I/O, as the MetadataTypeRedactor contract requires. Any metadata type that marks a field is redacted on every meta read door with nothing else to write; webhook is the first type served this way.
  3. The guard. A spec ratchet test reads every registered metadata-type schema (listMetadataTypeSchemaTypes()) and fails when a field whose name or description reads as a credential (secret, password, token, signing, api key, credential) carries no marker and sits on no explicit allow-list — the fail-closed half the datasource module asks for (datasource-credential-redaction.ts:40–49: a positive marker under-redacts when forgotten).
  4. Precedence and the two legacy types. An explicit registration or built-in still wins over the derivation; datasource (schema-reading plus URL scrubbing) and flow (nested regions, write-path strip) keep their explicit redactors and are NOT migrated.
  5. Unchanged from B: the write-path carry-forward (a save that omits the two fields keeps the stored values) is accepted and keyed, as today, on "a redactor resolves for the type"; the dispatcher's declared-webhook source keeps carrying both credentials (delivery signing unaffected); who may reach a meta read door does not change; plugin-webhooks registers nothing.

What done looks like

  • WebhookSchema.secret and .headers carry the marker; a spec unit test shows the derived webhook redactor drops both, serves the cleared forms, leaves every other field intact, and that an unmarked type derives no redactor (undefined, "serve as is").
  • The ratchet: a marked field passes; a credential-named field without a marker reds the suite; the allow-list is explicit and empty or near-empty.
  • Door-level pins in the dogfood suite, in BOTH compositions (with and without plugin-webhooks): the meta list (both spellings), item, layered-view and published reads, and the stored-row data door with its history twin, withhold both fields; ablation-verified (remove the marker, the pin reads red).
  • A save round trip of the served form leaves the stored values intact (the carry-forward pin).
  • @objectstack/spec patch changeset, Clause-②: no (narrowing); api-surface regenerated for any exported helper.
  • ⛔ No probe output, request shape or served value in the PR, its tests or their names; the pins assert absence by rule name.

Related

#24966 (the ruled card: dev report 6301197652, decision 6301250022, ruling 6303716122 and its amendment 6304303975) · #8300 (datasource built-in precedent) · #9387 (disclosure) · #24900 (source report).

Activity

  1. changed the title [-]spec: a built-in `webhook` metadata-type redactor — `secret` and the whole `headers` map are withheld on every meta read door, with or without plugin-webhooks mounted (ruling B on #24966)[/-] [+]spec: `WebhookSchema` marks `secret` and `headers` as credential fields, and the kernel registry derives the meta read-door redaction for any marked type — `webhook` first, with a ratchet for unmarked credential-named fields (ruling B+ on #24966)[/+] on Oct 20, 2026
  2. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 24
    Session: session_01D6F9axfoPzR7t8G1J7Ytzz
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-24994-schema-derived-credential-redactor
    Worktree: objectstack-issue-24994
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main fbb42d900f or later; stop on a breach and explain it in the report):

    • packages/spec/src/automation/webhook.zod.ts (the credential marker on secret and on the headers map);
    • packages/spec/src/kernel/metadata-type-redaction.ts (the schema-derived third resolution in getMetadataTypeRedactor);
    • the spec ratchet test over listMetadataTypeSchemaTypes(), with its allow-list;
    • the meta read-door pins that ruling B+ (6304303975) names;
    • generated artifacts and .changeset/24994-*.md.
      Container & model: M, mode:subagent, model: default (no path-derived mandate)
      Clause-②: no (narrowing: the /meta read doors return less for webhook)
      Responsibility: ruled B+ on #24966 (6304303975)
      Thread-read: none
      Serial constraints cleared: read at this act; the dev re-checks open PRs touching metadata-type-redaction.ts / webhook.zod.ts before opening its own.

    Disclosure: the maintainer's ruling on #9387 (5323310017) applies. The PR and every comment carry location, rule and abstract reach only, ⛔ no probe, reproduction or served value. The PR owes a CONTRACT_REVIEW_TIER contract review.

    Read on origin/main fbb42d900f at 2026-10-20T23:50Z: BUILTIN_METADATA_TYPE_REDACTORS has no webhook entry.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24994,
    "status": "done",
    "branch": "claude/issue-24994-schema-derived-credential-redactor",
    "pr": "#25036",
    "session": "session_01D6F9axfoPzR7t8G1J7Ytzz",
    "premise_still_valid": true,
    "summary": "Ruling B+ implemented. WebhookSchema now marks secret with .meta({ credential: 'value' }) and the headers map as a whole with .meta({ credential: 'map' }). A new internal module, packages/spec/src/kernel/metadata-credential-fields.ts, holds the marker vocabulary, a walk over the declared object shapes (nested ones included), the cleared forms, and a memoised pure redactor built from the markers. getMetadataTypeRedactor now resolves an explicit registration first, then a built-in, then this schema-derived redactor; listMetadataTypeRedactorTypes includes the derived types, and datasource and flow are untouched. A spec ratchet (metadata-credential-fields.test.ts) covers every registered metadata-type schema plus the stack-collection kinds; this was needed because webhook lives in UNREGISTERED_KIND_SCHEMAS, so listMetadataTypeSchemaTypes alone never reads it. Dogfood door pins cover both compositions. Reading choice, stated in the PR: a withheld field is REMOVED (the flow precedent, and dev report option A), and a stored cleared form is served as written. This is what makes ruling item 5's 'a save that omits the two fields keeps the stored values' hold, because the carry-forward compares the incoming value with the served one. Replacing the withheld value with '' would have made a served-body save look like a deliberate clear.",
    "tests": "Head 0cf6eed. Spec: the two touched files give 2 files and 39 tests passed. spec typecheck: exit 0. spec build, then check:generated: all 14 artifacts up to date, nothing regenerated. Full spec test exit 0 at 441ce01; the later commit only changes zodDefOf to read the public .def and adds one test. rest: test exit 0 (307 files; 6157 passed, 326 skipped) and typecheck exit 0, at 0cf6eed. An earlier head turned rest red in rest-exposure-gate-execution-read.test.ts, and the cause was this change: a _zod read through a second Proxy around a lazySchema Proxy throws a Proxy invariant error. It was fixed by reading .def, and the fix is pinned by a doubly-proxied unit test. metadata-protocol and plugin-webhooks: test and typecheck exit 0 at 441ce01. dogfood: typecheck exit 0; the new pin file plus webhook-materialization and wall-declared-webhook-deployment-wide give 3 files and 24 tests passed at 0cf6eed. Ablation, with the fix committed first: both credential markers were rewritten to a non-marker key through scripts/ablation-replace.mjs (anchor 1 to 0 each; on-disk counts 0 and 0). spec was rebuilt, and ablation-dist-preflight --absent found both markers absent from all 232 built files. The pin file then read 12 failed and 3 passed; the 3 passes are the composition guards and the dispatcher-source pin. Restore: blob equal to HEAD, git diff HEAD empty, spec rebuilt, the preflight found both markers present in 18 built files, and the pin file read 15 of 15 passed. A first ablation attempt was refused by the tool's replacement-count check before any mutation ran (a no-op, counted as nothing). Every locked run returned VERDICT command-exit 0, apart from queue-timeout 99s that were retried under the same slot.",
    "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack derived 87 families (stderr names objectstack-ai/objectstack at 0cf6eed), and --ran reconciled 87 derived, 87 run, 0 unrun. All exit 0 except one. The spec, changeset and citation subset (33 commands, check:generated among them) was run again at 0cf6eed, all exit 0. check:type-check-debt ran under the lock at 0cf6eed: re-measure OK, no entry above its recorded number. NOT MEASURED: check:dual-build-cjs-loads, because it needs every workspace package built and only the dogfood closure was built; declared to CI. No ① closure beyond the dogfood closure was built (turbo build --filter='@objectstack/dogfood^...': 66 tasks, 66 successful).",
    "deviations": [
    "The card names listMetadataTypeSchemaTypes() for the ratchet. The ratchet reads that list PLUS listUnregisteredKindSchemaTypes(), because webhook is not a registered kind; without the second list the guard would never read the one type the card is about.",
    "Card item 2's wording 'serves its cleared form' was read as: remove the withheld key, and serve a stored cleared form as written. The PR body explains why. If the PM reads the ruling as 'substitute the cleared form', that is a change to two lines in deriveCredentialRedactor, and the carry-forward on omission would then stop holding.",
    "Door set pinned: the card's list (meta list both spellings, item, layered view, published, stored-row data door and history twin), plus the metadata protocol's getMetaItems and getMetaItem. No MCP pin: the card's list does not name one, and #24966's dev report measured that the MCP HTTP tools refuse the stored metadata objects.",
    "The four new spec exports (walker helpers) live in a module that is not re-exported from the kernel barrel, so api-surface is unchanged. The marker key also appears in the generated JSON Schema of webhook (zod copies .meta into the JSON Schema node); no committed artifact changed.",
    "No label written: the dispatch named none, and skip-changeset does not apply (a spec patch changeset ships).",
    "Worktree /home/user/objectstack-issue-24994 removed after the PR opened (node_modules deleted, worktree remove without force succeeded)."
    ],
    "files_changed": [
    "packages/spec/src/automation/webhook.zod.ts",
    "packages/spec/src/kernel/metadata-credential-fields.ts",
    "packages/spec/src/kernel/metadata-credential-fields.test.ts",
    "packages/spec/src/kernel/metadata-type-redaction.ts",
    "packages/spec/src/kernel/metadata-type-redaction.test.ts",
    "packages/qa/dogfood/test/webhook-meta-door-credential-redaction.dogfood.test.ts",
    ".changeset/24994-webhook-credential-marker.md"
    ],
    "mcp_calls": "0",
    "api_writes": "2 relay dispatches through scripts/pm/with-fleet.sh --via dispatch. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls (draft), then POST /repos//issues/25036/assignees with baozhoutao. (2) This os-dev-report comment: POST /repos//issues/24994/comments. Outside REST: git push of the branch. Reads were gh api REST only.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed — a schema wrapped in a second Proxy around a lazySchema Proxy throws a Proxy invariant TypeError on any _zod read, because lazySchema's getOwnPropertyDescriptor reports a non-configurable _zod its function target lacks. It is reachable only by a caller that proxies a registered schema (today, one rest test); this PR's walker avoids it by reading .def. Dedupe words: lazySchema proxy invariant, _zod getOwnPropertyDescriptor, double proxy schema."
    ]
    }


    Generated by Claude Code

  4. added a commit that references this issue on Oct 21, 2026
  5. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 24994,
    "status": "done",
    "branch": "claude/issue-24994-schema-derived-credential-redactor",
    "pr": "#25036",
    "session": "session_01D6F9axfoPzR7t8G1J7Ytzz",
    "premise_still_valid": true,
    "summary": "Fix round for the contract-review FAIL, one point. The changeset is re-graded from patch to minor, as the launch-window narrowing rule in scripts/check-changeset-no-major.mjs requires (maintainer ruling 2026-09-04, batch #35, on #15294). It now also carries a BREAKING banner and a spec!: headline, the precedent shape of the no (narrowing) changesets on main. origin/main was merged in, with no rebase and no force-push. New head: e86f322. The PR is still a draft, and its body's changeset line plus a fix-round section were updated through the relay.",
    "tests": "At e86f322: pnpm --filter @objectstack/spec test passed 703 files and 21388 tests (1 skipped, 1 todo), and typecheck passed, under the lock with VERDICT command-exit 0. These changeset gates all exit 0: check-changeset-no-major (--base origin/main and --self-test), check-adr-0087-registration (both), check-empty-changeset (both), and check:changeset-gate-self-tests.",
    "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at e86f322 gives the same 87 families as the previous head. The delta-touched gates all exit 0: check:nul-bytes, check:issue-citations, check:doc-authoring, check:pm-changeset-deadline-census, check-closing-keyword-parity, check:objectui-changeset, check:closed-ceremony-text. The rest are declared to CI.",
    "mcp_calls": "0",
    "api_writes": "2 relay dispatches. (1) issue_patch: PATCH /repos//issues/25036, the body, 12727 bytes stored identically. (2) This comment: POST /repos//issues/24994/comments. Also one git push.",
    "open_questions": [],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Close-out — landed

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

Metadata

Metadata

Assignees

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:specpriority:p1High: required for production / M2securitytarget:18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions