Skip to content

fix(objectql): a refused upgrade leaves the installed version serving - #28822

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-cjuysm
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/project-thread-cjuysm

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

Requested by Leehom · project thread

Refs #28697

Clause-②: yes (widening) — the published SchemaRegistry gains one public method, restoringOnRefusal
Metadata-protocol: none

维护者速读

  • 改了什么:已安装的包升级时,如果注册表在注册新版本的过程中拒绝了它,已安装的版本继续完整地服务:包记录、对象、应用、条目、选项列表、命名空间、权限集名和默认记录页都回到升级之前的样子。其他包对它的对象的扩展、租户的覆盖层都保持不变。
  • 为什么改:升级被拒后,系统应当和升级前完全一样,运维修好包再装即可。拒绝本身(错误码、状态、文字)不变,变的只是拒绝之后留下了什么。
  • 风险与回滚:objectql patch。注册表在升级写入每个条目之前先记下它原来的样子,被拒时只把这些条目放回去,从不经过卸载。首次安装、干净的升级行为不变。回滚就是 revert 这个 PR。
  • 先看:packages/objectql/src/registry.ts 的 restoringOnRefusal 和各个 note* 记录点(registerObject、registerItem、installPackage、registerPicklistExtension、命名空间与两类声明);engine.ts 的 registerApp 里升级走它的那一处。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/package-refused-upgrade-keeps-installed.test.ts,预期 13 passed。
  • 需要你做的:无,席位落地。

What changes

  • A refused upgrade leaves the installed version serving, whole. When ObjectQL.registerApp re-registers an installed package id and the registry refuses the new version, the package record, every object (as served and as layered), app and item, picklist options and extensions, namespaces, permission-set names, default record pages and datasource definitions read exactly as before the upgrade. Another package's extension of its objects and a tenant's overlay of them are untouched and still served.
  • SchemaRegistry.restoringOnRefusal(register) (new public method) runs one synchronous registration. Before the run's first write to an entry, it records what that entry held: an object's contributor list, a namespace's owners, an item slot (the package record included), the package's extension of one picklist, and the security-catalog and default-record-page claims the package door records. If the run throws, it puts back exactly those entries, re-resolves merged objects and throws the same error. Entries the run did not write are never recorded and never put back. It never goes through uninstall.
  • The engine runs an upgrade's registration through it. An upgrade means a package record already answers the id. A first install keeps the existing withdrawal, a clean upgrade registers as before, and every refusal keeps its code, status and words.
  • The shared registry test double models the new member by running the body as given.

Why

An upgrade that is refused should leave the running system exactly as it was, so the operator can fix the package and upgrade again while the installed version keeps serving.

Tests

  • packages/objectql/src/package-refused-upgrade-keeps-installed.test.ts (new, 13 tests). An installed version with objects, an app, items, a picklist, another package's extension and a tenant overlay; then a newer version the registry refuses, one case for each registry refusal that can arrive during an upgrade. Each case asserts the same code and status, and that everything the engine serves reads exactly as before the upgrade, the extension and the overlay included. Controls: a clean upgrade behaves as before and keeps the extension and the overlay; a refusal raised before anything is written leaves the installed version as before; the other package's entries do not move.
  • package-refused-registration-withdrawn.test.ts: the control "an installed id refused part-way by the registry is not withdrawn" now asserts the installed version keeps serving.
  • packages/qa/dogfood/test/install-local-refused-package-leaves-nothing.dogfood.test.ts: new case 4. Through POST /api/v1/marketplace/install-local, a refused upgrade answers 422 PLUGIN_REGISTER_FAILED as before, and afterwards the installed version, its object (with another package's extension field) and its app read exactly as before the upgrade.

Acceptance notes

  • A tenant overlay is pinned at the unit layer only. Through the HTTP doors, an overlay of a managed package's object cannot be saved (403 NOT_OVERRIDABLE, ADR-0131 D6), so the dogfood case pins the other package's extension instead.
  • The restore covers what one registration writes into the registry. The engine's own writes (the package manifest it keeps for routing, datasource definitions, navigation contributions, contributed kinds) were already written only after a whole registration, and still are.

Verification

All readings below are on the tree of f72ef4f7.

Red first on base (`c064d3a2`)
  • Unit, with engine.ts, registry.ts and the test double taken from c064d3a2: the two files ran 11 failed and 26 passed. The 11 are the ten refused-upgrade cases and the flipped control. On the first red run each case passed its code-and-status assertion and failed at the state comparison. The three controls and every case of the earlier file pass. Restored with git checkout HEAD --, and git diff HEAD was empty.
  • Dogfood, with engine.ts and registry.ts from c064d3a2, objectql rebuilt and the dist preflight showing restoringOnRefusal absent: 1 failed and 3 passed (case 4). Restored (blob == HEAD, diff empty) and rebuilt; the preflight showed the marker present and the tree clean.
Green
  • package-refused-upgrade-keeps-installed.test.ts 13/13 and package-refused-registration-withdrawn.test.ts 24/24.
  • pnpm --filter @objectstack/objectql test: 520 files, 9996 tests, all green. pnpm --filter @objectstack/objectql typecheck: exit 0 (check:test-typecheck OK, ledger held).
  • Dogfood, with the @objectstack/dogfood^... closure built under the lock: install-local-refused-package-leaves-nothing 4/4 (VERDICT command-exit 0).
Ablations (unit files; objectql tests import src), each through ablation-replace with the restore proven blob == HEAD

Each mutation was run on f72ef4f7, over both unit files (37 tests).

Mutation Red
engine: an upgrade does not go through restoringOnRefusal 11 (all ten cases and the flipped control)
registerObject: contributor list not recorded 11
installPackage: package record slot not recorded 11
registerItem: item slot not recorded 4 (standalone action, picklist, picklist extension, nested plugin)
registerItem: a new type store not recorded 1 (standalone action)
registerPicklistExtension: this package's entry not recorded 2 (standalone action, nested plugin)
registerNamespace: owner set not recorded 1 (nested plugin)
security-catalog claim not recorded 10
default-record-page claims not recorded 10
no re-resolve after the restore 1 (nested plugin: a v1 object bound to the picklist kept serving the refused version's options)
Derived gates

node scripts/pm/dispatch-gates.mjs --commands derived 68 families, and 67 exited 0. pnpm check:dual-build-cjs-loads exited 3 PREREQUISITE NOT MET because it needs every package's dist/, so it is NOT MEASURED here and left to CI. check-plugin-teardown-shape --self-test first exited 3 because the shallow clone lacked its pinned fixture commit; after fetching that one commit it exited 0. The --ran reconciliation reads 68 accounted, 67 run, 1 NOT-MEASURED, 0 unrun. test:repo and the lint farm run in CI. The full dogfood battery was not run, since the title has no ! and there is no narrowing arm.

Overlap: open PRs were re-read before opening. #28811 (draft) touches registry.ts at the top of registerObject, away from these hunks. #28791 merged after this branch was cut, and this branch merges with main cleanly (git merge-tree).

Clause-② and Metadata-protocol, measured: nothing newly refused and no authored shape changes (Metadata-protocol: none). The one public surface change is a new public method on the published SchemaRegistry, declared Clause-②: yes (widening). The changeset is patch for @objectstack/objectql.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes (CI).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01FEkQ5kohmFH2NCe2Gvxduz

The registry records each entry an upgrade writes before its first write
and, when the upgrade is refused part-way, puts exactly those entries back.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FEkQ5kohmFH2NCe2Gvxduz
…og and default-page claims in the upgrade fixture
@github-actions

github-actions Bot commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 22 documentable anchor(s).

8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via SchemaRegistry (symbol, a top-level class))
  • content/docs/deployment/app-dependencies.mdx (via installPackage (symbol, a method of class SchemaRegistry))
  • content/docs/deployment/environment-variables.mdx (via SchemaRegistry (symbol, a top-level class))
  • content/docs/kernel/contracts/metadata-service.mdx (via /api/v1/packages/:packageId (route, bridged from symbol installPackage — its route source's handler names it))
  • content/docs/kernel/services-checklist.mdx (via SchemaRegistry (symbol, a top-level class))
  • content/docs/plugins/adding-a-metadata-type.mdx (via SchemaRegistry (symbol, a top-level class))
  • content/docs/protocol/kernel/error-handling.mdx (via registerApp (symbol, a method of class ObjectQL), registerObject (symbol, a method of class SchemaRegistry), /api/v1/packages/:packageId (route, bridged from symbol installPackage — its route source's handler names it))
  • content/docs/protocol/kernel/plugin-spec.mdx (via SchemaRegistry (symbol, a top-level class))

⛔ 4 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v12.mdx (via installPackage (symbol, a method of class SchemaRegistry))
  • content/docs/releases/v17/17-0.mdx (via /api/v1/packages/:packageId (route, bridged from symbol installPackage — its route source's handler names it))
  • content/docs/releases/v17/17-4.mdx (via SchemaRegistry (symbol, a top-level class), /api/v1/packages/:packageId (route, bridged from symbol installPackage — its route source's handler names it))
  • content/docs/releases/v18/18-2.mdx (via installPackage (symbol, a method of class SchemaRegistry))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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

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

Which tree this was computed on

This run read content/docs from c42de6639a0275cade2bd652ea0073cb6ebc06b8 — the merge of head f72ef4f72c41e360be5fced77465bc68b23a1c7a into base 663b7c08eedd61a3a492a425ced0dbbd57676415, which is what actions/checkout gives a pull_request run. Not the PR head.

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

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin c42de6639a0275cade2bd652ea0073cb6ebc06b8 && git checkout c42de6639a0275cade2bd652ea0073cb6ebc06b8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 663b7c08eedd61a3a492a425ced0dbbd57676415 f72ef4f72c41e360be5fced77465bc68b23a1c7a && git checkout -B drift-repro 663b7c08eedd61a3a492a425ced0dbbd57676415 && git merge --no-ff f72ef4f72c41e360be5fced77465bc68b23a1c7a

node scripts/docs-audit/affected-docs.mjs --json 663b7c08eedd61a3a492a425ced0dbbd57676415

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

Contract review

Served-tier: TIER_DEFAULT
Head-sha: f72ef4f72c41e360be5fced77465bc68b23a1c7a
Local-runs: none

Face: the committed tree touches no published schema and no governed path (record-recognisers --paths on the seven changed files decides no path face and defers to the changeset's declaration line), and the declaration carries no (narrowing) arm, so the review is owed on the declaration face alone and is served at TIER_DEFAULT. Read at 2026-11-01T22:02Z, read-only: card #28697 (the claim and the os-dev-report), PR #28822 (body, five commits, the net diff against the merge-base c064d3a2: seven files, +569 / −12), PR #28790 (merged 899782b5) with both records on it, the cited code on the head and on origin/main. Nothing built, run or re-run. The PR author is objectstack-fleet[bot]. Check-runs on the head at the read: 0 failure; Check Changeset, Governed Surface Queue Guard, PR-body guards, Lint · ESLint, Lint · PM dispatch-gates, Dogfood Regression Gate, Spec property liveness, Type Check · source gates and Test UI are success; Build Core, the eight Test Core shards, three lint lanes, three type-check lanes and Live E2E UI are in progress. Landing pre-check ② (all green) is not met yet at this read; the seat re-reads at landing. Draft, mergeable; no needs:contract-review marker was hung.

Security class: verdicts in product words. Mechanism detail, the write-by-write completeness table and the follow-up observations are in a private note with the seat.

① Derived judgments

  1. A refused upgrade leaves the installed version serving, whole. When a package id that is already installed is re-registered and the registry refuses the new version at any point of the registration, what the engine serves afterwards reads exactly as before the attempt: the package record and its version, every object as served and as layered, every app and item, picklist options and extensions, namespaces, security-catalog names, default record page holders, the engine's package entry and datasource routing. I read every registry state field against the write sites the registration reaches, and each one the registration can write is recorded before its first write and put back on a throw; the engine's own entries were already written only after a whole registration and still are. Another package's extension of the installed version's objects and a tenant's overlay layer are put back as the same entries, unchanged. Entries the registration did not write are neither recorded nor touched, so other packages' and tenants' entries do not move. Right. Pinned for every refusal the registry can raise during an upgrade (ten unit cases, one dogfood case through POST /marketplace/install-local), each asserting the same code and status as before and a whole-state equality with the reading taken before the attempt.
  2. Not through uninstall, and safe. The restore calls no removal verb; it puts entries back in place. The registration it runs is synchronous by contract (a body that returned a promise is refused, and what the synchronous part wrote is still put back), a nested call joins the open run, no hook or listener exists on the registry to re-enter it, and every undo step is a plain container operation that cannot itself throw. The restore applies only to an installed id, and PR fix(objectql): a package refused while it registers leaves nothing of itself registered #28790's withdrawal applies only to an id that is not installed, so the two paths are disjoint by construction and a first install keeps fix(objectql): a package refused while it registers leaves nothing of itself registered #28790's behaviour unchanged (its file reads 24/24 with exactly one control flipped into the new behaviour). Right.
  3. The choice to build the restore without the ahead-judgment. The order asked the dev to measure which part-way refusals can be judged before the registry's first write before choosing, and preferred judging ahead where possible. The measured split is in the private hand-back: half of the part-way refusals depend on registry state and need the restore whatever else is built, and with the restore in place judging the rest ahead changes no outcome for an upgrade, would be an unpinned second call site of each judge, and would change which of two faults a body is refused for. The stop condition was not needed. I read the order's preference as one to weigh against the measurement it demanded, and the measurement supports the restore alone for the in-scope path. Acceptable; the os-dev-report's open question is answered under ③.
  4. No change to what is refused or its words. Every refusal keeps its code, status and message (each case pins code and status; the dogfood case pins the message); nothing is newly refused and nothing refused is admitted. Right.
  5. The new public method. One public method on the published SchemaRegistry, taking one synchronous registration and returning its result; the journal and its record points are private. That is the minimal door the engine needs, since the record points have to sit inside the registry's own verbs. The doc comment is accurate on what is recorded, on what is never recorded (the operator's removal verbs), on the synchronous contract and on nesting; one phrase ("re-resolved") is loosely worded for a cache drop. A third party calling it gets what it says: a restore of registry writes, nothing for a removal verb, and a clear error for an async body. The spec contract the engine exposes its registry through is structural and still satisfied. The shared registry test double runs the body as given, which is right for a double that never answers an installed package. Right.
  6. Controls and ablations (as reported, not re-run): a clean upgrade behaves as before and keeps the other package's extension and the tenant overlay; a refusal the package door raises before its first write leaves the installed version as before; the other package's own entries do not move; red-first on the base engine, registry and double reds exactly the ten upgrade cases plus the flipped control (each passing its code-and-status assertion and failing at the state comparison) and the dogfood case; ten ablations each red exactly the cases that reach the record point removed, the re-resolve ablation included. The counts match which cases reach which record point in the fixture as I read it. Consistent with the diff.
  7. Evidence left to CI. check:dual-build-cjs-loads was NOT MEASURED locally (it needs every package's build output); it is a step of the Build Core job, in progress on the head at the read. The remaining 67 derived gate families exited 0 in the dev's record, with 0 unrun.
  8. Overlap and drift. Of the 33 open PRs at the read, only feat(metadata-protocol,objectql): an object extension is an authorable item of a writable package, saved, published and exported through the existing doors (#28574) #28811 (draft) touches one of the four files, registry.ts, in hunks away from this PR's (a refusal block at the top of the object verb and new extension verbs further down); no textual overlap, and the one semantic touch point is consistent with this PR's documented contract. origin/main moved the engine and registry once since the merge-base (fix(objectql,cloud-connection)!: a field patch reaches the owner's own fields only, at the fold and the install door; the install door refuses it with 422 (#28733) #28791); the branch merges clean. No conflict.

② Semver level

One changeset, @objectstack/objectql at patch; Clause-②: yes (widening) with a dash-clause naming the one new public method on the published SchemaRegistry; Metadata-protocol: none. Both declarations are line-initial in the PR body and the changeset. AGENTS.md rule 3: the level follows the protocol kind alone, so none keeps patch, and a non-protocol widening ships patch; check-changeset-no-major.mjs reads yes (widening) + none + patch as consistent and Check Changeset is green on the head. The diff enlarges the public surface by exactly that one method and changes nothing an author can write; the spec's engine contract is untouched. No (narrowing) arm and no spec path, so the face is the declaration face at TIER_DEFAULT. Consistent.

③ Boundary flags

  • Boundary change. Nothing newly refused, nothing refused admitted; only what a refused upgrade leaves behind changes. Public surface: one new method (②).
  • Order rulings. A refused upgrade leaves the installed version serving, whole, with other packages' extensions and tenant overlays intact: met (① 1). Not through uninstall: met (① 2). The split measured before choosing, the restore of exactly the touched entries built, the ahead-judgment left out with the reason recorded: acceptable reading (① 3). No change to refusal codes or words: met (① 4). First installs keep fix(objectql): a package refused while it registers leaves nothing of itself registered #28790's withdrawal: met (① 2). The stop condition was not reached.
  • Dev flags and open_questions. One open question: also judge the body-only refusals ahead of the first write (A: restore only, this PR; B: a separate card for the one first-install case the private hand-back names). Answered: A for this PR (① 3); B only if the seat wants that first-install case closed, as a separate card, since it flips one of fix(objectql): a package refused while it registers leaves nothing of itself registered #28790's controls. One out-of-scope finding, the same first-install case: correctly left out, the order scopes this PR to upgrades and keeps first installs on fix(objectql): a package refused while it registers leaves nothing of itself registered #28790's behaviour; its existing control pins it.
  • Security class. The PR body, commits and changeset are in product words; nothing in them reads as a recipe. One optional trim for the seat is named in the private note. The test sources enumerate the cases they pin, as any test of a fix does; noted, acceptable.
  • CI state. 0 failures; Build Core (which hosts the one locally unmeasured gate), the Test Core shards, three lint lanes, three type-check lanes and Live E2E UI in progress at the read; landing pre-check ② is the seat's at landing.
  • File surface and author. Seven files, inside the claim's surface; author objectstack-fleet[bot]; draft, no auto-merge.

Implemented-by: claude/project-thread-cjuysm
Reviewed-by: session_0186vfjHHMsRXC9EUT9hTFMo

VERDICT: PASS

No must-fix. Should-fix, none required before landing: the one optional body trim and the doc-comment wording are in the private note with the seat.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo

席位核对

ACCEPT · domain:server seat 2 · os-project-manager · session_0186vfjHHMsRXC9EUT9hTFMo · 2026-11-01T22:02Z · head f72ef4f7 · second follow-up to #28697

The maintainer's standing authorisation for landing reviewed PRs, verbatim: 「按照 pm-dispatch 规则复核过的都授权」. The maintainer's 「所有的安全卡不要创建issue,直接派发处理修复」 is why this work has no issue of its own: it carries Refs #28697, and its claim is on that card (6563704354).

  1. Shape:
    • A draft against main with Refs #28697 and a patch changeset for @objectstack/objectql, declaring Clause-②: yes (widening) (one new public method on the published registry) and Metadata-protocol: none.
    • Not governed, +569 / -12. Landing tier S.
    • The claim names this PR's branch, claude/project-thread-cjuysm.
  2. Against the order:
  3. Seat's decision on the dev's open question: A, the restore alone, for this PR. It is the measured reading of the order, and the review finds it acceptable.
  4. Contract review: PASS on this head (6564524139), with no must-fixes. The seat applied the one body should-fix through the relay, which leaves the head unmoved.
  5. Gates: CI on this head has 0 red, 33 success and 5 pending (including Build Core, which carries the dual-build check the dev could not measure). Auto-merge waits for them.
  6. Landing: pr_ready + automerge_enable through the relay.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 1, 2026 22:04
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 1, 2026 22:04
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 1, 2026
Merged via the queue into main with commit 2587485 Nov 1, 2026
58 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-cjuysm branch November 1, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant