You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Forward conversion never opens on unreleased main: spec still labelled 17.4.0 while main refuses 17.5.0 retirements, so artifacts built by the published 17.4.0 CLI are refused #20390
This surfaced on objectstack-ai/hotcrm#1964 (floor 1 of the cloud framework re-cut, objectstack-ai/cloud#2332 / PR objectstack-ai/cloud#2423) and is tracked under epic objectstack-ai/cloud#2440.
Maintainer ruling in the epic session, 2026-09-28 (verbatim): 「平台修转换 + HotCRM 先删 scale(推荐)」. The platform fixes the forward conversion. hotcrm only removes what no release can convert.
What breaks
objectstack main (measured at 862b6ce8) already refuses keys that the retirement ledger marks "removed in @objectstack/spec 17.5.0". But packages/spec on main still carries the version label 17.4.0, and 17.5.x is not published (npm latest = 17.4.0).
applyArtifactForwardConversions (packages/metadata-core/src/artifact-forward-conversion.ts ~301, resolveInstalledSpecVersion) therefore reads runtime 17.4.0 == the artifact's authored floor ^17.4.0 and returns authored-current. The retired conversions are then not replayed, and MetadataPlugin._parseAndRegisterArtifact (packages/metadata/src/plugin.ts ~894) refuses the artifact.
Consequence: any artifact built by the published@objectstack/cli 17.4.0 that contains a dashboard chart cannot boot on a runtime that runs unreleased main. At 17.4.0, chartConfig.type is required, and main refuses it. Such a runtime is the EE composed boot through OS_COMPOSED_ARTIFACT_URL, as cloud does when it pins a framework SHA. No source spelling of chartConfig is legal on both versions short of deleting the block.
Measured (hotcrm#1964 dev report, 2026-09-28)
Probe: the same ObjectStackDefinitionSchema.parse(applyArtifactForwardConversions(...)) from objectstack 862b6ce dist, run on hotcrm main's artifact:
only the 23 currency scale issues (no conversion exists; hotcrm is removing them)
Direct EE boot of the same artifact on main: exit 1. With all three key families stripped: serves (HTTP 200).
Direction (for triage and the owning lane)
Make the conversion window open for artifacts authored at the last published release whenever the runtime already enforces a later retirement. The dev named two shapes; picking one is the owning lane's design call:
Option a.main carries a prerelease label (e.g. 17.5.0-next) as soon as it contains a 17.5 retirement.
Option b. The window compares the artifact's authored version against the retirement's own version instead of the package label.
Acceptance
An artifact built by the published 17.4.0 CLI with dashboard charts and page assignedProfiles boots on main, and the conversion notices are logged.
Newly authored sources that use the retired keys are still refused loudly.
A regression test pins the "unreleased main, artifact at last release" case.
Provenance: the maintainer answered this decision card's question in the epic session for objectstack-ai/cloud#2440 (session 786273a0-0246-4bb2-b026-bb37ba5d7295), on 2026-09-28. The question relayed the card's six options with its recommendation. It was shown right after the domain:engine#1 decision comment above.
Ruling (maintainer, verbatim): 「A:登记表加必填退役版本(推荐)」
As the card states it, applied as written:
MetadataConversion gains a required per-entry retirement version, e.g. retiredAfter, whenever retiredFromLoadPath is true.
Backfill the 73 published retired entries mechanically from the published tarballs, and stamp the 18 unreleased entries 17.4.0.
The metadata-core window uses the dev's union rule: replay entry E when the floor is below the runtime label, or when the floor is ≤ E.retiredAfter.
The strict per-entry narrowing is not ruled; it would be its own decision on named evidence.
Not chosen here: publishing 17.5.0 as a same-day unlock (option C). The maintainer picked A without it.
Next step, per the card's own 「裁后执行」. Re-route this card through triage to the spec seat as a Seam: card: the spec field, the backfill and the metadata-core window go in one vertical PR, and p1 stays. The needs-user-decision → pm:queue + pm:retriage transition is written with this comment.
Downstream waiting on this: objectstack-ai/cloud#2332, the framework re-cut behind PR objectstack-ai/cloud#2423.
Ruling: batch #235 item 1 · letter A · maintainer 「同意 A」 2026-09-28T08:01Z
Director seat (objectstack#12708, summon #30 续 2, session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-28, replying 「同意 A」 to batch #235 as presented — this card, recommendation A, fallback A′, the full four-axis body in chat.
Ruled: A. Every retired entry in the ADR-0087 conversion registry carries a REQUIRED retiredAfter: 'x.y.z' — the last published @objectstack/spec version whose authoring surface still accepted the old shape — and the artifact forward-conversion window decides per entry: entry E is replayed when the artifact's declared floor is below the runtime label OR at or below E.retiredAfter; DEFAULT_FLIPS_NOT_REPLAYED_HERE is still read first. A′ (the field optional, set on the 18 unreleased entries only), B (a spec-level "enforced through" constant), D (a second table in metadata-core) and E (< → ≤) are not taken. C — publishing 17.5.0 — is not a letter: it is the maintainer's own release act, available in parallel, and it does not replace A.
Readings that decided it:
Long-term: the fact the window needs lives on the entry it describes, and the registry stays the single authority (artifact-forward-conversion.ts:90, "Not a second conversion table"). Any label-derived window reopens this gap every release cycle, because main's label lags its content by design. Mainstream (from memory): Rails and Django migrations carry their own version, and Kubernetes converts per apiVersion — the effective version is written on the rule, not derived from the build label.
Real use: p1 — the cloud framework re-cut (cloud#2440, hotcrm#1964) is blocked; 63 issues measured, 44 conversions apply at a forced 17.5.0.
AI-safety: a REQUIRED field makes tsc refuse a retirement that omits its version; A′ brings the defect back silently for any entry that omits it; E converts silently at the boundary the policy says must refuse (:43, "a key retired at version V stays a loud refusal for anything authored at ≥ V").
Startup scope: one field and a one-time mechanical backfill, no new gate; B adds a second version label someone must remember to bump; D a second table that drifts.
Execution parameters (ruled in the same stroke):
Route: this card goes to triage as a Seam: card and is re-routed to domain:spec for ONE vertical PR (spec field + backfill + census test, and the metadata-core window in the same PR); priority:p1 stays. The dev report on this card already holds the metadata-core half.
Spec half:MetadataConversion gains retiredAfter (required); the 73 published entries are backfilled from the published tarballs (the dev's census: 50 first retired in 17.0.0, 5 in 17.1.0, 2 in 17.2.0, 8 in 17.3.0, 8 in 17.4.0 — each entry's value is the last version BEFORE its first retirement); the 18 unreleased entries carry 17.4.0. A census test pins every published entry's value against the tarballs, and pins that every entry absent from the last published tarball carries the current package.json label — so a retirement landing after a release bump cannot be stamped low. gen:api-surface regenerated. Clause-②: yes, minor with a BREAKING note (a published type gains a required member; out-of-repo implementers presumed zero).
metadata-core half: the per-entry union rule above, ~30 lines plus tests; the module docblock's policy sentences stay true.
Pins: (1) an artifact built by the published 17.4.0 CLI with dashboard charts and page assignedProfiles boots on main and logs the conversion notices; (2) newly authored sources using the retired keys are still refused loudly; (3) an artifact whose floor is exactly 17.5.0 on a 17.5.0-labelled runtime is refused, not converted; (4) the "unreleased main, artifact at last release" regression case from the card's acceptance.
C in parallel: merging the open release PR chore: version packages #17076 (17.5.0) unblocks cloud the same day by the label alone; it is the maintainer's act and changes nothing above.
State: needs-user-decision → pm:retriage (triage re-routes to domain:spec, then pm:queue); the Ruled line goes on the body in the same stroke.
Director seat (objectstack#12708, summon #30 续 2). The maintainer, on ruling 5865890672 (A), asked in chat why this waits on triage when the lane is already fixed by the ruling — verbatim 「你可以直接处理」. Done in this act: the card is domain:spec, pm:queue, priority:p1, for one vertical PR (spec retiredAfter + backfill + census test, and the metadata-core window in the same PR). The dev report on this card holds the metadata-core half; the branch claude/issue-20390-forward-conversion-window is empty and free for the taker.
Claim: PM loop round 2
Session: session_01ARcDurZ5j34RdqsGgc4jgH
Account: os-warren (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-20390-forward-conversion-window (the empty branch the director left free, 5866178043)
Worktree: objectstack-issue-20390
Domain: domain:spec
Seat: domain:spec#4 (seat post #18917)
File surface: ONE vertical PR per ruling 5865890672 (letter A), execution parameters, both halves:
spec half:
packages/spec/src/conversions/types.ts: MetadataConversion gains a required retiredAfter.
packages/spec/src/conversions/registry.ts and the conversion entry files it assembles: a retiredAfter value on every retired entry.
A census test in packages/spec that pins each value against the published tarballs.
packages/spec/api-surface/**, regenerated by gen:api-surface, and any other generated artifact the tools move, never by hand.
metadata-core half:packages/metadata-core/src/artifact-forward-conversion.ts, the window decision only (the per-entry union rule), plus tests in packages/metadata-core and packages/metadata. The artifact door is test side unless the door itself must change.
.changeset/.
⛔ Not packages/spec/package.json's version label, nor any release or prerelease label: the release PR chore: version packages #17076 is the maintainer's act.
⛔ Not the membership of DEFAULT_FLIPS_NOT_REPLAYED_HERE: it is still read first.
⛔ Not the conversions' apply bodies.
(stop on breach; explain in the report)
Container & model: M, mode:subagent, model: default judgment tier (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable). Clause-②: yes, as the ruling says, so an at-tier contract review is owed before enqueue.
Clause-②: yes
Thread-read: 5866178043
Serial constraints cleared: read at 2026-09-28T09:24Z on origin/main7db1332f.
{
"issue": 20390,
"status": "done",
"branch": "claude/issue-20390-forward-conversion-window",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20435",
"session": "session_01ARcDurZ5j34RdqsGgc4jgH — the dispatching seat's session; this run is mode:subagent under it (the Claude-Session line on every commit of the branch)",
"premise_still_valid": true,
"summary": "Ruling 5865890672 (letter A) is implemented in one vertical PR, draft #20435, head 87da6b88. Spec: MetadataConversion is now a live-or-retired union, so retiredFromLoadPath true requires retiredAfter, typed as a stable x.y.z template-literal string, and tsc refuses an unstamped retirement (TS2322). All 94 retired entries are stamped. The 73 published ones come from the published tarballs: 15.1.0=5 (15.0.0), 17.0.0=45 (16.1.0), 17.1.0=5, 17.2.0=2, 17.3.0=8, 17.4.0=8. The 21 unpublished ones carry the label 17.4.0, including flow-decision-mode-inclusive-explicit from #20344, merged in. A committed census (retired-after.census.json) and an offline census test pin every value. scripts/build-retired-after-census.ts re-derives the census from npm; its --check passes today. gen:api-surface and gen:export-origins are regenerated: MetadataConversion (interface) becomes (type). metadata-core: applyArtifactForwardConversions replays entry E when floor is below the runtime label OR floor is at or below E.retiredAfter. DEFAULT_FLIPS_NOT_REPLAYED_HERE is still read first, and its membership is untouched. The new verdict is 'converted-retired-after'. Pins (1) to (4) are green on a REAL artifact built by the published @objectstack/cli 17.4.0. Label and npm latest were both 17.4.0 at the final check, so the census is against 17.4.0.",
"tests": "HEAD 87da6b88 (the union ran after the final commit). GATES: dispatch-gates --commands derived 88 commands and all 88 exit 0 on 87da6b88. `dispatch-gates --ran` with per-line exit codes printed 'Run reconciliation — 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ ... 0 NOT-MEASURED (a DERIVED zero — all 88 recorded an exit code and none of them is 3)'. Four gates first answered PREREQUISITE NOT MET (exit 3): lint check:doc-formula-expressions, check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt. After a full packages build (turbo, 71/71 successful, under the verify lock) all four were re-run, and the whole union was re-run on 87da6b88. TESTS, run at b9a07ea3; the last merge, 87da6b88, has an empty diff under packages/spec, packages/metadata-core and packages/metadata. spec `vitest run --project local`: 'Test Files 564 passed (564) / Tests 16604 passed | 1 todo (16605)'. spec `--project repo`: 'Test Files 36 passed (36) / Tests 655 passed (655)'. metadata-core: 'Test Files 16 passed (16) / Tests 295 passed (295)'. metadata: 'Test Files 55 passed (55) / Tests 825 passed (825)'. Typecheck: spec, metadata-core and metadata each exit 0 (spec: tsc + scripts + test-typecheck 'OK ... held'). `--listFiles` counts each new test file once in its package's type program. check:generated: 'All 15 generated artifacts are up to date'. eslint --no-inline-config --format json on the 8 changed source files: 8 linted, 0 errors, 0 warnings; the config enables no type-aware linting. ABLATION, from committed state, through scripts/ablation-replace.mjs in WRAP mode, with a bash trap restoring from HEAD. The anchor 'const closed = idsTheFloorPostdates(floor);' became \"const closed = floor && 'ABLATION-20390-LABEL-ONLY' ? null : idsTheFloorPostdates(floor);\" (the old label-only verdict). On disk: anchor 1 → 0, blob 5daaa45c → eed68e51. metadata-core was rebuilt, and ablation-dist-preflight read '✓ dist/: marker present in 2 built files'. Mutated: metadata-core 'Tests 3 failed | 32 passed (35)' — pin (4), the older-retirement companion and the default-flip per-entry companion are red; pin (3), the premise and the post-release companion are green. metadata file 'Tests 2 failed | 2 passed (4)' — pin (1) boot and pin (1) notices are red; pin (2) and the premise guard are green. Restore: blob after restore 5daaa45c equals HEAD, `git diff HEAD` is empty, porcelain has 0 lines, the rebuilt dist reads 'marker absent from all 12 built files' and 'working tree clean against HEAD', metadata-core is 35/35 and metadata 4/4. REVERSE VERIFICATION: with retiredAfter removed from page-assigned-profiles-removed, spec `tsc --noEmit` exits 2 with exactly 1 'error TS2322' on that entry. The first attempt was a no-op that ablation-replace refused (the replacement was a prefix of the anchor, so its count rose by 0), and it restored; the retry used a distinct replacement. The census test, mutated: published object-compactLayout-to-highlightFields stamped '14.8.0' reds the PUBLISHED test ('the tarballs say 15.0.0'); unpublished page-assigned-profiles-removed stamped '17.3.0' reds the UNPUBLISHED test. `build-retired-after-census.ts --check` exits 0 against npm, and exits 1 with 'census drift' when precedingRelease is tampered. Every mutation was restored to the HEAD blob with 0 porcelain lines. CI on 87da6b88 at report time: in_progress (32 check runs: 17 in_progress, 12 success, 3 skipped, 0 failed).",
"mcp_calls": "0",
"api_writes": "3 — each one POST /repos/objectstack-ai/objectstack/dispatches through the fleet relay, executed as objectstack-fleet[bot]: (1) pr_create, which became POST /repos/objectstack-ai/objectstack/pulls, draft #20435, relay run 36418356626; (2) label-write --assign os-warren, which became POST /repos/objectstack-ai/objectstack/issues/20435/assignees, relay run 36418507779, read back as a match; (3) this os-dev-report comment via post-stamped, which becomes POST /repos/objectstack-ai/objectstack/issues/20390/comments. git push is not REST. The reads were REST GETs of the card, its comments, the PR and its check runs, plus npm registry GETs.",
"gates": "88/88 derived gates exit 0 on HEAD 87da6b88; dispatch-gates --ran: 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero, since every line carries an exit code). CI on the PR: in_progress at report time.",
"line_budget": "n/a — no skills/** or other line-ratcheted file touched. The diff is 13 files, +1480/−49 (dispatch-gates at 87da6b88), under the 5000-line human-merge threshold.",
"files_changed": [
".changeset/20390-conversion-retired-after-window.md",
"packages/metadata-core/src/artifact-forward-conversion.test.ts",
"packages/metadata-core/src/artifact-forward-conversion.ts",
"packages/metadata/src/__fixtures__/forward-probe-17.4-built.artifact.json",
"packages/metadata/src/plugin-artifact-forward-conversion-retired-after.test.ts",
"packages/spec/api-surface/root.json",
"packages/spec/export-origins/root.json",
"packages/spec/scripts/build-retired-after-census.ts",
"packages/spec/src/conversions/apply.ts",
"packages/spec/src/conversions/registry.ts",
"packages/spec/src/conversions/retired-after.census.json",
"packages/spec/src/conversions/retired-after.census.test.ts",
"packages/spec/src/conversions/types.ts"
],
"deviations": [
"Unpublished-entry rule, one tolerance. Read literally, 'every entry absent from the last published tarball carries the current package.json label' turns the Version Packages PR red: that PR bumps the label to 17.5.0 before 17.5.0 is published, while the 17.5.0 cohort correctly carries 17.4.0. So while the label is ahead of the census's last release, the test accepts any value from that release up to the label, and it demands the exact label whenever the two are equal (true today). The tolerance closes once the census records the new tarball.",
"Network half. PM mechanism assumption 5 is falsified: the pin against the tarballs is scripts/build-retired-after-census.ts (write / --check), not a repo-tier test. vitest.repo-tests.json is held EQUAL to the set of tests that read outside the package by check:cross-package-test-inputs ('lists file(s) that do not read outside the package' reds), so a network-only test cannot be listed there. Reading the tarballs also means about 11 downloads totalling over 250 MB per run. CI pins the committed census offline. The script is loud when offline and never skips. It is not a package.json script and not wired into CI, so no gate is added.",
"Census counts differ from the ruling's, and the ruling's principle decides them: 94 retired = 73 published + 21 unpublished. The ruling's 17.0.0 bucket of 50 is 45 + 5: five entries (object-compactLayout-to-highlightFields, stack-roles-to-positions, owd-legacy-read-aliases, sharing-recipient-role-to-position, book-audience-profile-to-permission-set) are already retired in the 15.1.0, 15.1.1, 16.0.0 and 16.1.0 tarballs, so they carry 15.0.0. The engine seat's census had started at 17.0.0.",
"Stable releases only: the census and the rule skip the -rc versions, because a caret floor never names a prerelease and the door compares x.y.z triples.",
"File surface: packages/spec/src/conversions/apply.ts had one docblock sentence edited so it stays true (the door opens its window per entry with retiredAfter). The real 17.4.0-built fixture uses manifest id com.example.forward-probe, because main now refuses an underscore in manifest.id (#17534, a documented narrowing), which is unrelated to this window.",
"Changeset: @objectstack/spec minor and @objectstack/metadata-core minor. metadata-core earns minor because a published union type gains a verdict member. The changeset carries a BREAKING banner, 'Clause-②: yes' and the ADR-0087 disposition not-required (runtime-interface-only packages/spec/src/conversions/types.ts#MetadataConversion), which check-adr-0087-registration verified.",
"Main was merged three times through scripts/pm/os-regen-merge.sh. The #20344 merge had one conflict, in the metadata-core test file, where both sides added a describe block; both were kept. The regeneration discharge came after, and check:generated then reported all 15 up to date."
],
"open_questions": [
{
"question": "Nothing forces a census refresh after a stable publish. Until the refresh, the unpublished-entry rule stays in its release-pending tolerance, so an entry stamped low but not below the last censused release passes. How is the refresh triggered?",
"options": [
"A. One line in the release checklist (docs/releases-maintenance.md): after a stable @objectstack/spec publish, run build-retired-after-census.ts and commit. No gate is added.",
"B. Nothing. Accept the tolerance and refresh only when someone notices.",
"C. A scheduled network --check workflow. This is a new gate, and the startup-stage axis defaults it to no without the maintainer naming it."
],
"recommendation": "A, on the four axes. Real need: the window is only exact when the census is current, and a low stamp recreates this card's defect for that entry. Long-term soundness: the refresh is a mechanical publish-time step beside the one the release workflow already runs against the previous tarball. AI-proofing: a refreshed census turns a low stamp into a red test. Startup scope: one checklist line and no new gate, where C would add one."
}
],
"out_of_scope_findings": [
"carrier: 承接者:无 · noted, not filed. packages/metadata/src/plugin.ts: the door's warn line under the new 'converted-retired-after' verdict reads 'predates this runtime's spec (authored engines.protocol floor 17.4.0, runtime spec 17.4.0)'. It is accurate, since the runtime's content is newer than its label, but it can confuse an operator. Wording only. Dedupe words: predates this runtime's spec, converted-retired-after, artifact conversion warn.",
"carrier: 承接者:无 · noted, not filed. ADR-0087 D2 (docs/adr/0087, the retiredFromLoadPath paragraph) does not describe retiredAfter or the per-entry artifact window. An ADR-0087 note may be owed, but docs/adr/** is fenced for this PR. Dedupe words: retiredAfter, ADR-0087 D2, artifact forward conversion window, per-entry.",
"carrier: 承接者:无 · noted, not filed. .claude/skills/spec-property-retirement/SKILL.md lists 'retiredFromLoadPath: true' in its retirement kit without retiredAfter. tsc now refuses the omission, so the playbook is only behind. It is a governed surface (Tier S). Dedupe words: spec-property-retirement, retiredAfter, retirement kit."
]
}
REWORK round 1 (from the at-tier record on the PR: FAIL) — PR #20435 at head 87da6b88 · domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH) · 2026-09-28T12:14Z
The at-tier contract review FAILED at this head. Its record is on the PR as 5869622844. It passes every execution parameter of ruling 5865890672, (i)–(vii), and it cross-checks all 94 stamps against the committed census with zero mismatches. It fails on one in-tree consumer the new verdict left behind. The seat re-read that consumer on the branch: packages/metadata/src/plugin.ts:830 still reads if (result.verdict !== 'converted-forward' && result.verdict !== 'converted-undeclared') return;. The finding is accepted.
Owed in this round (new commits only; ⛔ no rebase, amend or force-push):
The changeset and the PR body drop 「The door's operator log lines are unchanged」 and name the consumer arm added.
In the same plugin.ts touch, amend the warn line under 'converted-retired-after' so it names what is replayed: the retirements this runtime enforces past the artifact's floor. The current wording, 「predates this runtime's spec … runtime spec 17.4.0」, reads as a contradiction.
The seat's answer to your open_questions entry: A. Add one line to docs/releases-maintenance.md, in the GA release flow (the Version Packages PR section): after a stable @objectstack/spec publish, run scripts/build-retired-after-census.ts and commit the refreshed census. No workflow and no gate (option C is refused on the startup-stage axis, and B leaves the defect this card fixes open for the next retirement). This is a verification-strategy step, a non-escalation class. The file is not governed. Add it to this PR's file surface.
The seat's readings on the two deviations (the record judged both to keep the ruling's intent):
(a) The release-pending tolerance is confirmed. The ruling's literal rule would turn the maintainer's Version Packages PR red. The tolerance is exactly [last censused release, label] and is open only while the label is ahead. The refresh step in item 5 closes the residual.
(b) The network half, build-retired-after-census.ts --check plus the committed census, is accepted as the ruling's 「census test … against the tarballs」.
Not owed here (the seat files them at landing, as governed-surface follow-ups): ADR-0087's #12772 addendum sentence 「floor at or above runtime replays nothing」, and .claude/skills/spec-property-retirement/SKILL.md's retirement kit.
Verification: re-run the dispatch-gates --commands union on the final head and reconcile it with --ran. Run spec (local + repo), @objectstack/metadata-core and @objectstack/metadata. Merge main with bash scripts/pm/os-regen-merge.sh if it has moved. Writes: git push, one os-dev-report comment on this card, and one PR-body update via scripts/pm/ (relay pr_patch or the tool the relay offers). No label or assignee write. No MCP write.
{
"issue": 20390,
"status": "done",
"branch": "claude/issue-20390-forward-conversion-window",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20435",
"session": "session_01ARcDurZ5j34RdqsGgc4jgH — the dispatching seat's session; this run is mode:subagent under it (the Claude-Session line on every commit of the branch)",
"premise_still_valid": true,
"summary": "REWORK round 1 per order 5869635456 (at-tier record 5869622844, FAIL on one consumer). All five items are done in new commits: 874a4e74 and a1dc7d33, plus two os-regen merges, df46d5b8 and 2c537b7e. There was no rebase, amend or force-push. (1) plugin.ts: the verdicts that open the window are now one total table, FORWARD_WINDOW_OPENED, a Readonly Record keyed by every ArtifactForwardConversionVerdict member and valued boolean, with 'converted-retired-after' on the open side. _warnUnboundFormPredicateRoots returns on !FORWARD_WINDOW_OPENED[result.verdict]. The form chosen is 'add the arm', spelled as a total table: the old hand-written guard defaults a future verdict to closed, which is how this defect arose; the inverted guard defaults it to open and also admits 'not-an-object'; a total Record has no default, so tsc refuses the next verdict until it is placed (reverse-verified with TS2741). The docblock sentence 'read off that pass's own verdict … never disagree' holds again, and the #12915 notice no longer depends on the label. (2) A new pin beside pin (1): the ^17.4.0 fixture with one bare-root form predicate, on the 17.4.0 runtime, logs the #12915 line exactly once, across two ingestions. It is red under the old guard and green now. (3) The changeset drops 'The door's operator log lines are unchanged' and names the arm added; it also gains '@objectstack/metadata': patch, because plugin.ts now changes. The PR body names it too. (4) Under 'converted-retired-after', the conversion summary names the retirement this runtime enforces past the artifact's floor, with the release that last accepted the shape. That fact comes from a new metadata-core result field, replayedRetirements (element type ArtifactReplayedRetirement), so the door does not recompute it. The #12915 notice opens with the same verdict-aware clause, and every other verdict keeps its old wording. (5) docs/releases-maintenance.md, GA Version Packages PR flow: after a stable @objectstack/spec publish, run scripts/build-retired-after-census.ts and commit the refreshed census. One consequence: the existing 'says NOTHING about an artifact authored against the current surface' pin had derived that surface as a caret range on the installed label, which is exactly the label-dependence removed here. It now derives the first x.y.z past both the label and every retiredAfter. Label and npm latest are still 17.4.0, and no new retired entry arrived with the merges (94 stamped).",
"tests": "Final HEAD 2c537b7e. GATES: dispatch-gates --commands derived 90 commands at 2c537b7e (16 files, +1667/−72) and all 90 exit 0. dispatch-gates --ran with per-line exit codes: 'Run reconciliation — 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ … 0 NOT-MEASURED (a DERIVED zero — all 90 recorded an exit code and none of them is 3)'. A full packages build ran first (turbo, 71/71 successful, under the verify lock). TESTS at 2c537b7e: spec --project local 'Test Files 565 passed (565) / Tests 16645 passed | 1 todo (16646)'. spec --project repo: one full run hit the 570s timeout (exit 124) under box contention and was rerun as two halves of vitest.repo-tests.json, 'Test Files 18 passed (18) / Tests 460 passed (460)' and 'Test Files 18 passed (18) / Tests 195 passed (195)', together 36 files and 655 tests. metadata-core 'Test Files 16 passed (16) / Tests 295 passed (295)'. metadata 'Test Files 55 passed (55) / Tests 826 passed (826)'. Typecheck: metadata-core exit 0. metadata first exited 2 (TS2550, Array.at in the new test helper under the package lib); a1dc7d33 fixed it and it then exits 0. eslint --no-inline-config --format json on the 10 changed source files: 10 linted, 0 errors, 0 warnings. ABLATION, from committed state a1dc7d33, through scripts/ablation-replace.mjs in WRAP mode, with a bash trap restoring from HEAD by absolute path. Anchor 'if (!FORWARD_WINDOW_OPENED[result.verdict]) return;' became \"if (result.verdict !== 'converted-forward' && result.verdict !== 'converted-undeclared') return; // ABLATION-20390-OLD-GUARD\". On disk: marker 1, new guard 0, blob 8f43972c → b64fd125. Mutated, over the three door suites (plugin-artifact-forward-conversion-retired-after, plugin-unbound-form-predicate-roots, plugin-artifact-forward-conversion): 'Tests 1 failed | 20 passed (21)'; the one red is 'announces a bare-root form predicate once — the #12915 notice follows the per-entry window', and every other test, including the updated current-surface silence pin, stays green. Restore: blob after restore 8f43972c equals HEAD, `git diff HEAD` has 0 lines, porcelain has 0 lines, marker 0 and new guard 1, 'Test Files 3 passed (3) / Tests 21 passed (21)'. The suites import plugin.ts from source, so no build sits between the mutation and the run. REVERSE VERIFICATION: with the \"'converted-retired-after': true\" row dropped from FORWARD_WINDOW_OPENED, packages/metadata `tsc --noEmit` exits 2 with 'error TS2741: Property '\"converted-retired-after\"' is missing …'. Restored to the HEAD blob, 0 porcelain lines. The round-0 evidence stands: window ablation, the TS2322 on an unstamped retirement, the census negatives and the census --check against npm. CI on 2c537b7e at report time: 42 check runs, 33 success, 5 skipped, 4 in_progress, 0 failed.",
"mcp_calls": "0",
"api_writes": "2 this round — each one POST /repos/objectstack-ai/objectstack/dispatches through the fleet relay, executed as objectstack-fleet[bot]: (1) issue_patch on #20435, which became PATCH /repos/objectstack-ai/objectstack/issues/20435 (the body), relay run 36428801663; the stored body was read back byte-identical, 14838 chars, and was confirmed unchanged since my read before the write; (2) this os-dev-report comment via post-stamped, which becomes POST /repos/objectstack-ai/objectstack/issues/20390/comments. No label or assignee write. git push is not REST. The reads were REST GETs of the order, the record, the PR and its check runs, plus npm registry GETs.",
"gates": "90/90 derived gates exit 0 on HEAD 2c537b7e; dispatch-gates --ran: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero, since every line carries an exit code). CI on the PR: 4 checks in_progress at report time, 0 failed.",
"line_budget": "n/a — no skills/** or other line-ratcheted file touched. The PR diff is 16 files, +1667/−72 (dispatch-gates at 2c537b7e), under the 5000-line human-merge threshold.",
"files_changed": [
".changeset/20390-conversion-retired-after-window.md",
"docs/releases-maintenance.md",
"packages/metadata-core/src/artifact-forward-conversion.test.ts",
"packages/metadata-core/src/artifact-forward-conversion.ts",
"packages/metadata/src/__fixtures__/forward-probe-17.4-built.artifact.json",
"packages/metadata/src/plugin-artifact-forward-conversion-retired-after.test.ts",
"packages/metadata/src/plugin-unbound-form-predicate-roots.test.ts",
"packages/metadata/src/plugin.ts",
"packages/spec/api-surface/root.json",
"packages/spec/export-origins/root.json",
"packages/spec/scripts/build-retired-after-census.ts",
"packages/spec/src/conversions/apply.ts",
"packages/spec/src/conversions/registry.ts",
"packages/spec/src/conversions/retired-after.census.json",
"packages/spec/src/conversions/retired-after.census.test.ts",
"packages/spec/src/conversions/types.ts"
],
"deviations": [
"Item 1: 'add the arm', spelled as a total Record over the verdict union (FORWARD_WINDOW_OPENED) rather than a longer hand-written disjunction. The order offered two forms, and this is the arm-adding one, made total so tsc places the next verdict. It is reverse-verified: dropping the row gives TS2741.",
"Item 4 needed a fact the door did not have (each replayed entry's retiredAfter), so metadata-core's result gains replayedRetirements, with the exported element type ArtifactReplayedRetirement. It stays inside the metadata-core minor. Reading it off the pass keeps the door from recomputing the window.",
"@objectstack/metadata now carries a patch changeset entry, because plugin.ts is changed. The at-tier record's 'no changeset owed' for packages/metadata predates this round.",
"The existing #12915 silence pin ('says NOTHING about an artifact authored against the current surface') had to change: it derived 'current' as a caret range on the installed label, which after item 1 names an artifact built BY the last release. It now derives the first x.y.z past both the label and every retiredAfter. Its intent (an artifact on the surface this runtime enforces gets no notice) is unchanged, and it stays green under both guards.",
"spec test:repo ran as two halves of vitest.repo-tests.json (18 + 18 files). A single run exceeded the 570s foreground budget under box contention (exit 124, 0 results read). It is not a narrowing: all 36 files ran.",
"Main was merged twice this round, via scripts/pm/os-regen-merge.sh (df46d5b8, 2c537b7e), with no conflicts; the script kept the branch's bytes of api-surface/export-origins root.json, and check:generated plus check:api-surface are green on the final head."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: the seat at landing (per order 5869635456) · noted, not filed by me. ADR-0087's #12772 addendum sentence that a floor at or above the runtime 'replays nothing' becomes false when this lands (Tier H, docs/adr/**). Dedupe words: ADR-0087 addendum, replays nothing, retiredAfter, per-entry window.",
"carrier: the seat at landing (per order 5869635456) · noted, not filed by me. The retirement kit in .claude/skills/spec-property-retirement/SKILL.md does not list retiredAfter; tsc refuses the omission (Tier S). Dedupe words: spec-property-retirement, retiredAfter, retirement kit."
]
}
{
"issue": 20390,
"status": "done",
"branch": "claude/issue-20390-forward-conversion-window",
"pr": "https://github.com/objectstack-ai/objectstack/pull/20435",
"session": "session_01ARcDurZ5j34RdqsGgc4jgH — the dispatching seat's session; this run is mode:subagent under it",
"premise_still_valid": true,
"summary": "Stamp lap before round-2 review. New head 59687e3f. origin/main was merged with scripts/pm/os-regen-merge.sh as 9cfc3122 (main 3cf64493, which carries 6e3e5462 #20357, 7fa3e3e0, 8cdbe0c6 and aeb0557f); the merge was clean, and the script kept the branch's api-surface/export-origins root.json bytes. On the merged tree exactly one retired entry lacked retiredAfter: view-list-tabs-removed (#20357). 7fa3e3e0, 8cdbe0c6, aeb0557f and 3cf64493 add no retired entry. Commit 59687e3f stamps it retiredAfter '17.4.0': it is in no published tarball, and the label and npm latest were both re-checked at 17.4.0. Census on 59687e3f: 95 retired = 73 published + 22 unpublished, all 22 at 17.4.0. retired-after.census.json is unchanged, because it holds tarball facts only and no new stable release exists; build-retired-after-census.ts --check against npm passes ('matches the published tarballs (11 releases)'). No round-1 work changed. main has since moved to b2855081 (#20414: a new data filter module plus the api-surface/export-origins data.json shards). It adds no retired entry, and a driverless merge-tree probe (bare --shared clone, no os-regen driver) of 59687e3f with b2855081 is clean (exit 0). I did not merge it, to avoid another full lap; the queue's merge ref carries it.",
"tests": "HEAD 59687e3f, after a full packages build under os-verify-lock (turbo 71/71 successful, 0 cached). spec --project local: 'Test Files 565 passed (565) / Tests 16644 passed | 1 todo (16645)'. spec --project repo, in two halves of vitest.repo-tests.json, which now lists 37 files: 'Test Files 18 passed (18) / Tests 460 passed (460)' + 'Test Files 19 passed (19) / Tests 224 passed (224)', so all 37 files and 684 tests. metadata-core: 'Test Files 16 passed (16) / Tests 295 passed (295)'. metadata: 'Test Files 55 passed (55) / Tests 826 passed (826)'. spec typecheck: exit 0 with 0 'error TS' lines (the merged tree's TS2322 on view-list-tabs-removed is gone); test-typecheck 'OK'. check:generated: 'All 15 generated artifacts are up to date'. check:api-surface: 'public API surface + factory signatures unchanged ✓'. GATES: dispatch-gates --commands derived 90 at 59687e3f (16 files, +1668/−72) and all 90 exit 0; --ran with per-line exit codes: 'Run reconciliation — 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ … a DERIVED zero'. CI on 59687e3f at report time: 35 check runs, 31 success, 3 skipped, 1 in_progress, 0 failed.",
"mcp_calls": "0",
"api_writes": "1 — this os-dev-report comment via scripts/pm/post-stamped.mjs on the fleet relay, which becomes POST /repos/objectstack-ai/objectstack/issues/20390/comments. No PR body, label or assignee write. git push is not REST. The reads were REST GETs of check runs and npm registry GETs.",
"gates": "90/90 derived gates exit 0 on HEAD 59687e3f; dispatch-gates --ran: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero).",
"line_budget": "n/a — no line-ratcheted file touched. The PR diff is 16 files, +1668/−72.",
"files_changed": [
"packages/spec/src/conversions/registry.ts (this lap: +1 line, view-list-tabs-removed retiredAfter '17.4.0')"
],
"deviations": [
"main moved to b2855081 after my merge at 3cf64493. I did not merge it: it adds no retired entry and touches none of this PR's files (its api-surface/export-origins changes are in the data.json shards; this PR moves root.json), and a driverless merge-tree probe of 59687e3f with b2855081 is clean."
],
"open_questions": [],
"out_of_scope_findings": []
}
Review: ACCEPT · PR #20435 at head 59687e3f2fbd435cbbab9b2df698cc3f88c6fc59 · 2026-09-28T15:10Z
domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), reviewer of record under claim 5867115315. The readings below were taken on GitHub or on the tree by this seat.
check
reading
Ruling
5865890672 (letter A; the maintainer's record is 5865873150). Every retired conversion entry carries a REQUIRED retiredAfter, and the artifact window decides per entry: replay E when the floor is below the label OR at or below E.retiredAfter. DEFAULT_FLIPS_NOT_REPLAYED_HERE is still read first. All seven execution parameters and the four pins are judged right on this head.
Shape
Draft, base main. Body line 1 Fixes #20390; Clause-②: yes stands alone.
Scope
16 files, +1668/−72, across packages/spec (the type, the 95 stamps, the census file, test and script, api-surface / export-origins root rows), packages/metadata-core (the window, a new verdict 'converted-retired-after', replayedRetirements), packages/metadata (the door's FORWARD_WINDOW_OPENED table, the #12915 pin, the 17.4.0-built fixture) and one line in docs/releases-maintenance.md.
Contract review
Round 0: FAIL5869622844 on 87da6b88. One consumer, _warnUnboundFormPredicateRoots, was left behind; the seat's rework order was 5869635456. Round 2: PASS5872777111, same head. It covers the five rework items, the new metadata-core surface, the re-derived silence pin, and the stamp lap. Census cross-check: 95 retired = 73 published + 22 unpublished, every one stamped, zero mismatches.
Seat answers
The release-pending tolerance is confirmed. The network half (build-retired-after-census.ts --check plus the committed census) is accepted as the ruling's census test. The census-refresh question is answered A: one line in the GA release flow, no gate.
Changeset
@objectstack/specminor with a BREAKING banner (a published type gains a required member); @objectstack/metadata-coreminor (a verdict member, a result member and its type); @objectstack/metadatapatch. ADR-0087 disposition not-required (runtime-interface-only …).
CI at this head
32 success, 3 skipped, all on the roster (check-expected-skips.mjs --pr 20435, exit 0). mergeable_state: clean. origin/mainfbeb56e4 adds no retired entry since the last merge (0 new retiredFromLoadPath: true lines), and git merge-tree is clean.
Governed / size
Not governed (check-governed-merges.mjs --pr 20435: 0 of 16 paths), 1740 changed lines.
Acceptance notes / follow-ups:
The changeset and PR body still count 「94 retired … 21 unpublished」, the 2c537b7e snapshot; at landing it is 95 / 22. This is prose only: no level, pin or accept set rests on it, and the census file and its test are the authority, a count every later retirement moves → Acceptance notes, carrier: none.
Landing next: ready + auto-merge through the relay. At MERGED, this card closes by Fixes. Downstream: objectstack-ai/cloud#2332 (the framework re-cut behind objectstack-ai/cloud#2423) was waiting on this.
This card was closed completed by the PR's Fixes line. pm:dispatched comes off in this act.
Ruling 5865890672 is executed: an artifact authored at the last published release (17.4.0) with retired shapes now boots on main with the conversions logged.
Downstream: objectstack-ai/cloud#2332 (the framework re-cut behind objectstack-ai/cloud#2423) was waiting on this.
From now on, every new retirement stamps retiredAfter, and tsc refuses one that does not. After each stable publish, the census refresh step in docs/releases-maintenance.md applies.
Activity
hotlong commentedon Sep 28, 2026
Maintainer ruling recorded: option A
Provenance: the maintainer answered this decision card's question in the epic session for objectstack-ai/cloud#2440 (session
786273a0-0246-4bb2-b026-bb37ba5d7295), on 2026-09-28. The question relayed the card's six options with its recommendation. It was shown right after thedomain:engine#1decision comment above.Ruling (maintainer, verbatim): 「A:登记表加必填退役版本(推荐)」
As the card states it, applied as written:
MetadataConversiongains a required per-entry retirement version, e.g.retiredAfter, wheneverretiredFromLoadPathis true.17.4.0.E.retiredAfter.Not chosen here: publishing 17.5.0 as a same-day unlock (option C). The maintainer picked A without it.
Next step, per the card's own 「裁后执行」. Re-route this card through triage to the spec seat as a
Seam:card: the spec field, the backfill and the metadata-core window go in one vertical PR, and p1 stays. Theneeds-user-decision→pm:queue+pm:retriagetransition is written with this comment.Downstream waiting on this: objectstack-ai/cloud#2332, the framework re-cut behind PR objectstack-ai/cloud#2423.
objectstack-fleet commentedon Sep 28, 2026
Ruling: batch #235 item 1 · letter A · maintainer 「同意 A」 2026-09-28T08:01Z
Director seat (objectstack#12708, summon #30 续 2,
session_01AsCNgFBs8HCjwhyHQsFbx3). Provenance: maintainer, live PM chat with the director seat, 2026-09-28, replying 「同意 A」 to batch #235 as presented — this card, recommendation A, fallback A′, the full four-axis body in chat.Ruled: A. Every retired entry in the ADR-0087 conversion registry carries a REQUIRED
retiredAfter: 'x.y.z'— the last published@objectstack/specversion whose authoring surface still accepted the old shape — and the artifact forward-conversion window decides per entry: entry E is replayed when the artifact's declared floor is below the runtime label OR at or belowE.retiredAfter;DEFAULT_FLIPS_NOT_REPLAYED_HEREis still read first. A′ (the field optional, set on the 18 unreleased entries only), B (a spec-level "enforced through" constant), D (a second table in metadata-core) and E (<→≤) are not taken. C — publishing 17.5.0 — is not a letter: it is the maintainer's own release act, available in parallel, and it does not replace A.Readings that decided it:
artifact-forward-conversion.ts:90, "Not a second conversion table"). Any label-derived window reopens this gap every release cycle, becausemain's label lags its content by design. Mainstream (from memory): Rails and Django migrations carry their own version, and Kubernetes converts perapiVersion— the effective version is written on the rule, not derived from the build label.tscrefuse a retirement that omits its version; A′ brings the defect back silently for any entry that omits it; E converts silently at the boundary the policy says must refuse (:43, "a key retired at version V stays a loud refusal for anything authored at ≥ V").Execution parameters (ruled in the same stroke):
Seam:card and is re-routed todomain:specfor ONE vertical PR (spec field + backfill + census test, and the metadata-core window in the same PR);priority:p1stays. The dev report on this card already holds the metadata-core half.MetadataConversiongainsretiredAfter(required); the 73 published entries are backfilled from the published tarballs (the dev's census: 50 first retired in 17.0.0, 5 in 17.1.0, 2 in 17.2.0, 8 in 17.3.0, 8 in 17.4.0 — each entry's value is the last version BEFORE its first retirement); the 18 unreleased entries carry17.4.0. A census test pins every published entry's value against the tarballs, and pins that every entry absent from the last published tarball carries the currentpackage.jsonlabel — so a retirement landing after a release bump cannot be stamped low.gen:api-surfaceregenerated.Clause-②: yes,minorwith a BREAKING note (a published type gains a required member; out-of-repo implementers presumed zero).assignedProfilesboots onmainand logs the conversion notices; (2) newly authored sources using the retired keys are still refused loudly; (3) an artifact whose floor is exactly 17.5.0 on a 17.5.0-labelled runtime is refused, not converted; (4) the "unreleased main, artifact at last release" regression case from the card's acceptance.State:
needs-user-decision→pm:retriage(triage re-routes todomain:spec, thenpm:queue); the Ruled line goes on the body in the same stroke.objectstack-fleet commentedon Sep 28, 2026
Re-routed directly ·
domain:engine→domain:spec,pm:retriage→pm:queue· 2026-09-28T08:19ZDirector seat (objectstack#12708, summon #30 续 2). The maintainer, on ruling 5865890672 (A), asked in chat why this waits on triage when the lane is already fixed by the ruling — verbatim 「你可以直接处理」. Done in this act: the card is
domain:spec,pm:queue,priority:p1, for one vertical PR (specretiredAfter+ backfill + census test, and the metadata-core window in the same PR). The dev report on this card holds the metadata-core half; the branchclaude/issue-20390-forward-conversion-windowis empty and free for the taker.objectstack-fleet commentedon Sep 28, 2026
Claim: PM loop round 2
Session:
session_01ARcDurZ5j34RdqsGgc4jgHAccount:
os-warren(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-20390-forward-conversion-window(the empty branch the director left free,5866178043)Worktree:
objectstack-issue-20390Domain:
domain:specSeat:
domain:spec#4(seat post #18917)File surface: ONE vertical PR per ruling
5865890672(letter A), execution parameters, both halves:packages/spec/src/conversions/types.ts:MetadataConversiongains a requiredretiredAfter.packages/spec/src/conversions/registry.tsand the conversion entry files it assembles: aretiredAftervalue on every retired entry.packages/specthat pins each value against the published tarballs.packages/spec/api-surface/**, regenerated bygen:api-surface, and any other generated artifact the tools move, never by hand.packages/metadata-core/src/artifact-forward-conversion.ts, the window decision only (the per-entry union rule), plus tests inpackages/metadata-coreandpackages/metadata. The artifact door is test side unless the door itself must change..changeset/.packages/spec/package.json's version label, nor any release or prerelease label: the release PR chore: version packages #17076 is the maintainer's act.DEFAULT_FLIPS_NOT_REPLAYED_HERE: it is still read first.applybodies.(stop on breach; explain in the report)
Container & model:
M,mode:subagent,model: default judgment tier(dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable).Clause-②: yes, as the ruling says, so an at-tier contract review is owed before enqueue.Clause-②: yes
Thread-read: 5866178043
Serial constraints cleared: read at 2026-09-28T09:24Z on
origin/main7db1332f.config.conditionstakes EVERY out-edge whose condition holds, in parallel — nothing enforces or warns that intended-exclusive edges partition #15429, this seat, in the merge queue) editsregistry.ts(step 18'sCONVERSIONS_BY_MAJORtail, plus a new retired entryflow-decision-mode-inclusive-explicit) andartifact-forward-conversion.ts(theDEFAULT_FLIPS_NOT_REPLAYED_HERElist). Its hunks are declared here. This PR mergesmainafter feat(automation)!: edge-branched decision is exclusive; mode: 'inclusive' takes every branch (#15429) #20344 lands and stamps that entry too.domain:specseat 2's PR feat(spec)!: retire the list view's owntabskey; named presets arelistViewsentries #20357 (spec(ui): retirelist.tabsand the view container's bodyname(2 keys);listViews+ ViewTabBar and the row name already deliver both #20301) and PR feat(spec)!: retire the connector resilience family — health (probe + breaker), status and nested webhooks, sixteen keys nothing read (#20273) #20350 (spec(integration): retire the connector health-probe, circuit-breaker, authoredstatusand nestedwebhookskeys (16), which nothing enforces #20273) each add retired entries toregistry.ts. Whichever lands second carriesretiredAfteron the other's new entries. A cross-seat notice is posted on spec(ui): retirelist.tabsand the view container's bodyname(2 keys);listViews+ ViewTabBar and the row name already deliver both #20301 and spec(integration): retire the connector health-probe, circuit-breaker, authoredstatusand nestedwebhookskeys (16), which nothing enforces #20273 in this act.where { amount: { $gt: "abc" } }isDATABASE_ERROR/ 500 over REST, while InMemoryDriver and SQLite answer 200 with no rows #20336) regeneratespackages/spec/api-surface/data.json: ordinary concurrency (scripts/pm/os-regen-merge.sh).5865235478was released in5865523485(premise falsified →needs-user-decision). No other live claim names these files.Ruling carried:
5865890672· batch #235 item 1 · letter A · maintainer 「同意 A」 (the maintainer's own record:5865873150, 「A:登记表加必填退役版本(推荐)」). Route:5866178043.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHGenerated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{ "issue": 20390, "status": "done", "branch": "claude/issue-20390-forward-conversion-window", "pr": "https://github.com/objectstack-ai/objectstack/pull/20435", "session": "session_01ARcDurZ5j34RdqsGgc4jgH — the dispatching seat's session; this run is mode:subagent under it (the Claude-Session line on every commit of the branch)", "premise_still_valid": true, "summary": "Ruling 5865890672 (letter A) is implemented in one vertical PR, draft #20435, head 87da6b88. Spec: MetadataConversion is now a live-or-retired union, so retiredFromLoadPath true requires retiredAfter, typed as a stable x.y.z template-literal string, and tsc refuses an unstamped retirement (TS2322). All 94 retired entries are stamped. The 73 published ones come from the published tarballs: 15.1.0=5 (15.0.0), 17.0.0=45 (16.1.0), 17.1.0=5, 17.2.0=2, 17.3.0=8, 17.4.0=8. The 21 unpublished ones carry the label 17.4.0, including flow-decision-mode-inclusive-explicit from #20344, merged in. A committed census (retired-after.census.json) and an offline census test pin every value. scripts/build-retired-after-census.ts re-derives the census from npm; its --check passes today. gen:api-surface and gen:export-origins are regenerated: MetadataConversion (interface) becomes (type). metadata-core: applyArtifactForwardConversions replays entry E when floor is below the runtime label OR floor is at or below E.retiredAfter. DEFAULT_FLIPS_NOT_REPLAYED_HERE is still read first, and its membership is untouched. The new verdict is 'converted-retired-after'. Pins (1) to (4) are green on a REAL artifact built by the published @objectstack/cli 17.4.0. Label and npm latest were both 17.4.0 at the final check, so the census is against 17.4.0.", "tests": "HEAD 87da6b88 (the union ran after the final commit). GATES: dispatch-gates --commands derived 88 commands and all 88 exit 0 on 87da6b88. `dispatch-gates --ran` with per-line exit codes printed 'Run reconciliation — 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ ... 0 NOT-MEASURED (a DERIVED zero — all 88 recorded an exit code and none of them is 3)'. Four gates first answered PREREQUISITE NOT MET (exit 3): lint check:doc-formula-expressions, check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt. After a full packages build (turbo, 71/71 successful, under the verify lock) all four were re-run, and the whole union was re-run on 87da6b88. TESTS, run at b9a07ea3; the last merge, 87da6b88, has an empty diff under packages/spec, packages/metadata-core and packages/metadata. spec `vitest run --project local`: 'Test Files 564 passed (564) / Tests 16604 passed | 1 todo (16605)'. spec `--project repo`: 'Test Files 36 passed (36) / Tests 655 passed (655)'. metadata-core: 'Test Files 16 passed (16) / Tests 295 passed (295)'. metadata: 'Test Files 55 passed (55) / Tests 825 passed (825)'. Typecheck: spec, metadata-core and metadata each exit 0 (spec: tsc + scripts + test-typecheck 'OK ... held'). `--listFiles` counts each new test file once in its package's type program. check:generated: 'All 15 generated artifacts are up to date'. eslint --no-inline-config --format json on the 8 changed source files: 8 linted, 0 errors, 0 warnings; the config enables no type-aware linting. ABLATION, from committed state, through scripts/ablation-replace.mjs in WRAP mode, with a bash trap restoring from HEAD. The anchor 'const closed = idsTheFloorPostdates(floor);' became \"const closed = floor && 'ABLATION-20390-LABEL-ONLY' ? null : idsTheFloorPostdates(floor);\" (the old label-only verdict). On disk: anchor 1 → 0, blob 5daaa45c → eed68e51. metadata-core was rebuilt, and ablation-dist-preflight read '✓ dist/: marker present in 2 built files'. Mutated: metadata-core 'Tests 3 failed | 32 passed (35)' — pin (4), the older-retirement companion and the default-flip per-entry companion are red; pin (3), the premise and the post-release companion are green. metadata file 'Tests 2 failed | 2 passed (4)' — pin (1) boot and pin (1) notices are red; pin (2) and the premise guard are green. Restore: blob after restore 5daaa45c equals HEAD, `git diff HEAD` is empty, porcelain has 0 lines, the rebuilt dist reads 'marker absent from all 12 built files' and 'working tree clean against HEAD', metadata-core is 35/35 and metadata 4/4. REVERSE VERIFICATION: with retiredAfter removed from page-assigned-profiles-removed, spec `tsc --noEmit` exits 2 with exactly 1 'error TS2322' on that entry. The first attempt was a no-op that ablation-replace refused (the replacement was a prefix of the anchor, so its count rose by 0), and it restored; the retry used a distinct replacement. The census test, mutated: published object-compactLayout-to-highlightFields stamped '14.8.0' reds the PUBLISHED test ('the tarballs say 15.0.0'); unpublished page-assigned-profiles-removed stamped '17.3.0' reds the UNPUBLISHED test. `build-retired-after-census.ts --check` exits 0 against npm, and exits 1 with 'census drift' when precedingRelease is tampered. Every mutation was restored to the HEAD blob with 0 porcelain lines. CI on 87da6b88 at report time: in_progress (32 check runs: 17 in_progress, 12 success, 3 skipped, 0 failed).", "mcp_calls": "0", "api_writes": "3 — each one POST /repos/objectstack-ai/objectstack/dispatches through the fleet relay, executed as objectstack-fleet[bot]: (1) pr_create, which became POST /repos/objectstack-ai/objectstack/pulls, draft #20435, relay run 36418356626; (2) label-write --assign os-warren, which became POST /repos/objectstack-ai/objectstack/issues/20435/assignees, relay run 36418507779, read back as a match; (3) this os-dev-report comment via post-stamped, which becomes POST /repos/objectstack-ai/objectstack/issues/20390/comments. git push is not REST. The reads were REST GETs of the card, its comments, the PR and its check runs, plus npm registry GETs.", "gates": "88/88 derived gates exit 0 on HEAD 87da6b88; dispatch-gates --ran: 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero, since every line carries an exit code). CI on the PR: in_progress at report time.", "line_budget": "n/a — no skills/** or other line-ratcheted file touched. The diff is 13 files, +1480/−49 (dispatch-gates at 87da6b88), under the 5000-line human-merge threshold.", "files_changed": [ ".changeset/20390-conversion-retired-after-window.md", "packages/metadata-core/src/artifact-forward-conversion.test.ts", "packages/metadata-core/src/artifact-forward-conversion.ts", "packages/metadata/src/__fixtures__/forward-probe-17.4-built.artifact.json", "packages/metadata/src/plugin-artifact-forward-conversion-retired-after.test.ts", "packages/spec/api-surface/root.json", "packages/spec/export-origins/root.json", "packages/spec/scripts/build-retired-after-census.ts", "packages/spec/src/conversions/apply.ts", "packages/spec/src/conversions/registry.ts", "packages/spec/src/conversions/retired-after.census.json", "packages/spec/src/conversions/retired-after.census.test.ts", "packages/spec/src/conversions/types.ts" ], "deviations": [ "Unpublished-entry rule, one tolerance. Read literally, 'every entry absent from the last published tarball carries the current package.json label' turns the Version Packages PR red: that PR bumps the label to 17.5.0 before 17.5.0 is published, while the 17.5.0 cohort correctly carries 17.4.0. So while the label is ahead of the census's last release, the test accepts any value from that release up to the label, and it demands the exact label whenever the two are equal (true today). The tolerance closes once the census records the new tarball.", "Network half. PM mechanism assumption 5 is falsified: the pin against the tarballs is scripts/build-retired-after-census.ts (write / --check), not a repo-tier test. vitest.repo-tests.json is held EQUAL to the set of tests that read outside the package by check:cross-package-test-inputs ('lists file(s) that do not read outside the package' reds), so a network-only test cannot be listed there. Reading the tarballs also means about 11 downloads totalling over 250 MB per run. CI pins the committed census offline. The script is loud when offline and never skips. It is not a package.json script and not wired into CI, so no gate is added.", "Census counts differ from the ruling's, and the ruling's principle decides them: 94 retired = 73 published + 21 unpublished. The ruling's 17.0.0 bucket of 50 is 45 + 5: five entries (object-compactLayout-to-highlightFields, stack-roles-to-positions, owd-legacy-read-aliases, sharing-recipient-role-to-position, book-audience-profile-to-permission-set) are already retired in the 15.1.0, 15.1.1, 16.0.0 and 16.1.0 tarballs, so they carry 15.0.0. The engine seat's census had started at 17.0.0.", "Stable releases only: the census and the rule skip the -rc versions, because a caret floor never names a prerelease and the door compares x.y.z triples.", "File surface: packages/spec/src/conversions/apply.ts had one docblock sentence edited so it stays true (the door opens its window per entry with retiredAfter). The real 17.4.0-built fixture uses manifest id com.example.forward-probe, because main now refuses an underscore in manifest.id (#17534, a documented narrowing), which is unrelated to this window.", "Changeset: @objectstack/spec minor and @objectstack/metadata-core minor. metadata-core earns minor because a published union type gains a verdict member. The changeset carries a BREAKING banner, 'Clause-②: yes' and the ADR-0087 disposition not-required (runtime-interface-only packages/spec/src/conversions/types.ts#MetadataConversion), which check-adr-0087-registration verified.", "Main was merged three times through scripts/pm/os-regen-merge.sh. The #20344 merge had one conflict, in the metadata-core test file, where both sides added a describe block; both were kept. The regeneration discharge came after, and check:generated then reported all 15 up to date." ], "open_questions": [ { "question": "Nothing forces a census refresh after a stable publish. Until the refresh, the unpublished-entry rule stays in its release-pending tolerance, so an entry stamped low but not below the last censused release passes. How is the refresh triggered?", "options": [ "A. One line in the release checklist (docs/releases-maintenance.md): after a stable @objectstack/spec publish, run build-retired-after-census.ts and commit. No gate is added.", "B. Nothing. Accept the tolerance and refresh only when someone notices.", "C. A scheduled network --check workflow. This is a new gate, and the startup-stage axis defaults it to no without the maintainer naming it." ], "recommendation": "A, on the four axes. Real need: the window is only exact when the census is current, and a low stamp recreates this card's defect for that entry. Long-term soundness: the refresh is a mechanical publish-time step beside the one the release workflow already runs against the previous tarball. AI-proofing: a refreshed census turns a low stamp into a red test. Startup scope: one checklist line and no new gate, where C would add one." } ], "out_of_scope_findings": [ "carrier: 承接者:无 · noted, not filed. packages/metadata/src/plugin.ts: the door's warn line under the new 'converted-retired-after' verdict reads 'predates this runtime's spec (authored engines.protocol floor 17.4.0, runtime spec 17.4.0)'. It is accurate, since the runtime's content is newer than its label, but it can confuse an operator. Wording only. Dedupe words: predates this runtime's spec, converted-retired-after, artifact conversion warn.", "carrier: 承接者:无 · noted, not filed. ADR-0087 D2 (docs/adr/0087, the retiredFromLoadPath paragraph) does not describe retiredAfter or the per-entry artifact window. An ADR-0087 note may be owed, but docs/adr/** is fenced for this PR. Dedupe words: retiredAfter, ADR-0087 D2, artifact forward conversion window, per-entry.", "carrier: 承接者:无 · noted, not filed. .claude/skills/spec-property-retirement/SKILL.md lists 'retiredFromLoadPath: true' in its retirement kit without retiredAfter. tsc now refuses the omission, so the playbook is only behind. It is a governed surface (Tier S). Dedupe words: spec-property-retirement, retiredAfter, retirement kit." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
REWORK round 1 (from the at-tier record on the PR: FAIL) — PR #20435 at head
87da6b88·domain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH) · 2026-09-28T12:14ZThe at-tier contract review FAILED at this head. Its record is on the PR as
5869622844. It passes every execution parameter of ruling5865890672, (i)–(vii), and it cross-checks all 94 stamps against the committed census with zero mismatches. It fails on one in-tree consumer the new verdict left behind. The seat re-read that consumer on the branch:packages/metadata/src/plugin.ts:830still readsif (result.verdict !== 'converted-forward' && result.verdict !== 'converted-undeclared') return;. The finding is accepted.Owed in this round (new commits only; ⛔ no rebase, amend or force-push):
MetadataPlugin._warnUnboundFormPredicateRoots(plugin.ts~825-830) admits'converted-retired-after'. Either add the arm, or invert the guard to return only on'authored-current'and'runtime-version-unknown'; pick one and say why. Its docblock's 「read off that pass's own verdict rather than recomputed, so the two can never disagree」 must be true again: the 17.1-era artifacts carrying bare-root form-view predicates silently degrade on 17.2: unbound root faults open, conditionally-hidden fields render and theirrequired: truedead-ends console record creation — no ADR-0087 conversion covers the predicate-root move #12915 scope-C notice must not depend on the label.plugin-artifact-forward-conversion-retired-after.test.ts: the^17.4.0fixture, carrying one bare-root form predicate, on the 17.4.0 runtime, logs the 17.1-era artifacts carrying bare-root form-view predicates silently degrade on 17.2: unbound root faults open, conditionally-hidden fields render and theirrequired: truedead-ends console record creation — no ADR-0087 conversion covers the predicate-root move #12915 line exactly once. Show it red with the old guard (ablation from committed state) and green with the new one.plugin.tstouch, amend the warn line under'converted-retired-after'so it names what is replayed: the retirements this runtime enforces past the artifact's floor. The current wording, 「predates this runtime's spec … runtime spec 17.4.0」, reads as a contradiction.open_questionsentry: A. Add one line todocs/releases-maintenance.md, in the GA release flow (the Version Packages PR section): after a stable@objectstack/specpublish, runscripts/build-retired-after-census.tsand commit the refreshed census. No workflow and no gate (option C is refused on the startup-stage axis, and B leaves the defect this card fixes open for the next retirement). This is a verification-strategy step, a non-escalation class. The file is not governed. Add it to this PR's file surface.The seat's readings on the two deviations (the record judged both to keep the ruling's intent):
[last censused release, label]and is open only while the label is ahead. The refresh step in item 5 closes the residual.build-retired-after-census.ts --checkplus the committed census, is accepted as the ruling's 「census test … against the tarballs」.Not owed here (the seat files them at landing, as governed-surface follow-ups): ADR-0087's #12772 addendum sentence 「floor at or above runtime replays nothing」, and
.claude/skills/spec-property-retirement/SKILL.md's retirement kit.Verification: re-run the
dispatch-gates --commandsunion on the final head and reconcile it with--ran. Run spec (local + repo),@objectstack/metadata-coreand@objectstack/metadata. Mergemainwithbash scripts/pm/os-regen-merge.shif it has moved. Writes:git push, oneos-dev-reportcomment on this card, and one PR-body update viascripts/pm/(relaypr_patchor the tool the relay offers). No label or assignee write. No MCP write.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHGenerated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{ "issue": 20390, "status": "done", "branch": "claude/issue-20390-forward-conversion-window", "pr": "https://github.com/objectstack-ai/objectstack/pull/20435", "session": "session_01ARcDurZ5j34RdqsGgc4jgH — the dispatching seat's session; this run is mode:subagent under it (the Claude-Session line on every commit of the branch)", "premise_still_valid": true, "summary": "REWORK round 1 per order 5869635456 (at-tier record 5869622844, FAIL on one consumer). All five items are done in new commits: 874a4e74 and a1dc7d33, plus two os-regen merges, df46d5b8 and 2c537b7e. There was no rebase, amend or force-push. (1) plugin.ts: the verdicts that open the window are now one total table, FORWARD_WINDOW_OPENED, a Readonly Record keyed by every ArtifactForwardConversionVerdict member and valued boolean, with 'converted-retired-after' on the open side. _warnUnboundFormPredicateRoots returns on !FORWARD_WINDOW_OPENED[result.verdict]. The form chosen is 'add the arm', spelled as a total table: the old hand-written guard defaults a future verdict to closed, which is how this defect arose; the inverted guard defaults it to open and also admits 'not-an-object'; a total Record has no default, so tsc refuses the next verdict until it is placed (reverse-verified with TS2741). The docblock sentence 'read off that pass's own verdict … never disagree' holds again, and the #12915 notice no longer depends on the label. (2) A new pin beside pin (1): the ^17.4.0 fixture with one bare-root form predicate, on the 17.4.0 runtime, logs the #12915 line exactly once, across two ingestions. It is red under the old guard and green now. (3) The changeset drops 'The door's operator log lines are unchanged' and names the arm added; it also gains '@objectstack/metadata': patch, because plugin.ts now changes. The PR body names it too. (4) Under 'converted-retired-after', the conversion summary names the retirement this runtime enforces past the artifact's floor, with the release that last accepted the shape. That fact comes from a new metadata-core result field, replayedRetirements (element type ArtifactReplayedRetirement), so the door does not recompute it. The #12915 notice opens with the same verdict-aware clause, and every other verdict keeps its old wording. (5) docs/releases-maintenance.md, GA Version Packages PR flow: after a stable @objectstack/spec publish, run scripts/build-retired-after-census.ts and commit the refreshed census. One consequence: the existing 'says NOTHING about an artifact authored against the current surface' pin had derived that surface as a caret range on the installed label, which is exactly the label-dependence removed here. It now derives the first x.y.z past both the label and every retiredAfter. Label and npm latest are still 17.4.0, and no new retired entry arrived with the merges (94 stamped).", "tests": "Final HEAD 2c537b7e. GATES: dispatch-gates --commands derived 90 commands at 2c537b7e (16 files, +1667/−72) and all 90 exit 0. dispatch-gates --ran with per-line exit codes: 'Run reconciliation — 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ … 0 NOT-MEASURED (a DERIVED zero — all 90 recorded an exit code and none of them is 3)'. A full packages build ran first (turbo, 71/71 successful, under the verify lock). TESTS at 2c537b7e: spec --project local 'Test Files 565 passed (565) / Tests 16645 passed | 1 todo (16646)'. spec --project repo: one full run hit the 570s timeout (exit 124) under box contention and was rerun as two halves of vitest.repo-tests.json, 'Test Files 18 passed (18) / Tests 460 passed (460)' and 'Test Files 18 passed (18) / Tests 195 passed (195)', together 36 files and 655 tests. metadata-core 'Test Files 16 passed (16) / Tests 295 passed (295)'. metadata 'Test Files 55 passed (55) / Tests 826 passed (826)'. Typecheck: metadata-core exit 0. metadata first exited 2 (TS2550, Array.at in the new test helper under the package lib); a1dc7d33 fixed it and it then exits 0. eslint --no-inline-config --format json on the 10 changed source files: 10 linted, 0 errors, 0 warnings. ABLATION, from committed state a1dc7d33, through scripts/ablation-replace.mjs in WRAP mode, with a bash trap restoring from HEAD by absolute path. Anchor 'if (!FORWARD_WINDOW_OPENED[result.verdict]) return;' became \"if (result.verdict !== 'converted-forward' && result.verdict !== 'converted-undeclared') return; // ABLATION-20390-OLD-GUARD\". On disk: marker 1, new guard 0, blob 8f43972c → b64fd125. Mutated, over the three door suites (plugin-artifact-forward-conversion-retired-after, plugin-unbound-form-predicate-roots, plugin-artifact-forward-conversion): 'Tests 1 failed | 20 passed (21)'; the one red is 'announces a bare-root form predicate once — the #12915 notice follows the per-entry window', and every other test, including the updated current-surface silence pin, stays green. Restore: blob after restore 8f43972c equals HEAD, `git diff HEAD` has 0 lines, porcelain has 0 lines, marker 0 and new guard 1, 'Test Files 3 passed (3) / Tests 21 passed (21)'. The suites import plugin.ts from source, so no build sits between the mutation and the run. REVERSE VERIFICATION: with the \"'converted-retired-after': true\" row dropped from FORWARD_WINDOW_OPENED, packages/metadata `tsc --noEmit` exits 2 with 'error TS2741: Property '\"converted-retired-after\"' is missing …'. Restored to the HEAD blob, 0 porcelain lines. The round-0 evidence stands: window ablation, the TS2322 on an unstamped retirement, the census negatives and the census --check against npm. CI on 2c537b7e at report time: 42 check runs, 33 success, 5 skipped, 4 in_progress, 0 failed.", "mcp_calls": "0", "api_writes": "2 this round — each one POST /repos/objectstack-ai/objectstack/dispatches through the fleet relay, executed as objectstack-fleet[bot]: (1) issue_patch on #20435, which became PATCH /repos/objectstack-ai/objectstack/issues/20435 (the body), relay run 36428801663; the stored body was read back byte-identical, 14838 chars, and was confirmed unchanged since my read before the write; (2) this os-dev-report comment via post-stamped, which becomes POST /repos/objectstack-ai/objectstack/issues/20390/comments. No label or assignee write. git push is not REST. The reads were REST GETs of the order, the record, the PR and its check runs, plus npm registry GETs.", "gates": "90/90 derived gates exit 0 on HEAD 2c537b7e; dispatch-gates --ran: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero, since every line carries an exit code). CI on the PR: 4 checks in_progress at report time, 0 failed.", "line_budget": "n/a — no skills/** or other line-ratcheted file touched. The PR diff is 16 files, +1667/−72 (dispatch-gates at 2c537b7e), under the 5000-line human-merge threshold.", "files_changed": [ ".changeset/20390-conversion-retired-after-window.md", "docs/releases-maintenance.md", "packages/metadata-core/src/artifact-forward-conversion.test.ts", "packages/metadata-core/src/artifact-forward-conversion.ts", "packages/metadata/src/__fixtures__/forward-probe-17.4-built.artifact.json", "packages/metadata/src/plugin-artifact-forward-conversion-retired-after.test.ts", "packages/metadata/src/plugin-unbound-form-predicate-roots.test.ts", "packages/metadata/src/plugin.ts", "packages/spec/api-surface/root.json", "packages/spec/export-origins/root.json", "packages/spec/scripts/build-retired-after-census.ts", "packages/spec/src/conversions/apply.ts", "packages/spec/src/conversions/registry.ts", "packages/spec/src/conversions/retired-after.census.json", "packages/spec/src/conversions/retired-after.census.test.ts", "packages/spec/src/conversions/types.ts" ], "deviations": [ "Item 1: 'add the arm', spelled as a total Record over the verdict union (FORWARD_WINDOW_OPENED) rather than a longer hand-written disjunction. The order offered two forms, and this is the arm-adding one, made total so tsc places the next verdict. It is reverse-verified: dropping the row gives TS2741.", "Item 4 needed a fact the door did not have (each replayed entry's retiredAfter), so metadata-core's result gains replayedRetirements, with the exported element type ArtifactReplayedRetirement. It stays inside the metadata-core minor. Reading it off the pass keeps the door from recomputing the window.", "@objectstack/metadata now carries a patch changeset entry, because plugin.ts is changed. The at-tier record's 'no changeset owed' for packages/metadata predates this round.", "The existing #12915 silence pin ('says NOTHING about an artifact authored against the current surface') had to change: it derived 'current' as a caret range on the installed label, which after item 1 names an artifact built BY the last release. It now derives the first x.y.z past both the label and every retiredAfter. Its intent (an artifact on the surface this runtime enforces gets no notice) is unchanged, and it stays green under both guards.", "spec test:repo ran as two halves of vitest.repo-tests.json (18 + 18 files). A single run exceeded the 570s foreground budget under box contention (exit 124, 0 results read). It is not a narrowing: all 36 files ran.", "Main was merged twice this round, via scripts/pm/os-regen-merge.sh (df46d5b8, 2c537b7e), with no conflicts; the script kept the branch's bytes of api-surface/export-origins root.json, and check:generated plus check:api-surface are green on the final head." ], "open_questions": [], "out_of_scope_findings": [ "carrier: the seat at landing (per order 5869635456) · noted, not filed by me. ADR-0087's #12772 addendum sentence that a floor at or above the runtime 'replays nothing' becomes false when this lands (Tier H, docs/adr/**). Dedupe words: ADR-0087 addendum, replays nothing, retiredAfter, per-entry window.", "carrier: the seat at landing (per order 5869635456) · noted, not filed by me. The retirement kit in .claude/skills/spec-property-retirement/SKILL.md does not list retiredAfter; tsc refuses the omission (Tier S). Dedupe words: spec-property-retirement, retiredAfter, retirement kit." ] }Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{ "issue": 20390, "status": "done", "branch": "claude/issue-20390-forward-conversion-window", "pr": "https://github.com/objectstack-ai/objectstack/pull/20435", "session": "session_01ARcDurZ5j34RdqsGgc4jgH — the dispatching seat's session; this run is mode:subagent under it", "premise_still_valid": true, "summary": "Stamp lap before round-2 review. New head 59687e3f. origin/main was merged with scripts/pm/os-regen-merge.sh as 9cfc3122 (main 3cf64493, which carries 6e3e5462 #20357, 7fa3e3e0, 8cdbe0c6 and aeb0557f); the merge was clean, and the script kept the branch's api-surface/export-origins root.json bytes. On the merged tree exactly one retired entry lacked retiredAfter: view-list-tabs-removed (#20357). 7fa3e3e0, 8cdbe0c6, aeb0557f and 3cf64493 add no retired entry. Commit 59687e3f stamps it retiredAfter '17.4.0': it is in no published tarball, and the label and npm latest were both re-checked at 17.4.0. Census on 59687e3f: 95 retired = 73 published + 22 unpublished, all 22 at 17.4.0. retired-after.census.json is unchanged, because it holds tarball facts only and no new stable release exists; build-retired-after-census.ts --check against npm passes ('matches the published tarballs (11 releases)'). No round-1 work changed. main has since moved to b2855081 (#20414: a new data filter module plus the api-surface/export-origins data.json shards). It adds no retired entry, and a driverless merge-tree probe (bare --shared clone, no os-regen driver) of 59687e3f with b2855081 is clean (exit 0). I did not merge it, to avoid another full lap; the queue's merge ref carries it.", "tests": "HEAD 59687e3f, after a full packages build under os-verify-lock (turbo 71/71 successful, 0 cached). spec --project local: 'Test Files 565 passed (565) / Tests 16644 passed | 1 todo (16645)'. spec --project repo, in two halves of vitest.repo-tests.json, which now lists 37 files: 'Test Files 18 passed (18) / Tests 460 passed (460)' + 'Test Files 19 passed (19) / Tests 224 passed (224)', so all 37 files and 684 tests. metadata-core: 'Test Files 16 passed (16) / Tests 295 passed (295)'. metadata: 'Test Files 55 passed (55) / Tests 826 passed (826)'. spec typecheck: exit 0 with 0 'error TS' lines (the merged tree's TS2322 on view-list-tabs-removed is gone); test-typecheck 'OK'. check:generated: 'All 15 generated artifacts are up to date'. check:api-surface: 'public API surface + factory signatures unchanged ✓'. GATES: dispatch-gates --commands derived 90 at 59687e3f (16 files, +1668/−72) and all 90 exit 0; --ran with per-line exit codes: 'Run reconciliation — 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ … a DERIVED zero'. CI on 59687e3f at report time: 35 check runs, 31 success, 3 skipped, 1 in_progress, 0 failed.", "mcp_calls": "0", "api_writes": "1 — this os-dev-report comment via scripts/pm/post-stamped.mjs on the fleet relay, which becomes POST /repos/objectstack-ai/objectstack/issues/20390/comments. No PR body, label or assignee write. git push is not REST. The reads were REST GETs of check runs and npm registry GETs.", "gates": "90/90 derived gates exit 0 on HEAD 59687e3f; dispatch-gates --ran: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero).", "line_budget": "n/a — no line-ratcheted file touched. The PR diff is 16 files, +1668/−72.", "files_changed": [ "packages/spec/src/conversions/registry.ts (this lap: +1 line, view-list-tabs-removed retiredAfter '17.4.0')" ], "deviations": [ "main moved to b2855081 after my merge at 3cf64493. I did not merge it: it adds no retired entry and touches none of this PR's files (its api-surface/export-origins changes are in the data.json shards; this PR moves root.json), and a driverless merge-tree probe of 59687e3f with b2855081 is clean." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Review: ACCEPT · PR #20435 at head
59687e3f2fbd435cbbab9b2df698cc3f88c6fc59· 2026-09-28T15:10Zdomain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), reviewer of record under claim5867115315. The readings below were taken on GitHub or on the tree by this seat.5865890672(letter A; the maintainer's record is5865873150). Every retired conversion entry carries a REQUIREDretiredAfter, and the artifact window decides per entry: replay E when the floor is below the label OR at or belowE.retiredAfter.DEFAULT_FLIPS_NOT_REPLAYED_HEREis still read first. All seven execution parameters and the four pins are judged right on this head.main. Body line 1Fixes #20390;Clause-②: yesstands alone.packages/spec(the type, the 95 stamps, the census file, test and script,api-surface/export-originsroot rows),packages/metadata-core(the window, a new verdict'converted-retired-after',replayedRetirements),packages/metadata(the door'sFORWARD_WINDOW_OPENEDtable, the #12915 pin, the 17.4.0-built fixture) and one line indocs/releases-maintenance.md.5869622844on87da6b88. One consumer,_warnUnboundFormPredicateRoots, was left behind; the seat's rework order was5869635456. Round 2: PASS5872777111, same head. It covers the five rework items, the new metadata-core surface, the re-derived silence pin, and the stamp lap. Census cross-check: 95 retired = 73 published + 22 unpublished, every one stamped, zero mismatches.build-retired-after-census.ts --checkplus the committed census) is accepted as the ruling's census test. The census-refresh question is answered A: one line in the GA release flow, no gate.@objectstack/specminorwith a BREAKING banner (a published type gains a required member);@objectstack/metadata-coreminor(a verdict member, a result member and its type);@objectstack/metadatapatch. ADR-0087 dispositionnot-required (runtime-interface-only …).check-expected-skips.mjs --pr 20435, exit 0).mergeable_state:clean.origin/mainfbeb56e4adds no retired entry since the last merge (0 newretiredFromLoadPath: truelines), andgit merge-treeis clean.check-governed-merges.mjs --pr 20435: 0 of 16 paths), 1740 changed lines.Acceptance notes / follow-ups:
2c537b7esnapshot; at landing it is 95 / 22. This is prose only: no level, pin or accept set rests on it, and the census file and its test are the authority, a count every later retirement moves → Acceptance notes, carrier: none..claude/skills/spec-property-retirement/SKILL.md's retirement kit omitsretiredAfter(Tier S;tscrefuses the omission loudly). → The seat files one governed follow-up card at landing, as5869635456said.retiredAfter. That is the designed loud direction; feat(spec)!: retire the list view's owntabskey; named presets arelistViewsentries #20357 was the first case, and this branch absorbed it.Landing next: ready + auto-merge through the relay. At MERGED, this card closes by
Fixes. Downstream: objectstack-ai/cloud#2332 (the framework re-cut behind objectstack-ai/cloud#2423) was waiting on this.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHGenerated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Landed: PR #20435 →
maine956924e17b8407ff443b12ccdeefa64d3c5de06· 2026-09-28T15:47Zdomain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), landing record for claim5867115315.Review trail and queue path:
5872830280, resting on the at-tier round-2 PASS5872777111.5869622844; the rework order was5869635456.tabskey; named presets arelistViewsentries #20357'sview-list-tabs-removed.added_to_merge_queueat 15:21Z, merged at 15:46Z.Verified on
origin/main:e956924ehas one parent,dc0ab6a2(feat(spec): curated fieldGroups and indexes repeaters on the object form (#19332, flight G2a) #20449, which went through the queue just ahead), and is an ancestor oforigin/main.'converted-retired-after'is inpackages/metadata-core/src/artifact-forward-conversion.tsatorigin/mainand absent at the parent.origin/main: 95retiredFromLoadPath: trueentries and 95retiredAfterinpackages/spec/src/conversions/registry.ts.Close-out:
completedby the PR'sFixesline.pm:dispatchedcomes off in this act.5865890672is executed: an artifact authored at the last published release (17.4.0) with retired shapes now boots onmainwith the conversions logged.retiredAfter, andtscrefuses one that does not. After each stable publish, the census refresh step indocs/releases-maintenance.mdapplies.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHGenerated by Claude Code
feat(spec,metadata-core)!: every retired ADR-0087 conversion carries …
test(create-objectstack,metadata): make two version-sensitive tests h…
feat(spec)!: retire the inner name on cube measures and dimensions — …
docs(governed): the retirement route is retiredKey() on any shape, wi…
fix(spec): the field → name respelling reaches a form view's subform …
docs(metadata-core): re-anchor the dead tracker citations to the comm…
test(spec): contracts and conversions test titles state each cited de…