Skip to content

finding: seven non-published sites still state the superseded readonly-on-INSERT contract as live — carded, not fixed, by the 2026-09-05 E3 ruling 「已发布必修,其余立卡」 (PR #15395 / #14147) #15819

Description

@claude

What this card is

The maintainer ruled on E3 (comment 5550450281 on #14147, 2026-09-05, verbatim: 「已发布必修,其余立卡」): for the class 「a superseded architecture still stated as live」, a published surface is fixed in the round, and non-published residue becomes one finding card while the PR lands. This is that card for PR #15395 (card #14147, ruling C 5522734749). The maintainer ruled these sites are carded rather than fixed in the PR; nothing here blocks #15395.

The class, in one sentence: any present-tense statement that a non-system INSERT is exempt from, or seeds past, the static readonly strip, or that the strip lives at the DataProtocol ingress. False since ruling C (2026-09-03): engine.insert runs the same isSystem-gated stripReadonlyFields as engine.update; the metadata-protocol ingress copy is deleted; a sys_-prefixed or managedBy object is left to its own guards by staticReadonlyInsertSubject (that asymmetry is #15719); readonlyWhen alone stays insert-exempt; preserveAudit is UPDATE-only.

How 「published」 was measured (PR #15395 patch round R4, head a73dd85db): the three packages were rebuilt and each site's sentence grepped, exit captured before any pipe, in the built dist/**/*.d.ts (the shipped declaration surface), the dist/**/*.{js,mjs,cjs}, and content/docs/** (the generated references/** projections and the customer-facing pages), with a firing control per package (the exported identifier next to the sentence). Three of the third verdict's nine N5 sites reached a .d.ts and were fixed in R4packages/spec/src/security/public-form.ts (TSDoc on PUBLIC_FORM_SERVER_MANAGED_FIELDS), packages/objectql/src/readonly-strict-errors.ts:166-173 (the operation property TSDoc of ReadonlyFieldRejectedError) and packages/services/service-settings/src/settings-service.ts:2274-2285 (the upsertRow docblock). The sites below reach no published artefact: test files and scripts/ are outside every package's files, and a floating module docblock ahead of a non-exported const is not emitted into the .d.ts. One boundary stated so nobody re-derives it: every shipped .js.map embeds its whole source (sourcesContent), so by that reading no source comment is ever non-published — that reading contradicts the ruling's own examples (test headers, in-source notes, scripts/ text), so the .d.ts / docs measurement above is the one applied.

The residue — each site, what it says, why it is the class

# site (at a73dd85db) what it says why it is the class
1 packages/objectql/src/readonly-strict-errors.ts:21-22 — the module docblock of the file that defines ERR_READONLY_FIELD_REJECTED (floating, ahead of the non-exported READONLY_CLASS_REASONS; NOT in dist/index.d.ts, measured) 「on INSERT only the runtime-owned ones, because a create is deliberately exempt from the author-declared strips (#3413)」 False since C: engine.ts throws ReadonlyFieldRejectedError with the insertDropped union the static pass feeds; engine-insert-static-readonly-strip.test.ts 「strictReadonlyWrites refuses before any driver dispatch」 pins a static completed_at refused with ERR_READONLY_FIELD_REJECTED. ⚠️ The same file's operation TSDoc (:166-173) WAS published and was corrected in R4, so the file now disagrees with itself 150 lines apart — this card is the remedy for the non-published half. buildRefusalMessage is right and pinned byte-for-byte (#5126/#5503); do not touch it
2 packages/services/service-settings/src/settings-secret-rotation.test.ts:13 (test header) 「The INSERT path is deliberately exempt (#3413).」 The general sentence is false. The object (sys_setting: sys_-prefixed, managedBy: 'engine-owned') still behaves as the file describes — the first write inserts and lands, later updates are stripped — but by the platform-object carve-out in staticReadonlyInsertSubject (#15719), not by #3413. Its source sibling settings-service.ts:2274-2285 was corrected in R4 with exactly that mechanism
3 packages/services/service-settings/src/settings-secret-rotation.test.ts:189 (a helper's docblock) 「while the INSERT path is exempt (#3413)」 As 2
4 packages/services/service-settings/src/sys-secret-orphan-report.test.ts:190 (a helper's docblock) 「while the INSERT path is exempt (#3413)」 As 2
5 packages/rest/src/rest-batch-endpoint.test.ts:308-312 (the comment heading the create-ingress-parity block) 「The engine's INSERT path is static-readonly-exempt by design (#3413), so the #3043 strip that stops a non-system caller from seeding a read-only column lives at the protocol's create ingress」 The overturned sentence verbatim, one file from the source note (rest-server.ts:12357-12375) that R3 corrected — source and test now contradict each other. The routing the test pins is still right (one create ingress, one response contract: assertObjectRegistered #3770, the internal: true response strip #7823, the droppedFields relay); only the stated reason is superseded
6 packages/services/service-automation/src/builtin/crud-dropped-fields.test.ts:144-146 (comment heading create_record is wired symmetrically (#3407)) 「Today ObjectQL's insert path strips nothing (INSERT is readonly-exempt, FLS write denial throws) — but the node listens anyway, so a future insert-side strip surfaces instead of going silent.」 The 「future」 strip is live: crud-nodes.ts:321-330 one file over says so, and create-record-readonly-drop.test.ts (2 passed) proves a non-system flow's readonly seed is dropped and reported. The test's own assertions still hold (they feed the channel a synthetic drop)
7 packages/spec/scripts/liveness/proof-registry.mts:156 (provenance comment on the readonly-static-write proof entry) 「The proof pins the server-side strip (forge dropped, sibling edit lands, insert exempt) over real HTTP.」 The ledger's own statement of what the proof proves; the proof's header (showcase-static-readonly.dogfood.test.ts) and the R2-fixed packages/spec/liveness/field.json readonly row say the strip covers BOTH write paths. scripts/ is outside packages/spec's files; the :150 summary (「cannot be forged by a non-system UPDATE」) is true but names UPDATE only — a spec-lane call whether it should name INSERT too

Stale by omission, not false — listed so nobody re-finds it: packages/objectql/src/validation/rule-validator.ts:1021, the stripReadonlyFields docblock, opens 「Strip CALLER-SUPPLIED writes to read-only fields from an UPDATE payload (#2948)」 and describes its guards in UPDATE terms, while since #14147 engine.insert calls the same function over staticReadonlyInsertSubject (the function's callers and the later paragraphs of the same file say so). Not in dist/*.d.ts (measured; control stripReadonlyFields present twice in other docblocks that already say 「on both write paths since #14147」).

Out of this card, stated so it is not re-filed: skills/objectstack-data/SKILL.md:274 (「from an UPDATE payload」) is the governed half of the ruling and lands in #15382 (human merge). content/docs/kernel/contracts/data-engine.mdx:371-373 is a historical quotation followed by 「That row is superseded」. Everything else the R4 sweep returns (147 unique lines over the twelve premise spellings, the content-only ingress co-occurrence pass and the claim-level patterns insert…exempt / exempt…insert / readonly-exempt / may seed|can seed / seed…readonly / readonly…seed / #3413 / covers UPDATE etc.) is classified line by line in the PR #15395 body (R4 section) as historical, ruled-state, readonlyWhen, the #7823 response strip, delegation prose, a pin that forbids the spelling, an unrelated subject, or a parser fixture.

How to close it

One prose-only PR, no behaviour change, no test deleted or skipped: replace each sentence with the ruled state (the R3/R4 corrections of rest-server.ts:12357-12375, settings-service.ts:2274-2285 and crud-nodes.ts:321-330 are the templates), then re-run the sweep from the PR #15395 body and show the live class at zero with the same positive controls. Cross-links: #14147 (card), PR #15395 (the engine change), ruling C 5522734749, verdicts 5548671173 (+ addendum 5548681179), 5549250610, 5550434842, the E3 ruling 5550450281, #15719 (the create/update asymmetry on managedBy objects), #15382 (the governed prose half), #15394 (the lint rules' scan gap).

Filed by the domain:engine execution seat, session session_01ARYe3yQTQCUFm5qPYNgKaJ, PR #15395 patch round R4. Ungraded, unrouted, unassigned — finding only, for the triage round.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions