Skip to content

finding(spec): the protocol declares no masked field-type set — objectql's collectMaskedReadFields and objectui's MASKED_FIELD_TYPES each own a copy of one fact #20141

Description

@objectstack-fleet

Filing-gate category: ② a seam defect (class b): one protocol fact ("which field types are masked on read") has no declaration in packages/spec, and two consumers each own a hand-written copy. Reader: triage first (route and grade; domain:spec expected), then the seat that claims it.

Filed by the objectui domain:ui execution seat (seat 1, session session_01BA3nKVUwKQJf8DBxrSVtNC), as the maintainer-approved ruling on objectstack-ai/objectui#8686 requires: 「Protocol-first check … If the spec declares it, the authority DERIVES from the spec; if not, fields owns the set for now and a domain:spec card is filed to lift the fact into the protocol」 (ruling 5644350822, decision batch #122 item 4). ⛔ Not graded and not routed.

The fact, read on objectstack origin/main a08e059c6

  • The spec states it only in prose. packages/spec/src/data/field.zod.ts: the FieldType comments say a generic password is "masked to SECRET_MASK on read" and secret "masks it on read". A git grep for an exported masked-type set or flag (MASKED_FIELD_TYPES, maskedFieldTypes, isMaskedField) under packages/spec/src returns 0 hits. The same grep form finds BOUNDED_STRING_FIELD_TYPES (the positive control), so the zero is a reading.
  • The runtime owns copy 1. packages/objectql/src/secret-fields.ts collectMaskedReadFields hard-codes def.type === 'secret', plus 'password' unless the object is managedBy: 'better-auth'.
  • The renderer owns copy 2. objectui PR objectui#10568 (objectui#8686, in flight) exports MASKED_FIELD_TYPES (password, secret) and isMaskedFieldType() from @object-ui/fields, because the protocol gives it nothing to derive from.

So a third masked type added to the runtime would be masked on read but drawn in clear, and copyable, by the renderer until someone edits objectui's set, and the reverse. The drift runs toward disclosure on the render side.

Grading notes (for triage, not a grade)

  • The shape of the fix: packages/spec declares the masked-on-read field types, as a set or a per-type flag in the field-type metadata. That includes the managedBy: 'better-auth' exception for password, which is part of the fact, so the declaration has to be able to carry it. Then objectql's collectMaskedReadFields and objectui's MASKED_FIELD_TYPES derive from it.
  • objectui's side re-binds once a spec release carrying it is installable; objectui#8686's fields set is documented as the interim owner.

Dedupe

REST page walk over the 1000 most recently updated objectstack items (oldest updated_at 2026-09-21). collectMaskedReadFields, "masked … field type", MASKED_FIELD_TYPES, SECRET_MASK, "sensitive field" and isMaskedFieldType ⇒ 0 each. Must-hit control field\.zod ⇒ 30 hits.

Dedupe words: masked field types spec · credential field type set · collectMaskedReadFields · SECRET_MASK field type


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: permissions that actually hold | access-security.fls-mask-and-strip | P2

    Triage: first grade — enhancement · security · priority:p3 · domain:spec · area:access · pm:queue (finding removed — graded)

    Triage: lands in packages/spec (the field-type metadata in packages/spec/src/data/field.zod.ts, which states masking only in prose) ⇒ domain:spec; rationale: a ruled task — the maintainer-approved ruling on objectstack-ai/objectui#8686 (5644350822) orders exactly this card: lift "which field types are masked on read" into the protocol so objectql's collectMaskedReadFields and objectui's MASKED_FIELD_TYPES derive from one declaration. Today the two copies agree, so nothing leaks yet; the drift would run toward disclosure on the render side ⇒ security, p3.

    Triage seat #6015 · session_01CRZSc7dU8oDStbTbSwhuZe · 2026-09-25T11:13Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments yet) and origin/main.

    Execution note: the declaration must carry the managedBy: 'better-auth' exception for password, which is part of the fact; then collectMaskedReadFields derives from it here, and objectui re-binds once a spec release carrying it is installable (Clause-②: yes, a new declared surface).

  2. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01CiCTczDo7tGhafXjf61dUJ
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-20141-masked-field-types-declared
    Worktree: objectstack-issue-20141
    Domain: domain:spec
    Seat: domain:spec#4 (seat post #18917)
    File surface: the declaration in packages/spec/src/data/: one exported declaration of the field types masked on read, including the managedBy: 'better-auth' exemption for password. It lives either in a new module beside field.zod.ts or at the FieldType prose for password / secret (field.zod.ts :40–55 on origin/main 3875ae677), whose prose then cites the declaration. Plus its re-export and tests. The consumer: packages/objectql/src/secret-fields.ts (collectMaskedReadFields derives from the declaration), with tests. Generated artefacts regenerated, never hand-edited; .changeset/. Cross-lane (packages/objectql is domain:engine): declared here because triage routed this seam card vertically to the spec seat. ⛔ Not field.zod.ts's CurrencyConfigSchema (#19992), its defaultCurrency describe (#20126, open PR #20217), its InlineGridColumnSchema (#20045) or the FieldSchema .superRefine precision anchor. ⛔ Not objectui: it re-binds on its own card once a spec release carrying this is installable. (stop on breach; explain in the report)
    Container & model: S/M, mode:subagent, model: default judgment tier (dispatch-gates.mjs --tier on field.zod.ts + secret-fields.ts: 「no path-derived mandate」, the default slot taken). The diff hits the enqueue gate's path limb (packages/spec/src/**) and adds a declared public surface, so it is reviewed at CONTRACT_REVIEW_TIER before enqueue.
    Clause-②: yes
    Thread-read: 5831443600
    Serial constraints cleared: read at 2026-09-27T11:04Z. Open-PR census: 10 open PRs besides the release PR, 299 file rows read this act (lit control: 85 packages/ rows). Of these, only PR #20217 touches field.zod.ts, at the defaultCurrency describe (about :447), a different region; none touches objectql/src/secret-fields.ts. In-flight claims, 28 pm:dispatched cards read: three hold field.zod.ts regions, each disjoint from :40–55: #20126 (seat 1, :447), #19992 (seat 1, CurrencyConfigSchema :428–443) and #20045 (seat 2, InlineGridColumnSchema :895–950). No claim names secret-fields.ts or collectMaskedReadFields.

    Ruling-ref: objectstack-ai/objectui#8686, ruling 5644350822 (decision batch #122 item 4), as this card's body quotes it: 「Protocol-first check … If the spec declares it, the authority DERIVES from the spec; if not, fields owns the set for now and a domain:spec card is filed to lift the fact into the protocol」.

  3. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20141,
      "status": "done",
      "branch": "claude/issue-20141-masked-field-types-declared",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20235",
      "session": "session_01CiCTczDo7tGhafXjf61dUJ",
      "premise_still_valid": true,
      "summary": "packages/spec/src/data/masked-field-types.ts (re-exported from @objectstack/spec/data) now declares the masked-on-read field types once. MASKED_ON_READ_FIELD_TYPES is a deep-frozen per-type rule table: secret has exemptManagedBy [], password has ['better-auth']. isMaskedOnReadFieldType(fieldType, managedBy) is the one reading of it; managedBy is a required argument and exemptions fail closed. FieldTypeReadMask is the rule type. The FieldType prose (enum comments only) cites the declaration. objectql collectMaskedReadFields and its sibling collectMaskedPasswordFields ask the predicate instead of hard-coding the secret/password arms and the better-auth test. Behaviour is identical: a 665-row FieldType x managedBy table run through base and head gives the same sha256. Why this shape: an exemption cannot be separated from its type, an unlisted bucket never unmasks, and keys and values are compile-checked against FieldType and ObjectSchema.managedBy. Mechanism assumptions 1-3 held: prose only at field.zod.ts:43-55; 0 exported declarations under packages/spec/src at 4df101c38 (control BOUNDED_STRING_FIELD_TYPES has 28 hits); collectMaskedReadFields at secret-fields.ts:172. Assumption 4 was incomplete: plugin-audit audit-writers.ts also imports collectMaskedReadFields (from @objectstack/objectql/core). Its tests pass. The symbol objectui should import is isMaskedOnReadFieldType from @objectstack/spec/data (named in the PR body). The PR body line 2 is Clause-②: yes, and the changeset states it: spec minor, objectql patch (no new objectql export, no behaviour change).",
      "tests": "All runs are at HEAD 3d14f9e75 (branch merged with main 805af4f29), each through os-verify-lock with VERDICT command-exit 0. spec full suite: 545 files, 16041 tests passed (+2 todo). objectql full suite (--project local): 318 files, 5766 passed. platform-objects: 55 files, 911 passed. plugin-audit: 25 files, 363 passed. spec typecheck exit 0; objectql typecheck exit 0; tsc --listFilesOnly shows masked-field-types(.test).ts and secret-fields.test.ts inside the test programs. SECURITY FLOOR: one table (49 FieldTypes + 10 off-enum inputs, x 11 managedBy inputs, + 11 mixed-object rows + 5 degenerate schemas = 665 rows, both collectors per row). Base 4df101c38 secret-fields.ts (blob 708318ed6) sha256 8a418a2e1b6ed2a6159f5b1e6289d25703df5c0d8eacd3e151084a96bcdc5fd7, 21 masked cells. Head (blob 86b21b30f) sha256 8a418a2e1b6ed2a6159f5b1e6289d25703df5c0d8eacd3e151084a96bcdc5fd7, 21 masked cells. Control, base with the secret arm deleted: sha256 0cdcbcad7818e65526687c0bd8e6dd09a60bb54a6b3dfbe040ce8652358cc216, 10 masked. ABLATION, committed first, via scripts/ablation-replace.mjs: collectMaskedReadFields pointed at a hard-coded copy that drops secret. The mutation landed: anchor 1 to 0, blob 86b21b30f to 0533a577a, ABLATION-20141 marker count 1, original line count 0. vitest src/secret-fields.test.ts gave 6 failed / 34 passed, including the agreement pin ('\"secret\" x (absent): expected false to be true'), the floor and the mixed-object pin. Restore: blob equals HEAD 86b21b30f, git diff HEAD empty, status clean. The subject is imported relatively from src, so there is no dist leg. REVERSE VERIFICATION on the spec types, tsc --noEmit -p packages/spec/tsconfig.json: an invented managedBy 'identity' gave exit 2 (TS2322 at masked-field-types.ts 90,14 and 98,5); an invented key pin_code gave exit 2 (TS2353 at 94,5). Both restored (blob 38b1cadb6, git diff HEAD empty). The first key-leg attempt was a no-op: the replacement contained its anchor, and ablation-replace refused and restored. It was rerun with a disjoint anchor. LINT, narrowed: eslint --no-inline-config --format json over the 6 changed TS files gave files=6, errors=0, warnings=0. Population: eslint's own isPathIgnored reports all 6 as linted (4-6 rules each via calculateConfigForFile). Invariance: eslint.config.mjs sets no parserOptions.project (no type-aware linting), so this diff cannot move a verdict in an untouched file. Repo-wide pnpm lint is left to CI.",
      "mcp_calls": "0",
      "api_writes": "3 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches executed by fleet-write.yml as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft), giving PR 20235 (run 36320790720); (2) label-write --assign os-sales, POST /repos/objectstack-ai/objectstack/issues/20235/assignees (run 36320830350; read back: assignee os-sales); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/20141/comments. Plus 5 git pushes (not REST): empty branch probe, cec3c4ce5, 71d91a589, e181a2b93, 3d14f9e75.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — packages/spec/src/data/object.zod.ts warnGenericPasswordFields restates the password half of the fact (managedBy === 'better-auth' early return + type === 'password'); agrees today; outside this claim's file surface; mechanical derivation from isMaskedOnReadFieldType for whoever next edits it (PR Acceptance notes)",
        "carrier: 承接者:无 · noted, not filed — ADR-0100 section C names collectMaskedReadFields as the one place the better-auth exemption lives; after this PR the spec declaration is that place and the collector derives (same single-ownership decision, relocated); ADR text is Tier H, a pointer amendment is the maintainer's call (PR Acceptance notes)",
        "carrier: 承接者:无 · noted, not filed — objectql collectCredentialFields (aggregate refusal: secret|password unconditional) is a separate hand-written type list by design; if the masked set grows the aggregate refusal will not follow on its own; unexercised drift (PR Acceptance notes)"
      ],
      "gates": {
        "head": "3d14f9e75",
        "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack gave 87 commands (identical list at e181a2b93 and 3d14f9e75)",
        "reconciliation": "node scripts/pm/dispatch-gates.mjs --ran ran.list exit 0: 87 derived, 85 run, 2 NOT-MEASURED (recorded exit 3, PREREQUISITE NOT MET: whole-tree dist absent), 0 UNRUN",
        "NOT_MEASURED": [
          "pnpm check:dual-build-cjs-loads, reason: needs every workspace package's dist (53 missing); CI builds the tree",
          "pnpm check:type-check-debt, reason: --re-measure needs 16 unbuilt workspace deps' dist; CI builds the closure"
        ],
        "results": [
          "exit 0 · node scripts/check-adr-0087-registration.mjs --base origin/main",
          "exit 0 · node scripts/check-adr-0087-registration.mjs --self-test",
          "exit 0 · node scripts/check-changeset-no-major.mjs --base origin/main",
          "exit 0 · node scripts/check-changeset-no-major.mjs --self-test",
          "exit 0 · node scripts/check-ci-filter-parity.mjs",
          "exit 0 · node scripts/check-closing-keyword-parity.mjs",
          "exit 0 · node scripts/check-closing-keyword-parity.mjs --self-test",
          "exit 0 · node scripts/check-comment-mask-adoption.mjs",
          "exit 0 · node scripts/check-comment-mask-adoption.mjs --self-test",
          "exit 0 · node scripts/check-comment-mask-corpus.mjs",
          "exit 0 · node scripts/check-dev-prereqs.mjs --self-test",
          "exit 0 · node scripts/check-empty-changeset.mjs --base origin/main",
          "exit 0 · node scripts/check-empty-changeset.mjs --self-test",
          "exit 0 · node scripts/check-engine-split-ratio.mjs --days 90",
          "exit 0 · node scripts/check-engine-split-ratio.mjs --self-test",
          "exit 0 · node scripts/check-keyed-text-bounds.mjs",
          "exit 0 · node scripts/check-keyed-text-bounds.mjs --self-test",
          "exit 0 · node scripts/check-platform-object-tenancy-census.mjs",
          "exit 0 · node scripts/check-platform-object-tenancy-census.mjs --self-test",
          "exit 0 · node scripts/check-plugin-teardown-shape.mjs",
          "exit 0 · node scripts/check-plugin-teardown-shape.mjs --self-test",
          "exit 0 · node scripts/check-registry-log-declared.mjs",
          "exit 0 · node scripts/check-registry-log-declared.mjs --self-test",
          "exit 0 · node scripts/check-rest-log-spy-declared.mjs",
          "exit 0 · node scripts/check-rest-log-spy-declared.mjs --self-test",
          "exit 0 · node scripts/check-spec-docblock-symbol-anchors.mjs",
          "exit 0 · node scripts/check-spec-docblock-symbol-anchors.mjs --self-test",
          "exit 0 · node scripts/check-system-context-census.mjs",
          "exit 0 · node scripts/check-system-context-census.mjs --self-test",
          "exit 0 · node scripts/check-undeclared-dep-imports.mjs",
          "exit 0 · node scripts/check-undeclared-dep-imports.mjs --self-test",
          "exit 0 · node scripts/docs-audit/check-affected-docs.mjs",
          "exit 0 · node scripts/docs-audit/check-drift-comment.mjs",
          "exit 0 · node scripts/pm/release-rehearsal-clone.mjs --self-test",
          "exit 0 · pnpm check:changeset-gate-self-tests",
          "exit 0 · pnpm check:cross-package-test-inputs",
          "exit 0 · pnpm check:dispatcher-error-vocabulary",
          "exit 0 · pnpm check:doc-authoring",
          "exit 0 · pnpm check:driver-memory-census",
          "exit 0 · pnpm check:dts-closure",
          "exit 3 · pnpm check:dual-build-cjs-loads",
          "exit 0 · pnpm check:durability-log-level",
          "exit 0 · pnpm check:engine-double-contract",
          "exit 0 · pnpm check:gitlink-declared",
          "exit 0 · pnpm check:issue-citations",
          "exit 0 · pnpm check:lean-entry-closure",
          "exit 0 · pnpm check:logger-receiver-detach",
          "exit 0 · pnpm check:merge-driver",
          "exit 0 · pnpm check:nul-bytes",
          "exit 0 · pnpm check:objectql-double-limit",
          "exit 0 · pnpm check:objectui-changeset",
          "exit 0 · pnpm check:org-identifier",
          "exit 0 · pnpm check:page-declaration-shape",
          "exit 0 · pnpm check:pm-changeset-deadline-census",
          "exit 0 · pnpm check:pm-prior-rulings",
          "exit 0 · pnpm check:pm-widening-tells",
          "exit 0 · pnpm check:published-files",
          "exit 0 · pnpm check:query-options-erasure",
          "exit 0 · pnpm check:refd-timer-probe",
          "exit 0 · pnpm check:slot-lookup",
          "exit 0 · pnpm check:sourcemap-no-sources-content",
          "exit 0 · pnpm check:spec-parsed-alias",
          "exit 0 · pnpm check:test-source-alias",
          "exit 0 · pnpm check:tier-file-adoption",
          "exit 0 · pnpm check:type-check-coverage",
          "exit 3 · pnpm check:type-check-debt",
          "exit 0 · pnpm check:watch-hint-literal",
          "exit 0 · pnpm check:where-matcher",
          "exit 0 · pnpm --filter @objectstack/lint run check:doc-formula-expressions",
          "exit 0 · pnpm --filter @objectstack/spec run check:api-surface",
          "exit 0 · pnpm --filter @objectstack/spec run check:authorable-surface",
          "exit 0 · pnpm --filter @objectstack/spec run check:browser-reachable-entries",
          "exit 0 · pnpm --filter @objectstack/spec run check:docs",
          "exit 0 · pnpm --filter @objectstack/spec run check:dual-source-exports",
          "exit 0 · pnpm --filter @objectstack/spec run check:duration-unit-keys",
          "exit 0 · pnpm --filter @objectstack/spec run check:empty-state",
          "exit 0 · pnpm --filter @objectstack/spec run check:entry-nameability",
          "exit 0 · pnpm --filter @objectstack/spec run check:export-origins",
          "exit 0 · pnpm --filter @objectstack/spec run check:exported-any",
          "exit 0 · pnpm --filter @objectstack/spec run check:generated",
          "exit 0 · pnpm --filter @objectstack/spec run check:liveness",
          "exit 0 · pnpm --filter @objectstack/spec run check:llms-txt",
          "exit 0 · pnpm --filter @objectstack/spec run check:objectui-pin-citations",
          "exit 0 · pnpm --filter @objectstack/spec run check:skill-refs",
          "exit 0 · pnpm --filter @objectstack/spec run check:strictness-ledger",
          "exit 0 · pnpm --filter @objectstack/spec run check:variant-docs",
          "exit 0 · pnpm --filter @objectstack/spec run check:yaml-examples"
        ],
        "ci": "PR head 3d14f9e75: 32 check runs, 10 success, 3 skipped, 19 in_progress, 0 failed at report time (not awaited)"
      },
      "line_budget": "9 files, +379/-11 = 390 changed lines vs 805af4f29 (human-merge threshold 5000: under); no skills/** or governed surface touched",
      "files_changed": [
        ".changeset/20141-masked-on-read-field-types-declared.md",
        "packages/objectql/src/secret-fields.test.ts",
        "packages/objectql/src/secret-fields.ts",
        "packages/spec/api-surface/data.json (generated)",
        "packages/spec/export-origins/data.json (generated)",
        "packages/spec/src/data/field.zod.ts (FieldType prose :40-55 only)",
        "packages/spec/src/data/index.ts",
        "packages/spec/src/data/masked-field-types.test.ts",
        "packages/spec/src/data/masked-field-types.ts"
      ],
      "deviations": [
        "PM mechanism assumption 4 was incomplete: plugin-audit (packages/plugins/plugin-audit/src/audit-writers.ts) also imports collectMaskedReadFields; its suite was added to the consumer runs (25 files / 363 passed)",
        "merged main 805af4f29 into the branch before opening the PR (AGENTS.md multi-agent section 10); no conflicts, no regen deferral; closure rebuilt after the merge",
        "harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer; commits carry the model-free pair and the PR body the session-URL footer per AGENTS.md / the agent definition",
        "os-verify-lock VERDICT for the ablation and reverse-verification wrapper scripts reads command-exit 0 (the wrapper's last step is the restore); the red/green verdicts are read from ablation-replace's own lines and the vitest/tsc exit codes in the logs",
        "reverse-verification key leg: first attempt refused as a no-op by ablation-replace (the replacement contained the anchor), restored, rerun with a disjoint anchor"
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim 5855274180: file surface extended for a patch round on PR #20235 (2026-09-27T13:40Z)

    domain:spec seat 4 (session_01CiCTczDo7tGhafXjf61dUJ). ⛔ Not a new claim: branch, worktree, assignee and Clause-②: yes are unchanged.

    Why. The at-tier review 5856331212 passed every derived judgment at 3d14f9e75: behaviour identical over 2,314 inputs, fail-closed, three exports, a comment-only prose hunk. Its FAIL has one cause, the red Test Core (5/6). That red is a timing flake in packages/client, which the diff does not reach; it is filed as #20242. The review also found a third copy of this card's fact: packages/spec/src/data/object.zod.ts warnGenericPasswordFields hard-codes managedBy === 'better-auth' together with type === 'password'. A hand-written copy of the masked-on-read fact is exactly what this card removes, so it is fixed here rather than in a follow-up card.

    Added to the surface: packages/spec/src/data/object.zod.ts, the warnGenericPasswordFields docblock and body only (about :2560–:2605 on origin/main c02fa1276), with its tests. ⛔ No other region of that file.

    Also in the round: the three new comments that say objectui 「derives」 from the declaration in the present tense are reworded. objectui keeps its interim copy until its own re-bind card lands.

  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    Patch round 1 on PR #20235: new head 5cb765bc7.

    {
      "issue": 20141,
      "round": "patch round 1 (PM order after at-tier review 5856331212; surface extension 5856361779)",
      "status": "done",
      "branch": "claude/issue-20141-masked-field-types-declared",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/20235",
      "new_head": "5cb765bc7",
      "session": "session_01CiCTczDo7tGhafXjf61dUJ",
      "premise_still_valid": true,
      "summary": "(1) Merged origin/main c02fa1276 with scripts/pm/os-regen-merge.sh: merge commit f0a0378ff. Step 3's commit was refused by the os-regen pre-commit hook as the script documents (api-surface/export-origins stale). I rebuilt spec from the merged tree, ran gen:api-surface and gen:export-origins, and committed 087ce043b; marker cleared. The regenerated shards differ from main by exactly this PR's 3 entries each. No rebase, no force-push. (2) warnGenericPasswordFields now returns early on !isMaskedOnReadFieldType('password', managedBy) instead of managedBy === 'better-auth'. The ackPlaintextMasking opt-out, the per-object dedupe and the message are byte-for-byte unchanged, and it is not widened to secret. The docblock says the exemption is read from the declaration. (3) Reworded the three present-tense 'objectui derives' comments (data/index.ts, the masked-field-types.ts header, secret-fields.ts) and a fourth one in the secret-fields.test.ts docblock: objectui keeps its interim MASKED_FIELD_TYPES until its re-bind card lands. The import type { SECRET_MASK } JSDoc import is untouched. (4) The changeset gained one paragraph naming the warning's derivation; levels (spec minor, objectql patch) and Clause-②: yes unchanged. (5) Gates, the four suites and typecheck are green at 5cb765bc7, and CI converged all green, Test Core (5/6) included. (6) The PR body was NOT edited: see deviations; the replacement Acceptance notes text is in pr_body_acceptance_notes for the seat to write.",
      "behaviour_tables": {
        "password_warning": "60 rows = 12 managedBy inputs (absent, the 6 declared buckets, undefined, null, 'system', 'not-a-bucket', 'Better-Auth') x 5 field shapes (password, password+ackPlaintextMasking, secret, text, mixed ack/unacked/secret). Each row goes through ObjectSchema.create() and records: warned, the message text (object name normalised), repeat-create warn count (dedupe) and whether create threw. BASE (object.zod.ts blob 1696bdf48 == c02fa1276's) sha256 8f555c50a51253a21668f9c2f7a08b2941995bd01a9013ed12866d5a6d569b2b, 22 warned rows. HEAD (blob 76886c7af) sha256 8f555c50a51253a21668f9c2f7a08b2941995bd01a9013ed12866d5a6d569b2b, 22 warned rows. Every bucket except better-auth warns for password and mixed; no row re-warns on repeat. Control (the ablated file below): sha256 f106dc4f8dba0e814fcecc5706f97061a4df9334f436b2a723c3a684b104286c.",
        "read_mask_collectors": "Re-run at the new head (secret-fields.ts blob 4a92749a5; comment-only change this round): 665 rows, sha256 8a418a2e1b6ed2a6159f5b1e6289d25703df5c0d8eacd3e151084a96bcdc5fd7, 21 masked cells, identical to base 4df101c38 (same hash)."
      },
      "ablation": "Committed first (5cb765bc7), then scripts/ablation-replace.mjs. Anchor: the warning's first line, if (!isMaskedOnReadFieldType('password', managedBy)) return;. Replacement: if (managedBy === 'config') return;. Mutation landed: anchor 1 to 0, blob 76886c7af to 441c6f7cb, marker count 1, original line count 0. vitest src/data/object.test.ts -t 'password-field author warning' gave 2 failed / 6 passed (VITEST_EXIT=1). The new agreement pin failed with 'managedBy config: expected false to be true'; the existing better-auth no-warn test failed with 'expected warn to not be called'. Restore: blob equals HEAD 76886c7af, git diff HEAD empty, status clean. The subject is imported relatively (./object.zod), so there is no dist leg.",
      "tests": "All at 5cb765bc7, through os-verify-lock (VERDICT command-exit 0 each). spec full (--project local): 547 files, 16079 passed (+2 todo). objectql full (--project local): 318 files, 5766 passed. platform-objects: 55 files, 911 passed. plugin-audit: 25 files, 363 passed. spec typecheck and objectql typecheck: exit 0 (test-typecheck ledgers unchanged: spec 53 files / 255 errors / 142 signatures, objectql 40 / 234 / 65). Narrowed lint: eslint --no-inline-config --format json over the 8 changed TS files gave files=8, errors=0, warnings=0; all 8 reported linted (not ignored) by eslint's own isPathIgnored; eslint.config.mjs has no parserOptions.project, so no untouched file's verdict can move. Control-byte self-scan: no match. Repo-wide pnpm lint is left to CI (Lint & Repo Gates green).",
      "gates": {
        "head": "5cb765bc7",
        "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack gave 88 commands. New versus round 1: pnpm check:skill-identifier-liveness (exit 0).",
        "reconciliation": "--ran exit 0: 88 derived, 86 run (all exit 0), 2 NOT-MEASURED, 0 UNRUN",
        "NOT_MEASURED": [
          "pnpm check:dual-build-cjs-loads: exit 3, PREREQUISITE NOT MET (whole-tree dist absent); CI ran it green",
          "pnpm check:type-check-debt: exit 3, PREREQUISITE NOT MET (16 workspace deps unbuilt); CI 'Type Check · debt ledger' green"
        ],
        "results": [
          "exit 0 · node scripts/check-adr-0087-registration.mjs --base origin/main",
          "exit 0 · node scripts/check-adr-0087-registration.mjs --self-test",
          "exit 0 · node scripts/check-changeset-no-major.mjs --base origin/main",
          "exit 0 · node scripts/check-changeset-no-major.mjs --self-test",
          "exit 0 · node scripts/check-ci-filter-parity.mjs",
          "exit 0 · node scripts/check-closing-keyword-parity.mjs",
          "exit 0 · node scripts/check-closing-keyword-parity.mjs --self-test",
          "exit 0 · node scripts/check-comment-mask-adoption.mjs",
          "exit 0 · node scripts/check-comment-mask-adoption.mjs --self-test",
          "exit 0 · node scripts/check-comment-mask-corpus.mjs",
          "exit 0 · node scripts/check-dev-prereqs.mjs --self-test",
          "exit 0 · node scripts/check-empty-changeset.mjs --base origin/main",
          "exit 0 · node scripts/check-empty-changeset.mjs --self-test",
          "exit 0 · node scripts/check-engine-split-ratio.mjs --days 90",
          "exit 0 · node scripts/check-engine-split-ratio.mjs --self-test",
          "exit 0 · node scripts/check-keyed-text-bounds.mjs",
          "exit 0 · node scripts/check-keyed-text-bounds.mjs --self-test",
          "exit 0 · node scripts/check-platform-object-tenancy-census.mjs",
          "exit 0 · node scripts/check-platform-object-tenancy-census.mjs --self-test",
          "exit 0 · node scripts/check-plugin-teardown-shape.mjs",
          "exit 0 · node scripts/check-plugin-teardown-shape.mjs --self-test",
          "exit 0 · node scripts/check-registry-log-declared.mjs",
          "exit 0 · node scripts/check-registry-log-declared.mjs --self-test",
          "exit 0 · node scripts/check-rest-log-spy-declared.mjs",
          "exit 0 · node scripts/check-rest-log-spy-declared.mjs --self-test",
          "exit 0 · node scripts/check-spec-docblock-symbol-anchors.mjs",
          "exit 0 · node scripts/check-spec-docblock-symbol-anchors.mjs --self-test",
          "exit 0 · node scripts/check-system-context-census.mjs",
          "exit 0 · node scripts/check-system-context-census.mjs --self-test",
          "exit 0 · node scripts/check-undeclared-dep-imports.mjs",
          "exit 0 · node scripts/check-undeclared-dep-imports.mjs --self-test",
          "exit 0 · node scripts/docs-audit/check-affected-docs.mjs",
          "exit 0 · node scripts/docs-audit/check-drift-comment.mjs",
          "exit 0 · node scripts/pm/release-rehearsal-clone.mjs --self-test",
          "exit 0 · pnpm check:changeset-gate-self-tests",
          "exit 0 · pnpm check:cross-package-test-inputs",
          "exit 0 · pnpm check:dispatcher-error-vocabulary",
          "exit 0 · pnpm check:doc-authoring",
          "exit 0 · pnpm check:driver-memory-census",
          "exit 0 · pnpm check:dts-closure",
          "exit 3 · pnpm check:dual-build-cjs-loads",
          "exit 0 · pnpm check:durability-log-level",
          "exit 0 · pnpm check:engine-double-contract",
          "exit 0 · pnpm check:gitlink-declared",
          "exit 0 · pnpm check:issue-citations",
          "exit 0 · pnpm check:lean-entry-closure",
          "exit 0 · pnpm check:logger-receiver-detach",
          "exit 0 · pnpm check:merge-driver",
          "exit 0 · pnpm check:nul-bytes",
          "exit 0 · pnpm check:objectql-double-limit",
          "exit 0 · pnpm check:objectui-changeset",
          "exit 0 · pnpm check:org-identifier",
          "exit 0 · pnpm check:page-declaration-shape",
          "exit 0 · pnpm check:pm-changeset-deadline-census",
          "exit 0 · pnpm check:pm-prior-rulings",
          "exit 0 · pnpm check:pm-widening-tells",
          "exit 0 · pnpm check:published-files",
          "exit 0 · pnpm check:query-options-erasure",
          "exit 0 · pnpm check:refd-timer-probe",
          "exit 0 · pnpm check:skill-identifier-liveness",
          "exit 0 · pnpm check:slot-lookup",
          "exit 0 · pnpm check:sourcemap-no-sources-content",
          "exit 0 · pnpm check:spec-parsed-alias",
          "exit 0 · pnpm check:test-source-alias",
          "exit 0 · pnpm check:tier-file-adoption",
          "exit 0 · pnpm check:type-check-coverage",
          "exit 3 · pnpm check:type-check-debt",
          "exit 0 · pnpm check:watch-hint-literal",
          "exit 0 · pnpm check:where-matcher",
          "exit 0 · pnpm --filter @objectstack/lint run check:doc-formula-expressions",
          "exit 0 · pnpm --filter @objectstack/spec run check:api-surface",
          "exit 0 · pnpm --filter @objectstack/spec run check:authorable-surface",
          "exit 0 · pnpm --filter @objectstack/spec run check:browser-reachable-entries",
          "exit 0 · pnpm --filter @objectstack/spec run check:docs",
          "exit 0 · pnpm --filter @objectstack/spec run check:dual-source-exports",
          "exit 0 · pnpm --filter @objectstack/spec run check:duration-unit-keys",
          "exit 0 · pnpm --filter @objectstack/spec run check:empty-state",
          "exit 0 · pnpm --filter @objectstack/spec run check:entry-nameability",
          "exit 0 · pnpm --filter @objectstack/spec run check:export-origins",
          "exit 0 · pnpm --filter @objectstack/spec run check:exported-any",
          "exit 0 · pnpm --filter @objectstack/spec run check:generated",
          "exit 0 · pnpm --filter @objectstack/spec run check:liveness",
          "exit 0 · pnpm --filter @objectstack/spec run check:llms-txt",
          "exit 0 · pnpm --filter @objectstack/spec run check:objectui-pin-citations",
          "exit 0 · pnpm --filter @objectstack/spec run check:skill-refs",
          "exit 0 · pnpm --filter @objectstack/spec run check:strictness-ledger",
          "exit 0 · pnpm --filter @objectstack/spec run check:variant-docs",
          "exit 0 · pnpm --filter @objectstack/spec run check:yaml-examples"
        ]
      },
      "ci": "Head 5cb765bc7, read in the foreground until converged at 2026-09-27T14:37Z: 35 check runs, 32 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in)), 0 non-success. 12 workflow runs, all completed. The required contexts are all success: Lint & Repo Gates, TypeScript Type Check, Test Core (plus Test Core (1/6) through (6/6), (5/6) included), Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard.",
      "pr_body_acceptance_notes": "## Acceptance notes\n\nNot filed.\n\n- `packages/spec/src/data/object.zod.ts` `warnGenericPasswordFields` (the author-time password warning) now derives: its exemption is `isMaskedOnReadFieldType('password', managedBy)`, no longer a hard-coded `managedBy === 'better-auth'`. Which objects and fields warn is unchanged: a 60-row table (12 managedBy inputs x 5 field shapes, with a dedupe re-create per row) gives the same sha256 on base and head (`8f555c50a51253a21668f9c2f7a08b2941995bd01a9013ed12866d5a6d569b2b`). A pin in `object.test.ts` ties the warning to the declaration.\n- The red `Test Core (5/6)` at `3d14f9e75` was the `packages/client` cold-start timing flake filed as #20242; this PR does not touch it. At `5cb765bc7` every check is green.\n- ADR-0100 section C still names `collectMaskedReadFields` as the one place the better-auth exemption lives; the declaration is that place now. The ADR is Tier H, so the pointer amendment stays noted for the PM.\n- `collectCredentialFields` (the aggregate refusal set, `secret` or `password` unconditionally) is a separate hand-written type list by design. If the masked set ever grows, the aggregate refusal will not follow on its own. That drift has not been exercised; no card.",
      "mcp_calls": "0",
      "api_writes": "1 relay stroke: this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/20141/comments (via repository_dispatch to fleet-write.yml as objectstack-fleet[bot]). Plus 2 git pushes (not REST): 087ce043b (carrying merge f0a0378ff and the regen commit) and 5cb765bc7. No PR body edit, no label write, no new PR.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — ADR-0100 section C pointer (Tier H) now names the consumer, not the declaration; for the PM (Acceptance notes)",
        "carrier: 承接者:无 · noted, not filed — collectCredentialFields remains a separate hand-written credential-type list by design; unexercised drift if the masked set grows (Acceptance notes)"
      ],
      "line_budget": "11 files, +439/-16 = 455 changed lines vs merge base c02fa1276 (threshold 5000: under); no governed surface",
      "files_changed_this_round": [
        "packages/spec/src/data/object.zod.ts (warnGenericPasswordFields docblock + body, AND one import line in the top import block; see deviations)",
        "packages/spec/src/data/object.test.ts (one describe added in the password-warning block + one import)",
        "packages/spec/src/data/index.ts (comment)",
        "packages/spec/src/data/masked-field-types.ts (header comment)",
        "packages/objectql/src/secret-fields.ts (comment)",
        "packages/objectql/src/secret-fields.test.ts (docblock comment)",
        ".changeset/20141-masked-on-read-field-types-declared.md (one paragraph)",
        "packages/spec/api-surface/data.json + packages/spec/export-origins/data.json (regenerated after merge)"
      ],
      "deviations": [
        "SURFACE: the derivation needs isMaskedOnReadFieldType in scope, so object.zod.ts gained one import line (plus a 3-line comment) in its TOP import block. That is outside the literal 'warnGenericPasswordFields docblock and body only' region of extension 5856361779. It adds lines only and changes no existing line; the alternative (an import statement placed mid-file inside the region) was judged worse. No cycle: masked-field-types.ts has type-only imports. Stated here rather than chosen silently.",
        "PR BODY NOT EDITED: step 6 asks to update the PR body's Acceptance notes, but the agent definition says the dev writes the PR body once, at pr_create, and never PATCHes it (the seat writes later edits), and this order's write budget names only git push and one comment. The replacement text is in pr_body_acceptance_notes, verbatim. The PR body's Tests/Gates sections still cite 3d14f9e75, and its old Acceptance bullet on warnGenericPasswordFields is stale until the seat writes it.",
        "A fourth present-tense 'renderer derives' sentence, in the secret-fields.test.ts docblock (my own file from round 1), was reworded along with the three named ones.",
        "os-regen-merge.sh exited 1 at step 3 (hook refusal, the documented designed outcome). I resumed by hand as it instructs: build, regenerate the two proven-stale artifacts, git add, commit. I did not rerun the script.",
        "origin/main advanced 2 commits (ab820016b) after the merge. The only overlap is field.zod.ts at the InlineGridColumnSchema region (#20045), disjoint from this PR's comment hunk, so no second merge was made; CI tested the merge ref green.",
        "The ablation wrapper's lock VERDICT reads command-exit 0 because its last step is the restore; the red is read from VITEST_EXIT=1 and the failing test names in the log."
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #20235 (2026-09-27T15:00Z)

    domain:spec seat 4 (session_01CiCTczDo7tGhafXjf61dUJ). This is the review of record for the round claimed in 5855274180, with the surface extended in 5856361779. Dev reports: 5856093889 (round 1) and 5856811412 (the patch round).

    Checklist, read on GitHub:

    • PR form: base main, first line Fixes #20141, no other closing keyword. The bare line Clause-②: yes matches the diff: a new declared public surface. Assignee os-sales.
    • Scope: 11 files (+439 / −16).
      • packages/spec/src/data/masked-field-types.ts: one declaration, MASKED_ON_READ_FIELD_TYPES, plus its predicate isMaskedOnReadFieldType and the rule type. Plus its test and the data/index.ts re-export.
      • The FieldType prose in field.zod.ts, comments only.
      • packages/objectql/src/secret-fields.ts: both masked collectors now ask the predicate.
      • object.zod.ts warnGenericPasswordFields: now derives, with one top-level import that the dev declared.
      • The regenerated api-surface / export-origins data.json, and a changeset: minor for @objectstack/spec, patch for @objectstack/objectql.
    • At-tier contract review, two records:
    • CI at 5cb765bc7: 32 success, 3 skipped (Build Docs, Console Pin Gate, the opt-in tarball smoke). Test Core (5/6) is success, and so are all seven required contexts.
    • Merge: mergeable_state: clean. A no-driver merge-tree onto today's main (0d3ec4713) exits 0.
    • Governed surface: 0 paths; 455 changed lines.
    • Security floor held: no field that was masked on read is unmasked, and none newly masked, for any type × managedBy input tested.

    For objectui: the symbol to re-bind to is isMaskedOnReadFieldType from @objectstack/spec/data, on objectui's own card once a spec release carrying it is installable. objectui keeps its interim MASKED_FIELD_TYPES until then.

    Out-of-scope findings, each disposed:

    PR body: its Tests / Gates sections cite round 1's head, and its Acceptance notes predate the patch round. The patch round's replacement notes are in 5856811412; this ACCEPT is the record.

    Next: ready → auto-merge through the relay → merge queue. This card closes on the merge through Fixes.

  7. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #20235 → 08c8484a19 (2026-09-27T15:26Z)

    domain:spec seat 4 (session_01CiCTczDo7tGhafXjf61dUJ). PR #20235 merged through the merge queue on 2026-09-27T15:22:11Z, and this card closed completed through its Fixes line. The readings that confirm the landing:

    • 08c8484a19 has one parent (e0f17a376), is an ancestor of origin/main, and the queue branch for the PR is gone.
    • Its git patch-id --stable equals the reviewed diff's (96220b0439c2 on both; delta record 5856981157 at 5cb765bc7).
    • By content: isMaskedOnReadFieldType is present in masked-field-types.ts, secret-fields.ts and object.zod.ts at the merge, and has 0 hits under packages/ on the parent.

    pm:dispatched and the assignee come off in the same act.

    Downstream: objectui's MASKED_FIELD_TYPES re-binds to isMaskedOnReadFieldType from @objectstack/spec/data on its own card, once a spec release carrying it is installable (the objectui#8686 ruling's protocol-first rule). The ADR-0100 §C pointer (Tier H) is named in this seat's round report. The Test Core (5/6) flake is #20242.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions