Repository navigation
fix(spec): the protocol 16 → 17 conversion summaries and three descriptions state each decision in words instead of a tracker number (stage 4) - #21555
Conversation
…s state their decisions in words Stage 4 of the spec lane's runtime-string burn-down, class (b): the 56 toMajor-17 ADR-0087 conversion summaries (68 tracker ids) and three schema and route descriptions (3 ids) now state what each cited decision was, or drop a citation the sentence already explained. Text only: no conversion's id, surface, step, transform or order changes. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…erences from the reworded text check:generated --fix regenerated exactly the three artifacts it proved stale: docs/protocol-upgrade-guide.md, packages/spec/spec-changes.json and the autonumberFormat rows of the generated reference pages. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…aries Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…ec-strings-stage4
📓 Docs Drift CheckThis PR changes 1 package(s): 53 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 12 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e268f5d3e8aba1a2e110906665d07432915d9172 && git checkout e268f5d3e8aba1a2e110906665d07432915d9172
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 72af58c62167d21e5663c099a62f998f72afe81a db81973cf679f803402069d0641b864782cfe2d4 && git checkout -B drift-repro 72af58c62167d21e5663c099a62f998f72afe81a && git merge --no-ff db81973cf679f803402069d0641b864782cfe2d4
node scripts/docs-audit/affected-docs.mjs --json 72af58c62167d21e5663c099a62f998f72afe81a
|
Contract reviewServed-tier: ① Derived judgmentsInputs. Read on GitHub through REST: card #20749 (body and all 14 comments, the stage-4 claim Text only; the accept set is unchanged. Every hunk of the three sources is read. In Public surface. A summary is a data value an author reads: the "Change" column of Generated artifacts mirror the source. The three regenerated artifacts are exactly the three these strings feed. The 59 rewritten strings, each against the cited decision. All 59 state the decision truly or drop a citation whose sentence already stated it; none changes the summary's "what moves" clause.
No surviving quote, no pin. The replaced id-bearing fragments survive at this head only in CHANGELOGs and The A2 split. Class (b) at the base is the stage-3 census unchanged: 91 summaries / 108 ids / 81 cards plus 3 descriptions. Over the claim's ~60-card bar, so delivered to the lowest protocol step that carries ids (17; steps 11, 13, 14, 15 carry none): 56 / 68 / 48, plus the 3 descriptions, 51 distinct cards and 493 changed lines (83 in source). The next stage, 35 / 40 / 34, is listed file:line at this head. The boundary is the one the claim named. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
ACCEPT — PR #21555 @
|
Part of #20749
Clause-②: no
Stage 4 of the
domain:speclane's share of the runtime-string burn-down (ruling5902360492, form D): class (b) of the stage-3 census, the textpackages/spec/srcshows authors and administrators. Every rewritten string now states in words what the cited decision was, or drops a citation its sentence already explained. Text only.What changed
packages/spec/src/conversions/registry.ts(68 tracker ids): every protocol 16 → 17 summary that carried an id. A summary is the "Change" column ofdocs/protocol-upgrade-guide.md, thetotext ofspec-changes.json'sconverted[]records and whatos migrate meta --jsonreports underspecChanges, so it is read by an author upgrading metadata.FieldSchema.autonumberFormat's.meta()description (data/field.zod.ts), and the route descriptions ofGET /:type/:name/layersandPOST /:type/:name/publish(api/plugin-rest-api.zod.ts).check:generated --fix(exactly the three artifacts it proved stale):docs/protocol-upgrade-guide.md(56 rows),packages/spec/spec-changes.json(56 summaries, twice each: the per-major record and the aggregate), and theautonumberFormatrows ofcontent/docs/references/data/field.mdx,data/object.mdxandsystem/migration.mdx.@objectstack/specpatch changeset,Clause-②: no.Size: the split (A2)
At this base the class (b) population is unchanged from the stage-3 census: 91 conversion summaries with 108 ids (81 distinct cards) plus 3 descriptions with 3 ids. That is over the ~60-card bar, so this PR delivers up to a protocol-step boundary, lowest step first:
The three descriptions ride this part (3 more cards, no overlap): 59 messages, 71 ids, 51 distinct cards delivered. The next stage's exact list is the 35 toMajor-18 summaries at the end of this body.
Delivered: each site, the decision read, the new words
Every cited card was read through REST with all comments; the record column names the comment (or commit) the decision was read from. Where a summary already said why, the citation is dropped and the sentence kept. Placeholders
OBJECT_NAME/RULE_NAMEbelow stand for the angle-bracket spelling in the source.action-execute-to-target(:727)executeis the deprecated alias oftarget; spec and objectui resolved the pair in opposite directions; align on the spec rule and drop the alias so the divergence is unrepresentable (body (closed completed, no comments))field-conditionalRequired-to-requiredWhen(:767)requiredWhencanonical, alias folded and dropped from parsed output (body (closed completed, no comments))agent-tools-to-skills(:822)agent.tools[]removed because it resolved names against the full registry with no surface check, breaking ADR-0064 (tool set = union of skills' tools) (PR body (merged))sharing-rule-access-level-full-to-edit(:881)full→editis lossless (5105498900)fullnever granted more thanedit; a sharing rule grants read or edit, while delete and transfer come from object permissions and ownership)flow-node-crud-object-alias(:954)??+ the shim's lastobject) graduate straight into the D2 layer; thereadAliasedConfigshim is deleted (5125152179)readAliasedConfigshim graduates into this layer, and the shim is deleted)flow-node-notify-config-aliases(:1077)??fallbacks graduate,actionUrlcanonical (downstream chain uses it). #4045:notify.sourcewas a read-but-undeclared shape → conversion layer, not configSchema (5125152179; 5127636357, 5138487536)??fallbacks graduated into this layer;actionUrlis canonical because the notification chain downstream already uses it), and nested 'source: {object, id}' → 'sourceObject' / 'sourceId' (a shape the executor read that no config schema declared)flow-node-wait-event-config-lift(:1288)node.config.eventTypeetc. were an undeclared second contract beside the declaredwaitEventConfig; graduate them (#4161) (5130277099, 5138487536)waitEventConfigblock: 'eventType', 'timerDuration'/'duration' → 'timerDuration', 'signalName'/'signal' → 'signalName', 'timeoutMs' (the executor also read these keys from the loose config, a second contract beside the declared block)flow-node-map-flow-alias(:1364)map.flowalias (undeclared executor fallback); graduated (#4228) (5138487536)flow-node-subflow-flow-alias(:1421)subflow; its bareflowName ?? flowfallback graduates (body (closed completed, no comments) + conversion docblock)flow-node-connector-config-lift(:1529)connectorConfig; the descriptor schema rooted the triple atconfig, so the Studio form wrote unread keys; descriptor stops publishing, stored loose keys lift (5132926517, 5138487536)connectorConfigblock (the executor reads only that block; the published designer form had been writing these keys where nothing read them)flow-node-script-config-aliases(:1641)??fallbacks graduate into the D2 layer (5125152179)??fallbacks, graduated into this layer)app-dead-authoring-keys-removed(:1742)app-area-fail-open-gates-removed(:1853)permission-rls-priority-removed(:1955)tool-inert-authoring-keys-removed(:2024)action-inert-keys-removed(:2137)flow-inert-keys-removed(:2162)view-inert-keys-removed(:2221)view-list-passthrough-keys-removed(:2267)view-export-options-pdf-removed(:2335)pdfleaves the enum (honest narrowing, not a runtime console.warn); PDF export declined (5270998514; #1301 is not planned)dashboard-inert-keys-removed(:2404)dashboard-widget-responsive-removed(:2474)responsive(no reader); #11027 ruling B: retirepage.components[].responsive, equally unread (5169512655; 5380752244)dashboard-widget-action-aria-removed(:2555)dashboard-widget-compareto-converged(:2652)compareToon the implemented executor contract{ kind, dimension? };1yrewrites, other offsets delegated (5173559485)agent-knowledge-removed(:2726)skill-trigger-phrases-removed(:2744)stack-api-require-auth-removed(:2782)api.requireAuthopt-out; anonymous always denied; public surfaces derive authorization from a declaration (form, share link, book audience) (body (decision recorded in body))book.audience: 'public'), which replaced the deployment-wide opt-outflow-node-wait-timeout-keys-removed(:2864)timeoutMsmoves totimerDuration(body (closed completed) + conversion docblock)datasource-inert-blocks-removed(:2944)mapping-inert-keys-removed(:3032)book-translations-removed(:3087)doc.translationsis livejob-id-removed(:3149)nameis the job's identity everywhere, so two jobs differing only inidwere the same job, and the key's own description advertised an override that did not exist)translation-validation-messages-removed(:3206)errorsat it; #14381 an object-scoped key ships with its reader (ADR-0049 enforced) (5159774792; #3778 body; 5503980929)errors:authors into it). Author the message on the rule itself (object.validations[].message), and translate it under the object-scoped groupobjects.OBJECT_NAME._validations.RULE_NAME.message, which the write path resolves (17.3.0, a translation key shipped together with its reader)datasource-capabilities-removed(:3264)readOnlynever made anything read-only)datasource-read-replicas-removed(:3319)configat it)datasource-config-driver-key-aliases(:3419)??fallbacks graduate to a D2 entry and are deleted from the reader (5157922838)??fallbacks, graduated into this layer and deleted from the reader)flow-node-script-branch-keys-removed(:3665)scriptconverges to a pure function-call node; the five branch keys retire (5151704360)object-managed-by-system-to-system-data(:3762)system, new valuesystem-data(notplatform-data) (5157022965)engine-owned; the rename leaves the name describing what the bucket actually holds: admin/user-writable platform data)object-enable-trash-mru-removed(:3829)enable.trash/enable.mru; soft delete stays parked; last slice of the dead-property removals (5156966571, 5161298967; 5051634768)object-index-type-partial-removed(:3912)retry-policy-converged(:4070)backoffMs, merged default 0 / 1 with pre-17 job defaults written out; flow.errorHandling joins, default 0 (silent retry can double-write) (5158710540 (analysis); 5173148383)hook-body-crypto-hash-removed(:4249)dataset-measure-array-string-agg-removed(:4419)array_agg/string_agg; keep and enforcecount_distinct(5219849918)connector-rate-limit-config-removed(:4544)field-mapping-transform-removed(:4669)jsdialect was retired as redundant with the L2 script body (5199338349; #3278 body)mapping.fieldMapping[].transform, which is unaffected)theme-inert-token-scales-removed(:4786)customVars(5175091297)page-header-subtitle-alias(:4940)description→subtitle; the consumer's bare??retires (5160118898, 5194297145)subtitle ?? descriptionfallback;subtitleis the declared key, and the fallback retires)record-picker-display-field-to-label-field(:5165)labelField(the delivered spelling) becomes canonical;displayFieldretires via conversion (5202137085)labelField ?? 'name'is what renders the row, so the delivered spelling became the declared one)record-picker-inert-keys-removed(:5287)searchFields/multipleretire (zero readers) (5202137085)page-card-body-to-children(:5414)childrenis the one composition key (5202137085)inline-action-api-params-to-body-extra(:5582)bodyExtra);paramskeeps one meaning (5202138112, 5228796853)paramsstays the ActionParam[] definition array)page-tabs-type-to-tab-style(:5847)typecollides with the node's dispatch key and is unauthorable in flat/JSX carriers;tabStyleis the spelling the renderer reads in all of them)page-structure-inert-keys-removed(:6035)record-details-layout-removed(:6201)app-hidden-to-unpublished(:6343)hiddenback to navigation presentation only; ADR-0045 amended (5173161521)hiddencarried BOTH the publish gate and 'keep out of the App Switcher', so the built-in Account app was withheld from every non-builder; the gate is now the machine-managed_unpublished, andhiddenis navigation presentation only, never an access gate. Stored rows only — an authoredhidden: trueis left untouched)action-global-nav-location-removed(:6456)global_nav(no demand; the designer previewed a surface the product lacks) (5229990375)locations: [])Descriptions:
data/field.zod.tsautonumberFormat{0000}is a declared contract default, and both hand-written fallbacks read it (5225535766, family done 5240072006){0000}, which every driver and the engine fallback read, so one field numbers alike on every backend."api/plugin-rest-api.zod.tsGET /:type/:name/layers/layerspath and schema, one route one shape (recorded 5216370790)api/plugin-rest-api.zod.tsPOST /:type/:name/publishText-only proof (A4)
Stage 3's AST-skeleton + string-text tool (
skeleton.cjs, one line changed: the TypeScript 6.0.3 load path), BASE1ac7308d7aagainst this branch: 3 of 3 SAME on both legs, exit 0 each —registry.ts62346 tokens, 4920 string groups, 56 changed;field.zod.ts8682 / 581 / 1;plugin-rest-api.zod.ts4912 / 475 / 2 (one literal re-split into three+pieces, which the skeleton reads as one string); parse diagnostics 0 / 0. Every changed group carried an id before and carries none after; every other string is byte-identical. Controls on scratch copies of the headregistry.ts, each mutation counted on disk first: an identifier rename → DIFF exit 1;!==flipped to===→ DIFF exit 1; one summary re-split into two+operands → SAME exit 0; asurfacestring (never carried an id) changed → text leg VIOLATION exit 1. No repo file was mutated for the controls.Census after the edit (stage 3's instrument, unchanged): non-test 219 → 160 messages, 429 → 358 ids; class (b) conversion summaries 91 / 108 → 35 / 40 (all toMajor 18); descriptions 3 / 3 → 0.
Pins and quotes (A6)
*.test.*/*.spec.*; the hits are other files' own prose with their own citations (test titles and comments such as(#3896 close-out)inview.test.ts), not quotes of a summary.content/docs/**: the only quotes are the three generatedreferences/**pages, regenerated.content/docs/releases/v17/17-0.mdx:1052repeats one action-key phrase with its own(#3896 close-out); it is release-owned and untouched.skills/**: no quote of any changed text.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsatdb81973cf6(9 paths, 493 changed lines): 105 commands, each run from the worktree with its exit code written before any pipe;--ran→ "✓ dispatch-gates --ran: 105 derived famil(ies) accounted for — 105 run, 0 NOT-MEASURED". Three first exited 3 (prerequisite: unbuilt lint / client packages) and were re-run green after the full package build (71 tasks). Plus@objectstack/specbuild,check:generated("All 15 generated artifacts are up to date"), the packagetestproject (603 files, 17845 passed), 48 of the 51test:repofiles, andtypecheck. Details are in the report on the card.Acceptance notes
#3896citations. Eight summaries cited#3896as an "audit" or a "close-out". The card's own body is the sharing-rule REST finding that opened that security-audit line; the decisions the summaries cited (removerls.priority, the four inert tool keys, then the fourteen-key enforce-or-remove sweep) are recorded in the landed commitsd6bfb3d0ab,eb95d97c02and12a19a88a5. Each summary already said why its key went, so the new words name the rule (ADR-0049 enforce-or-remove) rather than the card.conversions/registry.tsstill carry tracker ids; they belong to packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234's comment stages and are untouched. So ismigrations/registry.ts.docs/protocol-upgrade-guide.mdis not a governed surface (check-governed-merges' register).requiresonly on the compiled kinds — refused at parse on react, full and slotted pages (#21459) #21547 ([Decision] pagerequireson the page kinds whose source is never compiled at save (react,full,slotted): refuse it at parse, or keep admitting a key nothing derives? #21459) also editsconversions/registry.ts: it adds one toMajor-18 entry and its ordering row, in a region this PR does not touch. A localgit merge-treeof the two heads is clean (the file is hand-written, so no merge driver is involved). Neither PR's spec-changes / upgrade-guide output includes the other's entries, so whichever lands second mergesmainand regenerates them.Next stage: the 35 toMajor-18 summaries (file:line at this head · ids · conversion)
registry.ts:6550spec:Field.scaleaccepts meaningless declarations (2.5,-1) — now that scale is enforced, malformed declarations should be refused at authoring time #8321field-malformed-scale-precision-removedregistry.ts:6658spec:RecordChatterProps.positiondeclares a vocabulary no read point compares —sidebar/inline/drawervs the renderer'sbottom/right/left— and itscollapsibledefault inverts the renderer's #8762record-chatter-position-vocabularyregistry.ts:6777ADR-0049 enforce-or-remove:targetVariableonelement:text_input/element:record_picker— a declarative hint with zero readers in any repo and no liveness-ledger entry #9198element-input-target-variable-removedregistry.ts:7024ADR-0049 candidate:element:filterdeclarestargetVariable(and possibly the whole element) with no renderer or reader in any repo #9220element-filter-removedregistry.ts:7177ADR-0049 candidate:element:formdeclares a full form contract with no renderer registration found in objectstack/objectui #9249element-form-removedregistry.ts:7355[finding]TranslationDataSchema.settingsis declared, read by the settings service, and extractable by nothing — registry-driven emitter, or removal from the per-app schema (ADR-0049) #15178,[Decision]TranslationItemSchemastill declaressettings— the item door can override platform settings copy, and it is the stronger of the two doors the batch #132 ruling only half closed #19620translation-per-app-settings-removedregistry.ts:7609ADR-0049 candidate:element:formdeclares a full form contract with no renderer registration found in objectstack/objectui #9249translation-component-submit-label-removedregistry.ts:7782fix(actions): an action that CRASHED is a 500, not a 200 reporting success:false (#3913 follow-up) #3951,[spec] Field.inlineColumns is z.array(z.any()) — a mis-keyed inline-grid column publishes clean and renders blank cells #9227field-column-lists-canonicalizedregistry.ts:7909[finding]MetricSchema.filtersis an authorable per-metric filter with zero consumers — a hand-authored cube'sfilters: [{ sql }]is parsed and dropped #10414metric-filters-removedregistry.ts:8104TimeUpdateInterval declares eight intervals while driver-memory answers 501 for three — the ADR-0049 enforce-or-remove question #17206's changeset promises a card for #17296cube-sub-day-granularities-removedregistry.ts:8237[finding]Cube.joins[].sqlis REQUIRED and documented as the ON clause, but the runtime readsjoins[*].nameonly — an authored join condition is silently REPLACED by a synthesised FK equality under a 200 #18612cube-join-sql-and-relationship-removedregistry.ts:8502record:highlightsfieldiconis normalized by the renderer and then drawn by nothing at the pin — a possible #8691-class dead key #10054record-highlights-field-icon-removedregistry.ts:8759ADR-0049 enforce-or-remove:page.components[].responsiveis wired into no renderer — and four shipped author-facing texts redirect authors to it as the live alternative (cloud leg unmeasured) #11027page-component-responsive-removedregistry.ts:8860[spec] ADR-0049 enforce-or-remove: retireObjectGridProps.defaultSort(the producer half of objectui#5861) #11805object-grid-default-sort-removedregistry.ts:9047finding(spec):ComponentPropsMap['object-grid']keepsresizableColumnsas a live alias ofresizable, and types six membersObjectGridreads with fixed shapes asz.unknown()(rowHeight: 42passes every door) #21445object-grid-resizable-columns-removedregistry.ts:9250spec(ui):ObjectKanbanProps.quickAddis accepted-and-dropped onobject-kanban— objectui ruled it retired; the tombstone is this repo's half #17260object-kanban-quick-add-removedregistry.ts:9563spec: retire theallowRestore/allowPurgepermission props (ruled 2026-08-26; M2 anchor stays open, keys return with M2) #12497,[security][立项位] M2 权限生命周期(undelete/purge)功能与 allowRestore/allowPurge RBAC 同批建设(evaluator 已 fail-closed,allowTransfer 已 enforced) #1883permission-allow-restore-purge-removedregistry.ts:9881objectui: element:button forwardsbodyExtrato the ActionRunner; the object-formparamsarm becomes a deprecation window (#5777 objectui half) #6837field-reference-to-aliasregistry.ts:10301spec: duration-shaped number keys carry their unit in describe prose only — twottlkeys with different units in one block, baretimeoutkeys, unit-less tenant timeouts #14478hook-timeout-to-timeout-msregistry.ts:10342spec: duration-shaped number keys carry their unit in describe prose only — twottlkeys with different units in one block, baretimeoutkeys, unit-less tenant timeouts #14478job-timeout-to-timeout-msregistry.ts:10946spec: duration-shaped number keys carry their unit in describe prose only — twottlkeys with different units in one block, baretimeoutkeys, unit-less tenant timeouts #14478api-endpoint-cache-ttl-to-cache-ttl-secondsregistry.ts:11015spec: duration-shaped number keys carry their unit in describe prose only — twottlkeys with different units in one block, baretimeoutkeys, unit-less tenant timeouts #14478dashboard-refresh-interval-to-refresh-interval-secondsregistry.ts:11376spec: duration-shaped number keys carry their unit in describe prose only — twottlkeys with different units in one block, baretimeoutkeys, unit-less tenant timeouts #14478memory-persistence-auto-save-interval-to-msregistry.ts:11600spec: duration-shaped number keys carry their unit in describe prose only — twottlkeys with different units in one block, baretimeoutkeys, unit-less tenant timeouts #14478turso-config-timeout-to-timeout-msregistry.ts:11690spec: retire thepagemember of the list-viewtypeenum and itspageNamemount — enforce-or-remove, the objectui render half was never built (maintainer 2026-09-09 「撤」) #17063view-page-mount-removedregistry.ts:11795spec(ui):ListViewSchema.sortstill accepts the legacy"field desc"string — it is the PRODUCER whose documents objectui now refuses loudly, and #16553 does not cover it #17053,fix(plugin-auth): OIDC SSO registration works again — drop the retiredoidcConfig.mapping.id#8221list-view-sort-string-clause-to-arrayregistry.ts:12295Retiretenancy.organizationFieldfrom the authorable surface — one platform table's fact, not customer configuration #19054object-tenancy-organization-field-removedregistry.ts:12405view.hiddenandview.ownerare declared on the strictViewItemauthoring door and stored verbatim, but nothing in either repo reads or writes them — andcheck:livenesscannot see it, because its view walk stops at the container arm #20085view-item-owner-hidden-removedregistry.ts:12549spec(ui): a flattened view overlay acceptsowner/hiddenat the save door and nothing reads either, sohidden: truesaves clean and the view stays listed #20230view-overlay-owner-hidden-removedregistry.ts:13137同族第三处组装:share-link 路由把授权信封裁成 4 个字段后直接当 enforcement context 喂给 engine.find ——group租户姿态下 Layer 0 墙恒判否 #6206,decision: should the losslessly mappable subset of the record-formfilterget a real D2 conversion, or is "stored rows keep loading, re-saving is refused" the final answer? #17321page-component-filter-record-to-rule-arrayregistry.ts:13392[finding] a joined report'schart— container andblocks[].chart— parses and is never drawn, while the liveness ledger names the joined branch as its reader #20161report-joined-chart-removedregistry.ts:13657spec(ui):layout: 'inline' | 'grid'onobject-form/FormViewparse green and render asvertical— enforce-or-remove (ADR-0049) #20221form-layout-inline-grid-to-verticalregistry.ts:13813[finding] currencyConfig.precision is declared and validated against ISO 4217, but no renderer or runtime reads it — an ADR-0049 enforce-or-remove case, filed on ruling 乙 on #19910 #19992currency-config-precision-removedregistry.ts:13917spec(security): retirerowLevelSecurity[].tags(1 key); no mainstream platform tags a row-level policy #20321permission-rls-tags-removedregistry.ts:14056A decision node with no declaredconfig.conditionstakes EVERY out-edge whose condition holds, in parallel — nothing enforces or warns that intended-exclusive edges partition #15429flow-decision-mode-inclusive-explicitGenerated by Claude Code