Skip to content

fix(spec): the protocol 16 → 17 conversion summaries and three descriptions state each decision in words instead of a tracker number (stage 4) - #21555

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20749-spec-strings-stage4
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20749-spec-strings-stage4

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20749
Clause-②: no

Stage 4 of the domain:spec lane's share of the runtime-string burn-down (ruling 5902360492, form D): class (b) of the stage-3 census, the text packages/spec/src shows 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

  • 56 ADR-0087 conversion summaries in packages/spec/src/conversions/registry.ts (68 tracker ids): every protocol 16 → 17 summary that carried an id. A summary is the "Change" column of docs/protocol-upgrade-guide.md, the to text of spec-changes.json's converted[] records and what os migrate meta --json reports under specChanges, so it is read by an author upgrading metadata.
  • Three descriptions (3 ids): FieldSchema.autonumberFormat's .meta() description (data/field.zod.ts), and the route descriptions of GET /:type/:name/layers and POST /:type/:name/publish (api/plugin-rest-api.zod.ts).
  • Generated, by 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 the autonumberFormat rows of content/docs/references/data/field.mdx, data/object.mdx and system/migration.mdx.
  • One @objectstack/spec patch 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:

protocol step summaries with ids ids distinct cards
toMajor 11 / 13 / 14 / 15 0 0 0
toMajor 17 (delivered) 56 68 48
toMajor 18 (next stage) 35 40 34 (33 not cited in 17)

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_NAME below stand for the angle-bracket spelling in the source.

conversion (head line) cited decision as read (record) summary now reads
action-execute-to-target (:727) #3713 execute is the deprecated alias of target; 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)) action key 'execute' → 'target' (the deprecated handler alias; the spec and the renderer had resolved the pair in opposite directions, so one key now names the handler)
field-conditionalRequired-to-requiredWhen (:767) #3754 same fold-and-drop as #3713: requiredWhen canonical, alias folded and dropped from parsed output (body (closed completed, no comments)) field key 'conditionalRequired' → 'requiredWhen' (the deprecated predicate alias, folded into the canonical key so no reader picks its own precedence)
agent-tools-to-skills (:822) #3894 ADR-0109 accepted; 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)) agent key 'tools' removed — declare capability in a skill (ADR-0064: an agent's tools are exactly its skills' tools, and this inline slot resolved names against the whole registry with no surface check)
sharing-rule-access-level-full-to-edit (:881) #3865 route B is the end state: sharing grants read/edit only; delete, transfer and re-share come from object permissions, ownership and admin scope; full → edit is lossless (5105498900) sharing-rule accessLevel 'full' → 'edit' (full never granted more than edit; a sharing rule grants read or edit, while delete and transfer come from object permissions and ownership)
flow-node-crud-object-alias (:954) #3796 the seven aliases (six open-coded ?? + the shim's last object) graduate straight into the D2 layer; the readAliasedConfig shim is deleted (5125152179) CRUD flow-node config key 'object' → 'objectName' (the last alias in the executors' readAliasedConfig shim graduates into this layer, and the shim is deleted)
flow-node-notify-config-aliases (:1077) #3796, #4045 #3796: ?? fallbacks graduate, actionUrl canonical (downstream chain uses it). #4045: notify.source was a read-but-undeclared shape → conversion layer, not configSchema (5125152179; 5127636357, 5138487536) notify flow-node config keys 'to' → 'recipients', 'subject' → 'title', 'body' → 'message', 'url' → 'actionUrl' (executor ?? fallbacks graduated into this layer; actionUrl is 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) #4045 node.config.eventType etc. were an undeclared second contract beside the declared waitEventConfig; graduate them (#4161) (5130277099, 5138487536) wait flow-node loose config keys → the declared waitEventConfig block: '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) #4045 reconciliation found the map.flow alias (undeclared executor fallback); graduated (#4228) (5138487536) map flow-node config key 'flow' → 'flowName' (an undeclared spelling the executor accepted through a bare fallback; it graduates into this layer)
flow-node-subflow-flow-alias (:1421) #4278 reconcile the schemaless nodes' forms with their executors and check subflow; its bare flowName ?? flow fallback graduates (body (closed completed, no comments) + conversion docblock) subflow flow-node config key 'flow' → 'flowName' (an undeclared spelling the executor accepted through a bare fallback, found when the schemaless nodes were reconciled with their executors; it graduates into this layer)
flow-node-connector-config-lift (:1529) #4045 executor reads only connectorConfig; the descriptor schema rooted the triple at config, so the Studio form wrote unread keys; descriptor stops publishing, stored loose keys lift (5132926517, 5138487536) connector_action flow-node loose config keys 'connectorId' / 'actionId' / 'input' → the declared connectorConfig block (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) #3796 as above: open-coded ?? fallbacks graduate into the D2 layer (5125152179) script flow-node config keys 'functionName' → 'function', 'input' → 'inputs' (executor ?? fallbacks, graduated into this layer)
app-dead-authoring-keys-removed (:1742) #4001, #4509, #4667, #4709 liveness audits: keys unread or wrongly encoded are removed; #4709: the "no shell read homePageId" premise was false, ruling B keeps the retirement (an ID cross-reference that dangles is the wrong encoding) (5158421901 (#4509), 5159774792 (#4667), 5164227920 (#4709 ruling B)) app keys 'version'/'aria'/'objects'/'apis'/'sharing'/'embed'/'mobileNavigation'/'homePageId' plus contextSelectors 'includeAll'/'placement' and areas 'order' removed (liveness audits found each one unread or wrongly encoded; sharing/embed declared a public surface no route enforced, mobileNavigation was fully unimplemented, includeAll was deliberately disobeyed because an 'All' row would clear a mandatory scope, homePageId WAS read by objectui's console before v17 but encoded the landing page as an ID cross-reference that silently fell back when it dangled — the landing page is the first nav item (the first retirement record said nothing read it, a premise since corrected; the retirement stands), and no renderer ever sorted areas)
app-area-fail-open-gates-removed (:1853) #4651 ruling B: remove both keys; removing a fail-open gate is strictly safer than keeping it; prescription names the two enforced layers (5160072589) navigation-area keys 'visible'/'requiredPermissions' removed (ADR-0049 — FAIL-OPEN access gates: no layer ever read them, so a 'hidden' or permission-gated area was served and rendered to every user, while the identically named keys on a navigation ITEM and on the APP are enforced; gate the items inside the area, or gate the app)
permission-rls-priority-removed (:1955) #3896 the #3896 security-audit line: rls.priority promised conflict resolution that cannot exist (policies OR-combine) and had no reader; removed (card body + commits d6bfb3d (#3990), eb95d97 (#3998)) RLS-policy key 'priority' removed (a security audit found no reader: policies OR-combine, so the promised conflict-resolution semantics cannot exist; dropping it changes no outcome)
tool-inert-authoring-keys-removed (:2024) #3896 the audit close-out removes the four inert tool keys (permissions gated nothing, active:false withdrew nothing) (commit eb95d97 (#3998)) tool keys 'category'/'permissions'/'active'/'builtIn' removed (authorable and inert, so removed under ADR-0049 enforce-or-remove; permissions gated nothing, active:false withdrew nothing)
action-inert-keys-removed (:2137) #3896 close-out sweep: enforce-or-remove worklist, fourteen inert authoring keys leave the surface (commit 12a19a8 (#4054)) action keys 'shortcut'/'bulkEnabled' removed (inert, removed under ADR-0049 enforce-or-remove: no keydown path dispatches shortcuts; the multi-select toolbar reads the view's bulkActions)
flow-inert-keys-removed (:2162) #3896 as above (commit 12a19a8 (#4054)) flow keys 'active'/'template', node 'outputSchema' and errorHandling 'fallbackNodeId' removed (inert, removed under ADR-0049 enforce-or-remove: active:false never stopped a flow; status is the enforced lifecycle)
view-inert-keys-removed (:2221) #3896 as above (commit 12a19a8 (#4054)) view keys removed as inert (ADR-0049 enforce-or-remove): list 'responsive'/'performance', form 'defaultSort'/'aria' — no renderer read them (list aria/data and form data stay live)
view-list-passthrough-keys-removed (:2267) #7176 maintainer ruling: retire under ADR-0049; pass-through-only reads are dead in effect (5236139723) view list keys removed: 'striped'/'bordered'/'virtualScroll' — every measured reader copied the key forward and none applied it (a key that is only passed through is dead in effect; ADR-0049 enforce-or-remove)
view-export-options-pdf-removed (:2335) #8010, #1301 option A; pdf leaves the enum (honest narrowing, not a runtime console.warn); PDF export declined (5270998514; #1301 is not planned) list-view export format 'pdf' removed (PDF export was declined as not planned, and ObjectGrid dropped the declared format from the menu with only a runtime console.warn; an honest enum replaces that warning)
dashboard-inert-keys-removed (:2404) #3896 close-out sweep (as above) (commit 12a19a8 (#4054)) dashboard keys 'aria'/'performance' and widget 'performance' removed (inert, removed under ADR-0049 enforce-or-remove: no renderer applied any of them)
dashboard-widget-responsive-removed (:2474) #4876, #11027 #4876 ruling A: retire widget responsive (no reader); #11027 ruling B: retire page.components[].responsive, equally unread (5169512655; 5380752244) dashboard widget key 'responsive' removed (no renderer ever applied per-widget breakpoint overrides; the page.components[].responsive key this entry once deferred to was measured equally unread and retired at protocol 18)
dashboard-widget-action-aria-removed (:2555) #5010 retire the four dead widget keys; colorVariant kept (body ruling + 5179556002) dashboard widget keys 'actionUrl'/'actionType'/'actionIcon' and 'aria' removed (no renderer ever drew a per-widget action button, and widget ARIA attributes never reached the DOM; use header.actions[] and the widget title/description)
dashboard-widget-compareto-converged (:2652) #5011 converge compareTo on the implemented executor contract { kind, dimension? }; 1y rewrites, other offsets delegated (5173559485) dashboard widget 'compareTo' converged on the executor's { kind, dimension? } contract (the shape the dataset executor implements; the bare strings and { offset: '1y' } rewrite mechanically; other { offset } durations have no faithful target and are reported, not guessed)
agent-knowledge-removed (:2726) #3896 close-out sweep (as above) (commit 12a19a8 (#4054)) agent key 'knowledge' removed (inert, removed under ADR-0049 enforce-or-remove: declaring sources/indexes never scoped retrieval; restrict at the knowledge-service level)
skill-trigger-phrases-removed (:2744) #3896 close-out sweep (as above) (commit 12a19a8 (#4054)) skill key 'triggerPhrases' removed (inert, removed under ADR-0049 enforce-or-remove: activation is triggerConditions + the agent's skills[] allowlist; phrases were a dead-end projection)
stack-api-require-auth-removed (:2782) #3963 delete the api.requireAuth opt-out; anonymous always denied; public surfaces derive authorization from a declaration (form, share link, book audience) (body (decision recorded in body)) stack key 'api.requireAuth' removed — anonymous access is always denied; publish public surfaces by declaration (a public form, a share link or book.audience: 'public'), which replaced the deployment-wide opt-out
flow-node-wait-timeout-keys-removed (:2864) #4158 wait never had a timeout: withdraw the contract (route B), timeoutMs moves to timerDuration (body (closed completed) + conversion docblock) waitEventConfig keys 'timeoutMs' (→ 'timerDuration', stringified — its only reader used it as the duration) and 'onTimeout' (removed — zero readers, so no timeout ever fired): wait never had a timeout, so its timeout contract is withdrawn rather than built
datasource-inert-blocks-removed (:2944) #4583 all 20 dead datasource keys removed; each job already has a different live mechanism (5157934690) datasource keys 'retryPolicy'/'healthCheck' and external 'label'/'requirePermission' removed (nothing retried, nothing probed on a schedule, and the federation label/permission were read by nobody; each of those jobs already has a live mechanism)
mapping-inert-keys-removed (:3032) #4509 the three mapping keys retire (schema defaults made authorWarn impossible; removal is the only signal) (5158421901, 5158744185) mapping keys 'extractQuery'/'errorPolicy'/'batchSize' removed (no exporter reads a mapping, error handling belongs to the import request, and the write path sizes its own batches)
book-translations-removed (:3087) #4667 six dead authorWarn keys retired (5159774792) book keys 'translations' (book-level and group-level) removed (no resolver read them; the tree endpoint and portal render labels verbatim, so a localized book served its authoring locale to everyone). Localize the docs instead: doc.translations is live
job-id-removed (:3149) #4667 as above (5159774792) job key 'id' removed (nothing read it; name is the job's identity everywhere, so two jobs differing only in id were the same job, and the key's own description advertised an override that did not exist)
translation-validation-messages-removed (:3206) #4667, #3778, #14381 #4667 retire; #3778 legacy-key table had pointed errors at it; #14381 an object-scoped key ships with its reader (ADR-0049 enforced) (5159774792; #3778 body; 5503980929) translation key 'validationMessages' removed (no resolver read it, so a translated rule message was stored and never shown; the legacy-key table of the translation-bundle migration had been steering retired errors: authors into it). Author the message on the rule itself (object.validations[].message), and translate it under the object-scoped group objects.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) #4583 as above (5157934690) datasource key 'capabilities' removed (eleven flags no code read; pushdown comes from the driver's own supports.*, and readOnly never made anything read-only)
datasource-read-replicas-removed (:3319) #4468 remove: read-replica routing is an unbuilt feature (5150771330) datasource key 'readReplicas' removed (no driver opened a replica connection and no query path splits reads from writes; front replicas behind one endpoint and point config at it)
datasource-config-driver-key-aliases (:3419) #4456 the factory's undeclared ?? fallbacks graduate to a D2 entry and are deleted from the reader (5157922838) datasource config keys → canonical per driver: sqlite 'file'/'database' → 'filename', postgres/mysql 'connectionString' → 'url' and 'user' → 'username', mongo 'uri' → 'url' and 'user' → 'username' (undeclared driver-factory ?? fallbacks, graduated into this layer and deleted from the reader)
flow-node-script-branch-keys-removed (:3665) #4343 operator ruling: script converges to a pure function-call node; the five branch keys retire (5151704360) script flow-node config keys 'actionType' (→ 'function' when it was shorthand for one; otherwise removed — 'email'/'slack' were logger-backed stubs that delivered nothing), plus 'template' / 'recipients' / 'variables' (fed those stubs) and 'script' (inline JS the runtime never executed); script is now a pure function-call node, the only path that ran real logic
object-managed-by-system-to-system-data (:3762) #3355 retire system, new value system-data (not platform-data) (5157022965) object managedBy 'system' → 'system-data' (ADR-0103's residual bucket named the engine-owned half v16 had already moved out to engine-owned; the rename leaves the name describing what the bucket actually holds: admin/user-writable platform data)
object-enable-trash-mru-removed (:3829) #3207, #2377 remove enable.trash / enable.mru; soft delete stays parked; last slice of the dead-property removals (5156966571, 5161298967; 5051634768) object capability flags 'enable.trash'/'enable.mru' removed (the last slice of the dead author-facing property removals: no recycle bin and no MRU tracking ever ran; both default-true flags gated nothing)
object-index-type-partial-removed (:3912) #5248, #4943 remove both index keys; no DDL consumer; return enforce-first on real demand (5199336983; 5194762679 (duplicate)) object index keys 'indexes[].type'/'indexes[].partial' removed (no driver ever read either: the index method is the dialect's choice and a partial index is built by a database-layer migration, not declared)
retry-policy-converged (:4070) #4661, #4964 one RetryPolicy declaration, 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) retry policy unified across job.retryPolicy, try_catch retry and flow.errorHandling: base delay 'retryDelayMs' → 'backoffMs', and the pre-17 job defaults (maxRetries 3, backoffMultiplier 2) written out explicitly now that the merged default is 0 / 1: two declarations that differed only by accident became one, and retry is opt-in because a retry replays whatever the attempt already did
hook-body-crypto-hash-removed (:4249) #4391 remove the capability token and its build-time inference (5156969500) script-body capability token 'crypto.hash' removed (the sandbox never installed ctx.crypto.hash, so the token granted a call that always threw; the CLI inferred it too)
dataset-measure-array-string-agg-removed (:4419) #6188 retire array_agg / string_agg; keep and enforce count_distinct (5219849918) dataset measure aggregates 'array_agg' / 'string_agg' removed (no SQL backend compiled them and the v1 dataset runtime refused them by name, so a measure declaring one never produced a value; the measure is dropped, and with it any derived measure left referencing it)
connector-rate-limit-config-removed (:4544) #4911 outbound rate-limit vocabulary removed: no engine exists (implementation-first) (body ruling + 5169405647) connector key 'rateLimitConfig' removed (no outbound rate-limiting engine exists; the runtime's only token bucket limits INBOUND requests, so every knob here was inert while reading like a configured cap. The whole ConnectorRateLimitConfig shape went with it)
field-mapping-transform-removed (:4669) #5552, #3278 enforce-or-remove: all five members dead, the union retires; js dialect was retired as redundant with the L2 script body (5199338349; #3278 body) field-mapping key 'transform' removed (the whole five-member FieldMappingTransform union went with it: no runtime ever executed constant/cast/lookup/javascript/map, and the javascript member advertised dialect="js", a dialect already retired because JavaScript belongs in a script body. The enforced transform pipeline is the import mapping's string-enum mapping.fieldMapping[].transform, which is unaffected)
theme-inert-token-scales-removed (:4786) #5021 retire all nine token groups; re-declare under customVars (5175091297) theme keys 'typography.fontSize'/'fontWeight'/'lineHeight'/'letterSpacing', 'typography.fontFamily.heading'/'mono', 'animation' and 'zIndex' removed (ADR-0049 — the engine emitted --font-size-, --font-weight-, --line-height-, --letter-spacing-, --duration-, --timing-, --z-*, --font-heading and --font-mono faithfully, and no first-party component or stylesheet has ever read one. Re-declare any variable you actually consume under customVars, which emits it verbatim)
page-header-subtitle-alias (:4940) #3226 route B: a D2 conversion rewrites description → subtitle; the consumer's bare ?? retires (5160118898, 5194297145) page-header component prop 'description' → 'subtitle' (the off-spec spelling a renderer tolerated through a bare subtitle ?? description fallback; subtitle is the declared key, and the fallback retires)
record-picker-display-field-to-label-field (:5165) #5775 direction A: labelField (the delivered spelling) becomes canonical; displayField retires via conversion (5202137085) record-picker component prop 'displayField' → 'labelField' (the required key no renderer read; labelField ?? 'name' is what renders the row, so the delivered spelling became the declared one)
record-picker-inert-keys-removed (:5287) #5775 searchFields / multiple retire (zero readers) (5202137085) record-picker component props 'searchFields'/'multiple' removed (the control is a plain single-select with no search box; neither key had a reader)
page-card-body-to-children (:5414) #5775 children is the one composition key (5202137085) page:card component prop 'body' → 'children' (one composition key across every container; the card renderer already reads both)
inline-action-api-params-to-body-extra (:5582) #5777 direction A: the static payload gets its own key (bodyExtra); params keeps one meaning (5202138112, 5228796853) inline type:'api' action prop 'params' (object form) → 'bodyExtra' (a static payload and a parameter definition are two things, so the payload gets its own key; params stays the ActionParam[] definition array)
page-tabs-type-to-tab-style (:5847) #6776 Route A: rename to the spelling the renderer reads (5229120747, 5229693342) page:tabs component prop 'type' → 'tabStyle' (a props key named type collides with the node's dispatch key and is unauthorable in flat/JSX carriers; tabStyle is the spelling the renderer reads in all of them)
page-structure-inert-keys-removed (:6035) #6946 retire three zero-reader UI keys (body (maintainer ruling) + 5232767409) page:header prop 'icon' and page:card prop 'actions' removed (neither has a renderer read point in objectui; the header resolves icons per action and the card renders title/children/footer only)
record-details-layout-removed (:6201) #6946 as above (as above) record:details component prop 'layout' removed (the declared auto|custom modes were never implemented; the renderer branches only on inline|compact, values the schema never permitted, so both legal values selected nothing)
app-hidden-to-unpublished (:6343) #4829 A1: a machine-managed key carries the publish gate; hidden back to navigation presentation only; ADR-0045 amended (5173161521) stored app publish gate 'hidden' → '_unpublished' (ADR-0045 amended — hidden carried 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, and hidden is navigation presentation only, never an access gate. Stored rows only — an authored hidden: true is left untouched)
action-global-nav-location-removed (:6456) #6888 direction 2: retire global_nav (no demand; the designer previewed a surface the product lacks) (5229990375) action location 'global_nav' removed (no running-app surface rendered it; the ⌘K palette reads no action metadata, while the Studio designer previewed a command-palette frame for it. The value is stripped and the key kept, so an action left with no location becomes the documented headless shape locations: [])

Descriptions:

site cited decision as read (record) change
data/field.zod.ts autonumberFormat #6555 route 3: {0000} is a declared contract default, and both hand-written fallbacks read it (5225535766, family done 5240072006) "⇒ the contract default {0000}, which every driver and the engine fallback read, so one field numbers alike on every backend."
api/plugin-rest-api.zod.ts GET /:type/:name/layers #5882 ruling B: its own /layers path and schema, one route one shape (recorded 5216370790) "…hence its own path and its own response schema, since one route answers one shape."
api/plugin-rest-api.zod.ts POST /:type/:name/publish #7294 declare the served publish route's response, the save door's discipline (5237410722) "The route was served for a long time with no declaration behind it — this entry is what makes its response contract nameable, the same declared-equals-returned rule the save door follows."

Text-only proof (A4)

Stage 3's AST-skeleton + string-text tool (skeleton.cjs, one line changed: the TypeScript 6.0.3 load path), BASE 1ac7308d7a against this branch: 3 of 3 SAME on both legs, exit 0 each — registry.ts 62346 tokens, 4920 string groups, 56 changed; field.zod.ts 8682 / 581 / 1; plugin-rest-api.zod.ts 4912 / 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 head registry.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; a surface string (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)

  • Tests: no test asserts a changed summary or description phrase. The id-bearing fragments and the distinctive phrases of all 59 messages were searched across every *.test.* / *.spec.*; the hits are other files' own prose with their own citations (test titles and comments such as (#3896 close-out) in view.test.ts), not quotes of a summary.
  • content/docs/**: the only quotes are the three generated references/** pages, regenerated. content/docs/releases/v17/17-0.mdx:1052 repeats 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 --commands at db81973cf6 (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/spec build, check:generated ("All 15 generated artifacts are up to date"), the package test project (603 files, 17845 passed), 48 of the 51 test:repo files, and typecheck. Details are in the report on the card.

Acceptance notes

Next stage: the 35 toMajor-18 summaries (file:line at this head · ids · conversion)


Generated by Claude Code

claude added 4 commits October 3, 2026 05:20
…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>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tooling labels Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 44 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

53 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 72af58c62167d21e5663c099a62f998f72afe81a.

⛔ 12 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: objectName (literal, 37 pages)
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 138 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 72af58c62167d21e5663c099a62f998f72afe81a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e268f5d3e8aba1a2e110906665d07432915d9172 — the merge of head db81973cf679f803402069d0641b864782cfe2d4 into base 72af58c62167d21e5663c099a62f998f72afe81a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 72af58c62167d21e5663c099a62f998f72afe81a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: db81973cf679f803402069d0641b864782cfe2d4
Local-runs: none

① Derived judgments

Inputs. Read on GitHub through REST: card #20749 (body and all 14 comments, the stage-4 claim 5965785560 and dev report 5966338878 included), ruling 5902360492 on #20513, the stage-3 precedent (PR #21521 landed ad7c351898, its review 5964640454), PR #21555 (body, 9-file list, net diff against the merge base 24dc7c1134, its one bot comment), this head's check-runs, the 52 cited cards with every comment (51 here plus objectui#3226), the three #3896 close-out commits d6bfb3d0ab, eb95d97c02, 12a19a88a5, and PR #21547 (file list and its landed commit). Repository reads were git show / git grep at origin/main, the merge base and this head; the head's conversions/registry.ts, spec-changes.json and docs/protocol-upgrade-guide.md were copied to scratch and parsed there. Nothing was built, run or re-run. An isolated subagent of the seat session rendered this record.

Text only; the accept set is unchanged. Every hunk of the three sources is read. In conversions/registry.ts each of the 56 changed lines is the string literal of a summary: or one operand of its + chain; in data/field.zod.ts the one changed line is the description: string of autonumberFormat's .meta(); in api/plugin-rest-api.zod.ts the changed lines are operands of two description + chains (the publish description re-split into three pieces). No id, toMajor, surface, retiredFromLoadPath, retiredAfter, apply body, schema key, shape, default, enum, route path, method, handler, responseSchema or permission moves. 71 tracker ids leave the removed lines (68 in the registry, 2 in the route descriptions, 1 in the field description); 0 stand on any added line of the diff (every + line swept for # followed by 3 to 5 digits, objectui# included). At the head no toMajor-17 summary carries an id (57 entries) and 35 of 59 toMajor-18 summaries do, which is the next stage's list.

Public surface. A summary is a data value an author reads: the "Change" column of docs/protocol-upgrade-guide.md, the to of spec-changes.json's converted[] (the published projection the MCP spec_changes tool derives from) and what os migrate meta --json reports under specChanges. The three descriptions reach the generated references and the route table's OpenAPI text. Only text changes on those surfaces: no export, type, key or acceptance change, so check:api-surface, the liveness gate and the type-check gates read green as expected.

Generated artifacts mirror the source. The three regenerated artifacts are exactly the three these strings feed. spec-changes.json changes 112 lines, every one a to value (each of the 56 summaries appears twice, in aggregate.converted[] and perMajor[0].converted[]); docs/protocol-upgrade-guide.md changes 56 table rows, one per conversion; the three references/** pages change only the autonumberFormat row (5 occurrences). Cross-checked at the head: all 56 source summaries equal their two spec-changes.json records and their guide row, 0 mismatches. The guide's narrative paragraphs still carry ids (lines 108, 118, 146): they are projected from migrations/registry.ts, #20234's stage 11, which the claim excludes, so rightly untouched.

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.

Conversion / site Cited Read against Judgment
action-execute-to-target #3713 body: spec keeps target, objectui keeps execute; drop the alias so the divergence is unrepresentable True
field-conditionalRequired-to-requiredWhen #3754 body: no transform folded the alias, so precedence was re-implemented per reader; fold and drop True
agent-tools-to-skills #3894 PR body §3: ADR-0064's union-of-skills invariant; runtime resolved agent.tools[].name against the full registry with no surface check True
sharing-rule-access-level-full-to-edit #3865 5105498900: full byte-equivalent to edit; sharing grants read/edit, delete and transfer come from object permissions and ownership True
flow-node-crud-object-alias, -notify-config-aliases, -script-config-aliases #3796 (+#4045) 5125152179: seven ?? aliases graduate straight into D2, the readAliasedConfig shim deleted; actionUrl canonical because the downstream notification chain uses it. #4045 body: notify.source read by the executor, declared by no schema True
flow-node-wait-event-config-lift #4045 5130277099: node.config.eventType an undeclared second contract beside the declared waitEventConfig True
flow-node-map-flow-alias #4045 5138487536 (#4228: map.flow alias) and the entry's docblock (bare cfg.flowName ?? cfg.flow) True
flow-node-subflow-flow-alias #4278 body (schemaless nodes reconciled with their executors) and the docblock: found on subflow "for the #4278 schemaless-node reconciliation" True
flow-node-connector-config-lift #4045 5132926517: executor reads only connectorConfig; the published form wrote the triple where nothing read it True
app-dead-authoring-keys-removed #4001, #4509, #4667, #4709 5158421901, 5159774792; 5164227920 ruling B: "no shell ever read it" was false, retirement stands (an ID cross-reference is the wrong encoding) True
app-area-fail-open-gates-removed #4651 5160072589 ruling B: remove the fail-open gates; prescription names the two enforced layers True: citation dropped, sentence unchanged
permission-rls-priority-removed #3896 d6bfb3d0ab: policies OR-combine, nothing read the key; eb95d97c02 names the line "the #3896 security-audit line" True
tool-inert-authoring-keys-removed #3896 eb95d97c02: the four keys "parsed and did nothing", permissions gated nothing, active:false withdrew nothing True; ADR-0049 is the rule that applied
action-, flow-, view-, dashboard-inert-keys-removed, agent-knowledge-removed, skill-trigger-phrases-removed #3896 12a19a88a5, "the enforce-or-remove worklist across the remaining governed types", each key's reason as the summaries keep it True
view-list-passthrough-keys-removed #7176 5236139723: copy-without-apply is dead in effect; retire under ADR-0049 True
view-export-options-pdf-removed #8010, #1301 5270998514 option A: pdf leaves the enum, "honest narrowing, not a runtime console.warn"; #1301 closed not planned True
dashboard-widget-responsive-removed #4876, #11027 5169512655 ruling A; 5380752244 ruling B: page.components[].responsive wired into no renderer, retired (protocol 18) True
dashboard-widget-action-aria-removed #5010 body + 5179556002: four keys retire, colorVariant kept True: citation dropped, sentence unchanged
dashboard-widget-compareto-converged #5011 5173559485: converge on the implemented executor contract { kind, dimension? }; 1y rewrites, other offsets delegated True
stack-api-require-auth-removed #3963 body: delete the opt-out, anonymous always denied; public form, share link and book audience derive authorization from a declaration True (the three named are the ruling's declaration paths)
flow-node-wait-timeout-keys-removed #4158 body routes A/B; the conversion executes B (timeoutMs → timerDuration, onTimeout dropped), the docblock says why True
datasource-inert-blocks-removed, datasource-capabilities-removed #4583 5157934690: each key already has a live mechanism, so nothing to bridge True
mapping-inert-keys-removed #4509 5158421901 / 5158744185: route A, strict delete True: citation dropped, sentence unchanged
book-translations-removed, job-id-removed #4667 5159774792: six authorWarn keys retired, evidence as the summaries keep it True: citations dropped, sentences unchanged
translation-validation-messages-removed #4667, #3778, #14381 #4667 body: "#3778's legacy-key table" steered errors: authors into it; 5503980929: an object-scoped key shipped with its reader is ADR-0049 enforced, not reversed True
datasource-read-replicas-removed #4468 5150771330: remove; the only reader a preview renderer True: citation dropped
datasource-config-driver-key-aliases #4456 body + 5157922838: conversion entry, every factory ?? deleted True
flow-node-script-branch-keys-removed #4343 5151704360: converge script to a pure function-call node, the only path that ran real logic True
object-managed-by-system-to-system-data #3355 5157022965: system-data, not platform-data True: citation dropped
object-enable-trash-mru-removed #3207, #2377 #3207 body "the final object dead-property slice"; 5051634768 "Final slice → #3414" True
object-index-type-partial-removed #5248, #4943 5199336983: both removed, no DDL consumer; #4943 duplicate True: citations dropped
retry-policy-converged #4661, #4964 5158710540: two spellings of one formula (the C8 dual-source split); 5173148383 ruling A, default 0 because a silent retry can double-write True
hook-body-crypto-hash-removed #4391 5156969500: remove declaration and build-time inference True: citation dropped
dataset-measure-array-string-agg-removed #6188 5219849918: retire the two, keep count_distinct True: citation dropped
connector-rate-limit-config-removed #4911 body + 5169405647: no outbound engine exists True: citation dropped
field-mapping-transform-removed #5552, #3278 5199338349 option B; #3278 body: js dialect retired as redundant with the L2 script body True
theme-inert-token-scales-removed #5021 5175091297: nine groups retire, prescription customVars True: citation dropped
page-header-subtitle-alias objectui#3226 5160118898 route B: conversion entry, consumer reads subtitle only, bare ?? deleted; 5194297145 True
record-picker-display-field-to-label-field, -inert-keys-removed, page-card-body-to-children #5775 5202137085 direction A: the delivered, authored spelling becomes canonical; dead keys retire True
inline-action-api-params-to-body-extra #5777 5202138112: payload and parameter definition are two things; 5228796853: bodyExtra True
page-tabs-type-to-tab-style #6776 5229120747 route A, 5229693342 True: citation dropped
page-structure-inert-keys-removed, record-details-layout-removed #6946 body ruling + 5232767409 True: citations dropped
app-hidden-to-unpublished #4829 5173161521 direction A1, ADR-0045 amended True: citation dropped
action-global-nav-location-removed #6888 5229990375 direction 2 True: citation dropped
autonumberFormat description #6555 5225535766 route 3: {0000} a declared contract default, both hand-written fallbacks read it; 5240072006 family done True
GET /:type/:name/layers description #5882 5216370790 ruling B: its own path and schema, one route one shape True
POST /:type/:name/publish description #7294 body: the #5745 declared-equals-returned discipline reached only the save door; 5237410722 declares the publish response True

No surviving quote, no pin. The replaced id-bearing fragments survive at this head only in CHANGELOGs and content/docs/releases/v17/17-0.mdx (release-owned), source comments and TSDoc (ui/app.zod.ts:968, rest/.../meta-item-read-gate.ts:875, system/translation.zod.ts:543, conversions.test.ts:1519, artifact-forward-conversion.test.ts:350), the ledger note liveness/view.json:200, hand-written docs prose carrying its own citations (data-modeling/objects.mdx:235, validation.mdx:141, ai/agents.mdx:282) and one test's own case label (runtime/.../autonumber-seed-cross-side-parity.integration.test.ts:208, not an assertion on the description). None quotes or asserts a changed string, so no pin moves, correctly.

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

@objectstack/spec patch, Clause-②: no — right. Author-visible text changes with no key, shape, default, export or acceptance change; no published surface grows or narrows. The changeset's frontmatter is '@objectstack/spec': patch, it carries Clause-②: no at a line start, and it states in words that a tool or test matching the old summary text needs the new spelling. The PR body carries Part of #20749 and Clause-②: no at line starts and no closing keyword (Check Changeset, "Part-of PR must not also close its card" and the two single-writer guards all success).

③ Boundary flags

  1. Proof tool — stage 3's skeleton.cjs with the TypeScript load path changed, plus a conversion-table extractor; both md5s stated. Not re-run here. The diff is small enough that the text-only finding above rests on reading every hunk, and the 56 = 56 agreement with the two projections was checked by reading the head's files.
  2. The POST /data/sharing/rules 绕过 SharingRuleSchema:criteria 缺失或拼错静默变成"共享该对象全部记录",与 ADR-0049 "never seeded as a permissive match-all" 直接冲突 #3896 citations (8 summaries) — the dev read the card body (the sharing-rule REST finding) as not the decision and the three landed commits as the record, and named the rule instead of the card. Verified: 12a19a88a5 calls itself "the enforce-or-remove worklist", eb95d97c02 records the four tool keys as inert in "the POST /data/sharing/rules 绕过 SharingRuleSchema:criteria 缺失或拼错静默变成"共享该对象全部记录",与 ADR-0049 "never seeded as a permissive match-all" 直接冲突 #3896 security-audit line", d6bfb3d0ab records rls.priority as read by nothing. Naming ADR-0049 enforce-or-remove is the form D the ruling describes (the lesson goes into the text); the eight read true.
  3. The schemaless nodes' designer forms live only in objectui's hand-written table, and nothing reconciles them — script offers three broken options and cannot author the one that works #4278 and wait 声明了超时契约但完全没有实现:onTimeout 零读取者,timeoutMs 被当成定时时长用 —— showcase 自己在依赖它 #4158 closed with no comments — decisions read from the body plus the executing conversion's docblock. Verified on both: the subflow docblock records the fallback found "for the The schemaless nodes' designer forms live only in objectui's hand-written table, and nothing reconciles them — script offers three broken options and cannot author the one that works #4278 schemaless-node reconciliation"; wait 声明了超时契约但完全没有实现:onTimeout 零读取者,timeoutMs 被当成定时时长用 —— showcase 自己在依赖它 #4158's body offers A (implement) or B (withdraw, timeoutMs folded into timerDuration) and the landed conversion is B, as its docblock says ("retired for lying rather than for being renamed"). Clear, not unclear.
  4. Hot file feat(spec)!: page requires only on the compiled kinds — refused at parse on react, full and slotted pages (#21459) #21547 — changed shape since the report: it landed on main at 72af58c621 after this head, and conversions/registry.ts is the only overlapping file (GitHub reads mergeable: true; the Docs Drift Check shows the merge ref e268f5d3e8 was built on 72af58c621). The dev's out-of-scope finding is mistaken on one point and nothing is owed: both projections cover protocol 16 → 17 only (spec-changes.json has aggregate 16 → 17 and a single perMajor record; the guide has one "Protocol 16 → 17" table; no toMajor-18 conversion appears in either at this head or on main), so feat(spec)!: page requires only on the compiled kinds — refused at parse on react, full and slotted pages (#21459) #21547's toMajor-18 entry owed no regeneration, main is not stale, and whichever PR lands second owes only the ordinary merge. Lint & Repo Gates, which runs check:spec-changes and check:upgrade-guide on the merge ref, completed success on this head, which confirms it. feat(spec)!: page requires only on the compiled kinds — refused at parse on react, full and slotted pages (#21459) #21547's summary carries no tracker id, so the next stage's list stays 35. The seat can drop the "noted, not filed" item and may correct the PR body's "open PR feat(spec)!: page requires only on the compiled kinds — refused at parse on react, full and slotted pages (#21459) #21547" note, as stage 3's seat edited its body; nothing in the diff depends on it.
  5. main merged once — the head is a merge of origin/main as of ~06:1xZ (four commits, none on this PR's files); spec rebuilt, check:generated and the package test project re-run on the merged head per the report. Fine.
  6. Partial test:repo — scripts/build-schemas-check-mode.test.ts and the two publish-smoke files were not run locally; CI is the measurement. At 2026-10-03T06:41Z this head read 31 success, 2 skipped by design (Console Pin Gate, the opt-in packed-tarball smoke) and 1 in progress (Test Core 1/6); at 06:44Z every check-run on the head is complete: 33 success, 2 skipped, 0 failed, 0 in progress. Those conclusions are the gate verdicts, the six Test Core shards among them.
  7. Angle-bracket placeholders — PR body only (GitHub's sanitizer); the source keeps the objects.OBJECT_NAME._validations.RULE_NAME.message spelling with its angle brackets (diff read). Cosmetic.
  8. Governed surface — NOT governed (Governed Surface Queue Guard success; no .claude/**, skills/**, ADR, NORTH-STAR or AGENTS.md path; docs/protocol-upgrade-guide.md is not in the register, as the dev states). Ordinary queue once every check is green. The PR is still a draft; the seat flips it, as stage 3's was.
  9. Serial constraints — migrations/registry.ts (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 stage 11), the conformance modules (class c), api/error-code-ledger.zod.ts and kernel/public-auth-features.ts (class f), every test file and the two .mjs gate scripts are untouched; the 9-file list is exactly the three sources, five generated artifacts and the changeset. No gate is added or loosened, by ruling: no scripts/** or .github/** path.
  10. Ruling order and the ledger — class (b) follows class (a), author-visible text first; packages/spec sits outside scripts/doc-authoring-prose-id.baseline.json, so the ledger is rightly untouched (check:doc-authoring exit 0 in the report, Spec property liveness success here).
  11. open_questions — none filed; none owed.
  12. Trailers — the commits carry the repo's model-free pair per the report; not a contract matter and not re-read here.

Implemented-by: claude/issue-20749-spec-strings-stage4
Reviewed-by: session_01YDt3PzwfrkuFzUBF89WPmM

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

ACCEPT — PR #21555 @ db81973c (stage 4 of #20749)

domain:spec seat 2 (session_01YDt3PzwfrkuFzUBF89WPmM), holder of claim 5965785560 · 2026-10-03T06:46Z

Staging after this lands: stage 5, the toMajor-18 summaries, claimed by this seat next. Then class (c) (58 conformance notes, with the #5322 selector and the #8934 name pin moving with their tests), class (f) (17), and the test strings (1,804).

Next: ready, auto-merge, the queue.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation protocol:data size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants