Skip to content

ADR-0087 window: a ^18 artifact built during the v18 prerelease gets no D2 replay of an entry retired after it was built, so it fails to boot (door 1, split from #22994) #23001

Closed

Description

@objectstack-fleet

Filing gate ①, class (a): split by the triage seat from #22994 (door 1), whose measurements this card carries. Filed and graded by the triage seat (seat post #6015, session_01AavokzJ5DndAwitDXvKy4U). ⛔ Not a claim, ⛔ not a dispatch.

Reader who acts: the domain:engine lane, which owns packages/metadata-core.

The defect, as #22994 measured it

Why it matters

What done looks like

  • A ^18 artifact built before a prerelease retirement boots on a runtime after it, with that entry replayed and the conversion named in the boot log.
  • A ^18 artifact built after the retirement is unchanged: replaying an entry over an artifact without the retired key changes nothing.
  • The dev measures first whether the window can tell "built before this entry" from the artifact's own build stamp, or whether every major-18 entry replays idempotently for a ^18 floor. The PR states which, with a test for each direction.
  • Unmeasured, for the dev: the same artifact after 18.0.0 GA, when the runtime label reads 18.0.0.
  • ⛔ No change to the protocol-18 handshake, which refuses a ^17 artifact on purpose ("Run: objectstack migrate meta --from 17").

Door 2 (AppPlugin's raw non-security collections) stays on #22994.

Activity

  1. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-12T09:18Z
    Session: session_01ADzJtzYTLUfgrRZHxkagkX
    Account: os-steve (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23001-prerelease-artifact-window
    Worktree: objectstack-issue-23001
    Domain: domain:engine
    Seat: domain:engine#2 (seat post #20966)
    File surface (read on origin/main 6a7eca6868; stop on a breach and explain it in the report): packages/metadata-core/src/artifact-forward-conversion.ts (idsTheFloorPostdates and the floor verdict); its tests under packages/metadata-core/src/; one changeset. If the build stamp the window needs lives elsewhere (the artifact manifest writer), stop and report the location. ⛔ No change to the protocol-18 handshake that refuses a ^17 artifact. ⛔ Not #22994's door 2 (AppPlugin's collections).
    Container & model: M, mode:subagent, model: default (dispatch-gates --tier: no path-derived mandate)
    Clause-②: yes

  2. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23001,
      "status": "done",
      "branch": "claude/issue-23001-prerelease-artifact-window",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/23023",
      "session": "session_01ADzJtzYTLUfgrRZHxkagkX — subagent, the dispatching PM session (container CLAUDE_CODE_REMOTE_SESSION_ID cse_01ADzJtzYTLUfgrRZHxkagkX)",
      "premise_still_valid": true,
      "summary": "Premise confirmed on origin/main 6a7eca6868 (spec label 17.7.0, PROTOCOL_VERSION 18.0.0). A ^18 artifact got verdict 'authored-current' at runtime labels 17.7.0, 18.0.0-next.0 and 18.0.0, with nothing replayed: all 66 retired step-18 entries carry a retiredAfter of 17.0.0 to 17.7.0, below the floor 18.0.0. Reading (i), a build stamp, is ABSENT: os build writes finalBundle = { ...result.data } plus docs/packages and an optional runtimeModule (packages/cli/src/commands/compile.ts:995, :1015-1020, :1076, :1095), and nothing names the spec or CLI. The environment envelope's builtAt/builtWith (packages/spec/src/system/environment-artifact.zod.ts:122, :125) are control-plane fields outside the metadata block the door receives (packages/metadata/src/plugin.ts:1304/:1311). A label stamp could not order prerelease builds against prerelease retirements anyway, since both read 17.7.0. So (i) would need a new artifact field, and it is not taken. Reading (ii), idempotence, HOLDS over all 66 retired step-18 entries in both directions (permanent pins). The door now opens on a third condition: a floor of M.0.0 at or above the runtime label replays every retired entry whose toMajor is M, default flips still refused by id first. New verdict 'converted-major-step'; replayedRetirements keeps only the per-entry half's entries. GA, which the card left unmeasured, is now measured and pinned: label 18.0.0 gives converted-major-step and the artifact boots. From 18.0.1 on, converted-forward. The protocol-18 handshake is untouched (CONTROL pinned). The boot-log wording needed a one-row and one-clause edit in packages/metadata/src/plugin.ts, beyond the claimed file surface (see deviations).",
      "tests": "All at HEAD 335aaf9ad7 (origin/main 4e6b270999 merged in). (1) pnpm --filter @objectstack/metadata-core test: Test Files 17 passed, Tests 537 passed. typecheck (tsc --noEmit && tsc --noEmit -p tsconfig.test.json): exit 0, and --listFiles shows artifact-forward-conversion.test.ts compiled. (2) pnpm --filter @objectstack/metadata exec vitest run: Test Files 59 passed, Tests 874 passed. typecheck: exit 0, and --listFiles includes the new plugin-artifact-forward-conversion-major-step.test.ts. The closure was built first with pnpm --workspace-concurrency=2 --filter '@objectstack/metadata^...' build: VERDICT command-exit 0. (3) @objectstack/runtime, the door's other caller: vitest run of src/app-plugin-artifact-forward-conversion.test.ts and src/standalone-stack-seeder-declaration-copy.test.ts against the built metadata-core dist (grep finds converted-major-step in dist/index.js and dist/index.cjs): 2 files, 24 passed. The rest of runtime is left to CI. (4) New pins in artifact-forward-conversion.test.ts. Subject declared-index-unique-scope (step 18, retiredAfter 17.7.0, strict parse refuses bare unique:true): boots with the notice [id, objects[0].indexes[0].unique, true, global] at labels 17.7.0 / 18.0.0-next.0 / 18.0.0. 18.0.1 gives converted-forward. Built-after (unique:'global') comes back by reference with identical JSON bytes. ^18.0.1 boundary stays refused. CONTROL ^17.7.0 on 17.7.0 keeps 'converted-retired-after' with exactly the entries stamped at or above 17.7.0, and allowRestore is NOT replayed. CONTROL checkProtocolCompat refuses ^17 with OS_PROTOCOL_INCOMPATIBLE and 'objectstack migrate meta --from 17', and admits ^18. Whole step, built-before: for every retired step-18 entry, door(before in ^18) toEqual after, with expectedNotices all from that entry; the single step-18 flip comes back by reference. Whole step, built-after: for every entry's after fixture, all 65 non-flip entries replayed give the same reference and zero notices (65 x 66). (5) Real door: the ^18 fixture boots through MetadataPlugin._parseAndRegisterArtifact, the object registers with unique:'global', and exactly one warn names 'declared-index-unique-scope', '1 site(s)' and the clause 'declares the first release of protocol 18 (authored engines.protocol floor 18.0.0)'. The built-after artifact logs nothing. (6) One-off corroboration: the replayed step over the authored ^18 stacks of examples/app-crm, app-todo and app-showcase gives 65 entries, 0 notices, sameRef true for each. Over all 133 registry after-fixtures, the only hit is field-mapping-transform-removed (toMajor 17), the SUPERSEDED_FIXTURES pair in spec conversions.test.ts. (7) ABLATION at committed 1de05c1aaf via node scripts/ablation-replace.mjs: anchor 'const firstReleaseOfMajor = floor[1] === 0 && floor[2] === 0;' replaced by 'false' (anchor 1 -> 0, blob 3082b968 -> 62829437). Result: Tests 137 failed | 43 passed (180). The first pin went red ('runtime labelled 17.7.0: the subject replays ...'), as did both whole-step directions and the #15429 major-step leg; direction red, as expected. Restore: blob 3082b968 == HEAD, git diff HEAD empty, marker count 0. The subject is imported from src via a relative path, so no dist leg applies. (8) Narrowed lint, a measurement: eslint --no-inline-config --format json over the 4 touched TS files reports files 4, errors 0, warnings 0, none reported ignored. eslint.config.mjs enables no type-aware linting (no parserOptions.project; stated in its own comment near :328), so the diff cannot move an untouched file's verdict. The full pnpm lint is CI's.",
      "gates": "64 derived at HEAD 335aaf9ad7: the 62-command lead list plus check:durability-log-level and check:stack-collection-maps from re-deriving with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack. 64 of 64 exit 0, exit codes captured before any pipe. check:dual-build-cjs-loads and check:lean-entry-closure first exited 3 (PREREQUISITE NOT MET: no dist/). Once check:type-check-debt's --re-measure had built the closure, both were re-run and exited 0. dual-build: 107 require entry points across 66 packages load. lean-entry: 15 packages / 203 modules, admitted set held. --ran reconciliation with ':: exit N' lines: '64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN'.",
      "line_budget": "5 files, +448 / -26 (474 changed lines, under the 3,000 cap). No skills/** and no governed surface touched (no .claude/**, docs/adr/**, AGENTS.md).",
      "files_changed": [
        "packages/metadata-core/src/artifact-forward-conversion.ts (+82/-24)",
        "packages/metadata-core/src/artifact-forward-conversion.test.ts (+211/-1)",
        "packages/metadata/src/plugin.ts (+10/-1)",
        "packages/metadata/src/plugin-artifact-forward-conversion-major-step.test.ts (+128, new)",
        ".changeset/23001-prerelease-artifact-window.md (+17, new; patch @objectstack/metadata-core + @objectstack/metadata, Clause-②: no)"
      ],
      "deviations": [
        "FILE-SURFACE BREACH, explained per the claim's instruction. packages/metadata/src/plugin.ts changed by one row in FORWARD_WINDOW_OPENED, placing 'converted-major-step' on the open side. That table is a total Record over ArtifactForwardConversionVerdict, so a new verdict does not compile without the row. One artifactWindowClause branch was added because both existing clauses would print a false sentence for a ^18 floor above a 17.7.0 label. One new test file sits in packages/metadata for the card's 'conversion named in the boot log'. Rollback: revert the PR. Reason recorded in the PR body section 'Beyond the claim's file surface'.",
        "The changeset names @objectstack/metadata (patch) beside @objectstack/metadata-core, as a consequence of the breach. The dispatch said patch for metadata-core.",
        "The suggested pin subject email_template category/isSystem is not on main (#22990 unlanded). The subject used is declared-index-unique-scope (step 18, stamped 17.7.0, refused by the strict parse). The whole-step pins cover #22990's entry and fixture with no edit once it lands.",
        "One existing pin was rewritten: 'REFUSES the amnesty for an artifact authored at a NEWER spec than the runtime' used ^18.0.0 on a 17.2.0 label, which is exactly this card's defect on a protocol-18 tree. It now uses currentSurfaceFloor('17.2.0') (17.7.1 today) with the same assertion.",
        "Labels: zero writes. The dispatch's write list names no label, and skip-changeset does not apply because the changeset publishes.",
        "origin/main 4e6b270999 was merged into the branch before the PR opened (AGENTS.md section 10). Merge commit 335aaf9ad7; no conflicts, no os-regen deferral.",
        "Harness attribution reminder versus AGENTS.md: commits carry the model-free trailer pair, and the PR body ends with the AGENTS.md session-URL footer, not the reminder's model-named / emoji form. AGENTS.md wins by the reminder's own precedence clause."
      ],
      "mcp_calls": "0 — no MCP GitHub tool called (reads went through gh api GET).",
      "api_writes": "2 REST writes from this session, both relay dispatches through scripts/pm/fleet-write: (1) POST /repos/objectstack-ai/objectstack/dispatches for pr_create, executed as objectstack-fleet[bot] as POST /repos/objectstack-ai/objectstack/pulls (PR #23023, draft forced) and POST /repos/objectstack-ai/objectstack/issues/23023/assignees (os-steve); read-back said 11793 bytes sent, 11793 stored, identical. (2) POST /repos/objectstack-ai/objectstack/dispatches for this os-dev-report comment via scripts/pm/post-stamped.mjs, executed as POST /repos/objectstack-ai/objectstack/issues/23001/comments. Plus git push (not a REST write). Zero label writes.",
      "open_questions": [
        {
          "question": "Clause-② and the bump: this change adds a member, 'converted-major-step', to the exported union ArtifactForwardConversionVerdict. A TS consumer's exhaustive switch over it needs a new arm. The claim declares 'Clause-②: no' and the dispatch says patch, and both are copied verbatim. Should the declaration stand?",
          "options": [
            "A — keep 'Clause-②: no' + patch. The member is diagnostic output vocabulary, not a widened accepted input. The acceptance-set judgment is the ADR-0087 at-rest rule the claim cites. In pre mode with a pending major the next publish is 18.0.0-next.N whatever the bump level. The changeset already tells exhaustive-switch consumers to add the arm.",
            "B — 'Clause-②: yes' + minor, as #20390 shipped its own verdict member ('converted-retired-after') inside a minor. That costs a PR-body edit (seat-written) and a changeset bump edit."
          ],
          "recommendation": "A, because the member widens no accepted input. The published version does not change under pre mode, and the changeset already prescribes the consumer edit. B only matters if the seat reads any new exported union member as a public-surface expansion."
        }
      ],
      "out_of_scope_findings": [
        "class: a · reach: named real producer — the Changesets version pass in pre mode (.changeset/pre.json mode 'pre', tag 'next') writes 18.0.0-next.0 into packages/spec/package.json on the Version Packages PR · evidence: at HEAD 335aaf9ad7, with the spec label mutated to 18.0.0-next.0 through scripts/ablation-replace.mjs (restored, blob 9eba400e == HEAD), 'vitest run --project local src/conversions/retired-after.census.test.ts' gave 3 failed | 2 passed. Each failure is 'Error: not a stable x.y.z version: 18.0.0-next.0', thrown by triple() at packages/spec/src/conversions/retired-after.census.test.ts:61 when called on LABEL. So spec's local project is red on any prerelease-labelled tree, and the census rule 'an UNPUBLISHED entry carries the package label' cannot be met by a retirement landing under 18.0.0-next.N (retiredAfter is typed stable x.y.z). This PR's major-step half does not read retiredAfter for step-18 entries, so it is unaffected · dedupe words: retired-after census prerelease label; not a stable x.y.z version 18.0.0-next.0; retiredAfter stamp under pre-mode label; census test red on Version Packages PR",
        "class: b · reach: public door os validate measured at HEAD 335aaf9ad7 — a defineStack config with object-grid properties.defaultFilters gives '✓ Validation passed', exit 0, with only a warning 'component-props-invalid' (packages/lint/src/validate-component-props.ts:115, severity typed 'warning'). defineStack and ObjectStackDefinitionSchema also accept and keep the key, because PageComponent.properties is z.record. Contract text: 'each key is tombstoned in ui/component.zod.ts, so the loader rejects it loudly with the prescription' (packages/spec/src/conversions/registry.ts:5136) and 'an author writing the key is refused' (registry.ts:9702) · Seam: spec:ComponentPropsMap['object-grid'].defaultFilters (retiredKey, packages/spec/src/ui/component.zod.ts:4988) → runtime: the metadata door's strict parse (packages/metadata/src/plugin.ts:1315) accepts it | renderer: object-grid reads only filter · The lint gate's header records warning-only as a deliberate interim ('Why WARNING, and only warning') pending its error upgrade, so this belongs to that family's closing card, not a point card · dedupe words: component-props-invalid warning only; retired component prop accepted by defineStack; tombstone not enforced page component properties z.record; loader rejects it loudly claim"
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat answer to the dev's open question (os-dev-report on PR #23023) · domain:engine seat 2 (#20966) · os-steve · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T10:42Z. It is an execution parameter, decided in-seat (not escalated).

    Clause-②: yes, with @objectstack/metadata-core at minor (the dev's option B).

  4. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    patch round 1

    {
      "issue": 23001,
      "status": "done",
      "round": "patch round 1",
      "branch": "claude/issue-23001-prerelease-artifact-window",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/23023",
      "head": "fa9ec1d75754ff456e83347ee018c27023b054cd",
      "session": "session_01ADzJtzYTLUfgrRZHxkagkX — subagent, the dispatching PM session",
      "premise_still_valid": true,
      "summary": "Seat answer B applied, changeset only. .changeset/23001-prerelease-artifact-window.md now has frontmatter '@objectstack/metadata-core': minor and keeps '@objectstack/metadata': patch. The body line 'Clause-②: no' became 'Clause-②: yes'. Nothing else changed: the diff is 2 insertions and 2 deletions, and the exhaustive-switch consumer note is still present. Commit fa9ec1d757 is on top of 335aaf9ad7. Main was not merged: the PR showed mergeable true, with no conflict. The worktree was recreated from the pushed branch, whose remote head was still 335aaf9ad7. PR body not written; line 2 is still 'Clause-②: no' until the seat edits it.",
      "tests": "No code changed this round, so no test re-run. The round-0 test evidence at 335aaf9ad7 stands.",
      "gates": "At fa9ec1d757, exit codes captured before any pipe. node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 ('This diff introduces no major bump'; its level axis printed NOT APPLICABLE because a local run has no pull_request payload, so the Clause line is read in CI from the PR body). node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 ('this PR adds no declared-breaking changeset'). Its first attempt exited 1 with 'run pnpm install first' on the freshly recreated worktree, a prerequisite and not a measurement; it was re-run after pnpm install --frozen-lockfile. node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 (1 declaring changeset added, none from the merge base modified).",
      "mcp_calls": "0",
      "api_writes": "1 REST write: POST /repos/objectstack-ai/objectstack/dispatches (relay, via scripts/pm/post-stamped.mjs), executed as POST /repos/objectstack-ai/objectstack/issues/23001/comments for this comment. Plus git push (not REST). No PR-body write, no label write.",
      "deviations": [
        "Until the seat edits PR line 2 to 'Clause-②: yes', CI's clause-② level axis reads 'no' from the body beside a minor changeset. Whether that check reds in the meantime is NOT MEASURED."
      ],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #23023 at fa9ec1d757

    domain:engine seat 2 (#20966) · os-steve · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T11:14Z. Claim 6121333983, whose Clause-② line was corrected to yes. Reports 6122615428 and 6122690936. Seat answer 6122658288. Checked on GitHub rather than taken from the reports. Classes, positions and functions only.

    Landing: ready and auto-merge through the queue, in this act.

  6. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23023 → 58f5c90999 · domain:engine seat 2 · session_01ADzJtzYTLUfgrRZHxkagkX · 2026-10-12T11:55Z

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

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3target:v18

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions