Skip to content

fix(plugin-sharing): let system writes materialize sharing rules — drop the isSystem skips in bindRuleHooks (#13533) - #14528

Queued
claude[bot] wants to merge 8 commits into
mainfrom
claude/issue-13533-system-write-sharing-materialization
Queued

fix(plugin-sharing): let system writes materialize sharing rules — drop the isSystem skips in bindRuleHooks (#13533)#14528
claude[bot] wants to merge 8 commits into
mainfrom
claude/issue-13533-system-write-sharing-materialization

Conversation

@claude

@claude claude Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes #13533

A criteria sharing rule declares a promise — status == "approved" means the named recipients can see the record. bindRuleHooks did not keep that promise when the platform was the writer, so an approval write-back left a teammate unable to see an approved record until somebody ran POST /api/v1/sharing/rules/:id/evaluate or restarted the server. This removes the skips that caused it.

Implements the maintainer ruling of 2026-08-31 (issue comment 5472662038), quoted verbatim and untranslated:

裁定:系统写参与逐记录共享物化 —— 删除 plugin-sharing 两个钩子里的 isSystem 跳过,⛔ 不加声明式开关、不以文档代修。

The approval path, cited

The write the card is about is mirrorStatusField in packages/plugins/plugin-approvals/src/approval-service.ts (called from the decision, recall, reject and return paths). It writes:

const context = actorId ? { ...SYSTEM_CTX, userId: actorId } : SYSTEM_CTX;
await this.engine.update(object, { id: recordId, [field]: status }, { context });

SYSTEM_CTX is { isSystem: true, positions: [], permissions: [] }. Elevation is not incidental here: an approval node with lockRecord: true means only a platform write can land while the record is locked, so this is the only write that can carry the decision onto the record. plugin-approvals is read-only in this PR — cited, not edited.

What changed

Three early returns are gone from bindRuleHooks, not two.

site hook why
afterInsert the ruling's first named skip a system insert into a rule's criteria owes the grant
afterUpdate the ruling's second named skip this is the approval write-back
stashAffectedRows (beforeUpdate / beforeDelete) the second gate on the same path see below

The third one is load-bearing, and removing only the two named skips would have shipped a different defect. afterUpdate reads its row set through readAffectedRows, which answers { kind: 'unbounded', reason: 'resolve-failed', detail: 'no before-hook stash' } when no before hook stashed. With the stash still skipping system writes, every single-row system update — every approval — would have taken revokeThenQueueRegrant: an object-wide revoke of every rule grant plus an asynchronous re-grant, with a warn per approval. Ablation leg B below measures exactly that. Resolving is nearly free on this shape anyway: a write that names its row short-circuits in resolveAffectedRows step 1 without querying.

The SYSTEM_WRITE_SKIP_NOTICE channel retires with the skips. No declarative switch was added and no documentation substituted for the fix, per the ruling.

What deliberately did not change

afterDelete keeps its isSystem skip. Its payload is revocation, not materialisation, and record-share-cascade.ts delivers that on every sharing-capable object, stashing for system writes on its own account (#5103), with the boot orphan sweep behind it. This is one subscriber declining work another subscriber owns, not elevation used as a blanket silencer — the distinction the ruling itself draws.

bu-tree-recompute.ts is untouched. Its docblock describes the opposite disposition and is a different subject: BU-tree recompute deliberately does not skip system writes, because its payload is revocation on a path whose realistic production trigger (an HRIS or directory sync) is itself a system write. Nothing there was a materialisation skip, so nothing there was in the ruling's scope.

The kernel:bootstrapped boot backfill stays. It is no longer needed for seed rows, but it still reaches rows no hook saw — written before an object's hooks were bound, or while a rule was inactive — and it is the only pass that purges a deactivated rule's grants (#4433).

Acceptance anchor: the member perspective, without viewAllRecords

Triage's binding note (issue comment 5479116055) is that this defect is observable only to a principal without viewAllRecords who depends on the sharing rule. A manager or admin reads through the profile path, never consults the rule, and sees the record either way — "the manager sees it within 18 s" is true and is not a counter-proof.

The acceptance pins therefore end at SharingService.buildReadFilter for a plain member context and then run that filter against the table, so the assertion is that the teammate can see the record, not merely that a row appeared in sys_record_share:

  • before the write-back, buildReadFilter for the member is { owner_id: 'rep2' } and the record is not admitted;
  • after it, { $or: [ { owner_id: 'rep2' }, { id: { $in: ['lr1'] } } ] } and the record is admitted — additive access, the owner match still present;
  • and a companion pin asserts the approver's filter is null both before and after, which is the reproduction constraint stated as a test: verifying this fix from a manager view proves nothing.

Controls in the same file: a non-system write still materialises (the user path did not move); a system write that does not satisfy the criteria grants nothing; a system write on an object no active rule covers is a no-op; a recall revokes again.

The bulk-path census (ruling point 2), measured before disposal

The ruling forbids keeping the skip on unmeasured performance fear, and asks for the bulk system-write paths to be censused first. Measured on the merged tree:

path system write? reaches rule-covered objects? bound trailing batch already?
SeedLoader (packages/metadata-protocol/src/seed-loader.ts, SEED_OPTIONS = isSystem + skipTriggers + seedReplay) yes yes, any author-declared dataset BULK_BATCH_SIZE 200 per flush, dataset unbounded yesbackfillRuleGrants at kernel:bootstrapped, after every kernel:ready handler settles
claimSeedOwnership (plugin-security) yes yes, every non-sys_, non-managed, non-external object declaring owner_id two scans at limit: 10_000, written as single-id updates in a loop at boot, yes (same backfill); on its runtime replay, no
REST import runner (packages/rest/src/import-runner.ts) nowriteCtx is the importer's own context n/a n/a already per-record today; untouched by this card
data migrations (metadata-protocol/src/migrations/*) yes nosys_/cloud_/ai_ namespace only n/a n/a
plugin-sharing's own grant writes yes no — hooks bind only on objects carrying an active rule, and sys_record_share carries none n/a n/a

skipTriggers does not suppress these hooks: engine.ts skips only metadata-bound hooks and only under skipAutomations (if (skipAutomations && entry.meta)), and seed writes set skipTriggers. So the seed path really does reach bindRuleHooks.

Disposition: no new batch code. The ruling's remedy — replace per-record with one trailing batch on the bulk path — already exists for both shapes the hooks can see, and removing the skips is what makes system writes reach it:

  • a write the hooks can see as bulk (predicate/multi) over RULE_RECOMPUTE_ROW_CAP (1000) already takes revokeThenQueueRegrant into evaluateAllRulesForObject;
  • boot-phase seeding already has backfillRuleGrants reconciling every rule behind it.

What remains is per-record cost on single-row writes, and that is not new amplification: it is the cost a user write of the same shape has always paid. An executable pin (#13533 census: a system write costs exactly what the same user write costs) measures the two populations against each other rather than asserting an absolute — a 25-row system insert batch performs exactly the reads a 25-row admin batch performs, and the cost is linear in the batch. Adding a batch shortcut keyed on isSystem would reintroduce precisely the special case the ruling condemned. The one path the hook layer cannot help is claimSeedOwnership's loop of single-id writes, which is a writer-side shape in another package; it is reported to the PM rather than folded in.

Reverse verification

Both legs mutate the committed tree, prove the mutation on disk before reading anything, and prove the restore afterwards. No rebuild is involved or needed: the suites import ./rule-hooks.js, a same-package relative specifier vitest resolves to src/, and the mutation turning tests red with no rebuild is itself the proof that no dist/ sits in the resolution path.

Leg A — restore the afterUpdate skip. Marker present x1, blob moves to cb675403… off HEAD 23baa61d…. Result: Tests 5 failed | 44 passed (49) — the two approval write-back acceptance pins, both bulk-update directions, and the reversed bulk-recompute case. Every control stayed green.

Leg B — restore only the before* stash skip, leaving both after skips removed. Marker present x1, blob b8e9c653…. Result: Tests 4 failed | 45 passed (49), and the discriminating one is takes the BOUNDED per-record branch, never the object-wide revoke, which reds only in leg B. That is the measurement behind the third removal.

Baseline unmutated: Tests 49 passed (49). After every leg the restore is proved by blob equality with the HEAD blob plus an empty git diff HEAD, with an empty hash treated as failure rather than as "nothing to compare".

Verification

All readings at 376c04e00, after the last commit and after merging origin/main (7251bdb30). Re-run in full for patch round 1 — see that section for what moved.

  • pnpm --filter @objectstack/plugin-sharing testTest Files 30 passed (30), Tests 718 passed (718) (712 before patch round 1; main's fix(plugin-sharing): report a refused resolveToken usage stamp once as a durability degradation — #12981 batch 9 #14383 added six share-link cases).
  • pnpm --filter @objectstack/plugin-sharing typecheck — clean, and it now genuinely covers the test layer: check:test-typecheck: OK — @objectstack/plugin-sharing's test layer compiles under packages/plugins/plugin-sharing/tsconfig.test.json; 2 file(s) / 3 error(s) / 3 pinned signature(s) held in test-typecheck-debt.json. Those 3 are pre-existing entries in two files this PR does not touch.
  • Gate families derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands after the last commit — 69 commands over 9 paths. 64 green. Five are NOT MEASURED, every one a "build the whole repo first" prerequisite on packages this diff never touches, and each says so in its own verdict: check-test-completeness (exit 3, "Nothing was measured"), check:skill-examples ("packages/client-react/dist holds no .d.ts declarations"), check:dual-build-cjs-loads (exit 3, "PREREQUISITE NOT MET"), check:i18n ("PREREQUISITE NOT MET — the workspace CLI is not built"), check:type-check-debt (exit 3, "This is NOT a pass and NOT a finding"). CI builds the closure before those steps. Every exit code was captured before any pipe.
  • check:system-context-censusOK — 106 elevation read sites in 20 packages across 45 files, all anchored; 140 anchors resolve, 27 declared non-read.
  • check:engine-double-contract — green after --write repathed the renamed test file: "692 (file, verb) row(s), 2 added or grown, 2 lost. No seam losses". The script's own --self-test passes too.
  • Lint, narrowed — and the narrowing is measured, not assumed. (1) Population read from ESLint's own config: eslint --print-config claims all 5 lintable changed files, none ignored. (2) File count read from --format json: 5 files reported, 0 errors, 0 warnings, exit 0. (3) Invariance for untouched files: this repo runs one eslint.config.mjs which "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file" — the config states this itself, with a measured positive control — so no verdict on a file this diff does not touch can move. The repo-wide pnpm lint remains CI's run.

Documentation

content/docs/permissions/system-context.mdx is edited because a required gate demands it, not as a rider. check:system-context-census holds that page to a census of every isSystem read site — every site must be anchored at its exact file:line, and the page's census-derived counts must equal the census. Three sites vanished, so seven counts moved (109 → 106 elevation reads, 20 → 17 for plugin-sharing's share, 115 → 112 property reads, 105 → 102 behaviour-bearing). regen-artifacts.mjs records that the --fix path refuses a population change, so the rows were rewritten by hand: row 30 now describes the surviving afterDelete skip alone, and rough edge 2 records the reversal instead of the retired INFO notice.

Reversal register, not deletion

The two pins of the old behaviour are reversed and registered. system-write-skip-notice.test.ts becomes system-write-materialisation.test.ts carrying a row-by-row table of every #6783 expectation against its #13533 counterpart, including the four that are unchanged and why. bulk-recompute.test.ts keeps its case under a docblock recording what it used to assert, what reversed it, and the ruling's own words. Neither pin was a mistake; both pinned a real, separately-ruled behaviour that a later ruling overturned.

Contract review (Clause ②)

Declared yes, re-derived from the actual diff, which flips the dispatch's claimed no. git diff -U0 origin/main...HEAD | grep export reports exactly one exported symbol removed and none added:

-export const SYSTEM_WRITE_SKIP_NOTICE =

The published package surface is unchanged: plugin-sharing declares a single "." entry point resolving to src/index.ts, and index.ts re-exports bindRuleHooks, unbindAllRuleHooks, bindRuleCriteriaGuard, ruleRegrantQueue, SHARING_RULE_HOOK_PACKAGE and RULE_CRITERIA_GUARD_PACKAGE from this module — never SYSTEM_WRITE_SKIP_NOTICE. Its only consumer was the pin file renamed here. The declaration is yes because the dispatch's rule is mechanical about any exported symbol, and being over-conservative costs a label; the substantive review question is the behaviour change, which the ruling already settled. needs:contract-review is hung on this PR and on #13533.

Changeset is patch for that reason — no published export moves — and the change is a bug fix, not a declared-breaking one, so it carries no ADR-0087 marker. No new error-level log site through a published sink shape; the retirement removes an info line and adds none.

Patch round 1

origin/main moved to 7251bdb30 while this PR was open and the merge went dirty on exactly one path: content/docs/permissions/system-context.mdx. No code file conflicted.

What conflicted, and why it was not a real overlap. That page is routed to the os-regen merge driver, which refuses to text-merge it — so git stops there whenever both sides move it, regardless of whether the edits touch the same lines. They did not. Main's side (#14383, the #12981 batch 9 share-link work) moved row 37's share-link-service.ts anchors by +6. This branch had moved row 36's sharing-plugin.ts anchor by +11 and rewritten row 30, rough edge 2 and the counts. The resolution therefore takes row 37 from main and row 36 from this branch — each side's own row, neither discarded.

How it was verified. Not by inspection: the page was re-derived against the merged tree, and check:system-context-census reports OK — 106 elevation read sites in 20 packages across 45 files, all anchored; 140 anchors resolve, 27 declared non-read. The census total is unchanged at 106 because main added and removed no elevation read sites.

Re-run on the merged head 376c04e00, after pnpm install --frozen-lockfile and a rebuild of the dependency closure (main moved packages/objectql/src, which this package builds against):

  • pnpm --filter @objectstack/plugin-sharing testTest Files 30 passed (30), Tests 718 passed (718).
  • pnpm --filter @objectstack/plugin-sharing typecheck — clean; check:test-typecheck: OK — 2 file(s) / 3 error(s) / 3 pinned signature(s), the same pre-existing entries.
  • Gate families re-derived on the new base — still 69 commands over 9 paths, an identical set (nothing arrived, nothing left). 64 green, the same five NOT MEASURED for the same repo-build prerequisites.
  • Both ablation legs re-run and reproduce exactly: leg A Tests 5 failed | 44 passed (49), leg B Tests 4 failed | 45 passed (49) with the bounded-branch pin still the discriminating failure, baseline Tests 49 passed (49), and the restore proved by blob equality plus an empty git diff HEAD after every leg.

Patch round 2 — documentation only, after the A ruling

The maintainer ruled A on 2026-09-02 (verbatim, untranslated: 「#13564 转维护者处理;其他同意」 — decision batch #11, this card item 1, recommendation A; recorded at 13533#issuecomment-5511791709). Ruling point 2 is closed by the census plus the cost-equivalence measurement already in this body: no boot-phase skip predicate is added, and meta resync stays per row, carried by #14530. The three removed skips, the reversed pins and the changeset level are untouched by this round.

This round carries the contract review's non-blocking §5 notes 2, 3 and 4, plus this body's note-5 omissions. New head e9b612a7a.

No code path changed. The round's own diff is three files — the changeset, one census-page cell and one docblock — and every added or removed line in the only .ts file among them is a JSDoc body line (*): zero non-comment lines. No ablation is owed and none was run; with no behaviour change there is no red for a mutation to produce, and running one would measure the previous round's code.

  • note 2 — content/docs/permissions/system-context.mdx, row 30. "Lose: nothing" glossed the one shape where the cascade's delivery is the queued orphan sweep rather than a synchronous revoke. The cell now opens "Lose: nothing permanently — the revoke is delivered, but deferred on the unbounded shape" and says which shape: when the deleted ids are enumerable the cascade revokes inline; when they are not — a predicate delete whose row set the stash could not resolve — it hands the reclaim to a queued background orphan sweep, so the share rows outlive the deleted records until that sweep runs. No surviving record loses access either way, and a restart re-runs the same sweep. Prose only: no anchor and no count was hand-edited, the generator owns those.
  • note 3 — packages/plugins/plugin-sharing/src/bu-tree-recompute.ts docblock. It still described bindRuleHooks' materialisation skip as current ("…is about grant MATERIALISATION, which the boot backfill re-does anyway"), a sentence this PR makes false. It now says that bindRuleHooks no longer skips system writes — the afterInsert / afterUpdate materialisation skips and the before* stash skip that fed them are gone, and the one skip it keeps is afterDelete revocation, which record-share-cascade.ts delivers instead — and that this file's own hooks never carried an isSystem branch to skip with. That last claim is checked against the file's whole history, not just the head: the only isSystem line ever added to it is its SYSTEM_CTX constant. Nothing else in the file changed.
  • note 4 — .changeset/system-write-sharing-materialization.md. One operator sentence added: seed- and import-time system writes on rule-covered objects now pay per-record sharing evaluation at write time — the cost a user write of the same shape has always paid, with the kernel:bootstrapped backfill still reconciling behind it. And "unexported" now reads "not exported from the package entry point", matching what the file already said correctly further down. Level stays patch; no ADR-0087 marker is owed.

note 5 — what this body omitted

Comment-only edits this PR carries. sharing-plugin.ts (docblock corrections at the rule-hook binding and at the kernel:bootstrapped handler) — its whole diff against origin/main has zero non-comment changed lines. boot-backfill.test.ts (file header rewritten) — likewise zero. bulk-recompute.test.ts carries an ADMIN_SESSION explanatory comment alongside the reversed pin this body already describes.

A behavioural side effect this body did not state. With the before* stash skip removed, the beforeDelete stash now runs for system deletes on rule-covered objects, so hook ordering flips: the rule package (priority: 180, rule-hooks.ts:177) resolves the affected row set first, and the cascade (priority: 190, record-share-cascade.ts:283) then reads the stashed answer instead of resolving its own (bulk-recompute.ts:304-305). Cost-neutral — one resolve either way; only which subscriber pays for it changes.

The merge

origin/main had moved to 20b883918 and the PR read mergeable_state: dirty on the same single path as round 1: content/docs/permissions/system-context.mdx. No code file conflicted; sharing-plugin.ts and scripts/engine-double-contract.pinned.json auto-merged.

That page is routed to the os-regen merge driver, which for this MIXED file — generated anchors plus hand-written prose — refuses to keep either side whole and text-merges instead, so it conflicts whenever both sides move it. One hunk conflicted, two rows wide, and the two rows belong to opposite sides: row 36 is this branch's (it moved the sharing-plugin.ts anchor by the +11 comment lines this PR added above it), row 37 is main's (#14580, landing #14033, rewrote its prose — link creation is bypassed, redemption is not — and moved its five share-link-service.ts anchors). The resolution takes each side's own row; neither is discarded.

Proven by the gate, not by inspection. The merge was committed first — never regenerate in MERGE state — then pnpm gen:system-context-census ran on the merged tree and rewrote 0 anchors, i.e. the hand resolution already agreed with the merged tree, and check:system-context-census reports OK — 106 elevation read sites in 20 packages across 45 files, all anchored; 140 anchors resolve, 27 declared non-read. Both sides' content survives on the merged page: main's row-37 sentence about redemption, and this branch's row-30 rewrite plus rough edge 2.

Verification on the final head e9b612a7a

Every command ran through scripts/pm/os-verify-lock.sh, each exit code captured before any pipe; the lines quoted are the gates' own verdicts.

  • pnpm --filter @objectstack/plugin-sharing testTest Files 30 passed (30), Tests 729 passed (729). The delta from round 1's 718 is entirely main's: this round's diff touches no test file at all, and main's fix(plugin-sharing): hold publicSharing.enabled at redemption, not only at mint (#14033) #14580 added a net 11 cases across share-link-eligibility.test.ts and share-link-service.test.ts (15 it( lines added, 4 removed). 718 + 11 = 729.
  • pnpm --filter @objectstack/plugin-sharing typecheck — clean, and the test layer is genuinely covered: check:test-typecheck: OK — @objectstack/plugin-sharing's test layer compiles under packages/plugins/plugin-sharing/tsconfig.test.json; 2 file(s) / 3 error(s) / 3 pinned signature(s) held in test-typecheck-debt.json — the same pre-existing entries in two files this PR does not touch.
  • pnpm check:system-context-censuscheck-system-context-census --self-test: all cases passed, then check-system-context-census: OK — 106 elevation read sites in 20 packages across 45 files, all anchored; 140 anchors resolve, 27 declared non-read.
  • pnpm check:doc-authoring — clean on all four legs: 388 files with no bare metadata literals, 41 published skill files with no internal issue-id references, 14500 customer-facing strings across 710 spec sources clean, and 831 pinned sibling-prose sites across 231 files with "no growth, no burn-down unrecorded".
  • node scripts/check-changeset-no-major.mjs — "This diff introduces no major bump."
  • node scripts/check-adr-0087-registration.mjscheck-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).
  • node scripts/check-empty-changeset.mjs — "No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added)."
  • pnpm check:nul-bytescheck-nul-bytes: OK (scanned 7996 text file(s) -- 7996 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).
  • pnpm check:engine-double-contractcheck-engine-double-contract: OK — 754 pinned, 134 in the DEBT ledger, 3 exempt. (753 in round 1; main added one pinned row, this round added none.)
  • Gate family re-derived on the final head with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands69 commands over 10 paths (round 1: the same count over 9 paths; the tenth is bu-tree-recompute.ts, which brought no additional command). Run in seven foreground batches under the container's cap: 64 green, and the same five NOT MEASURED as round 1, each declaring its own unmet prerequisite rather than a finding — check-test-completeness (exit 3, "Nothing was measured"), check:skill-examples ("packages/client-react/dist holds no .d.ts declarations"), check:dual-build-cjs-loads (exit 3, "PREREQUISITE NOT MET"), check:i18n ("PREREQUISITE NOT MET — the workspace CLI is not built"), check:type-check-debt (exit 3, "This is NOT a pass and NOT a finding"). CI builds the closure before those steps.

Clause ② stays yes — this round adds and removes no export — and needs:contract-review remains hung on this PR and on #13533 for the seat to clear.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8

Generated by Claude Code


Generated by Claude Code

`bindRuleHooks` returned early on `ctx.session.isSystem` in `afterInsert` and
`afterUpdate`, so a system-context write that moved a record into a sharing
rule's criteria materialized no `sys_record_share` row. Approval write-back is
exactly that write — an approval node with `lockRecord: true` mirrors the
decision onto the subject record under a system context, because while the
record is locked no other write can land — so a teammate who depended on the
criteria rule could not see an approved record until somebody re-evaluated the
rule or restarted the server.

Removes three early returns: the two named above, and the one on the
`beforeUpdate` / `beforeDelete` row-set stash they depended on. That third one
is load-bearing: without a stash `readAffectedRows` reports the row set as
`unbounded`, which would have sent every single-row system update down the
object-wide revoke plus asynchronous re-grant branch instead of the per-record
recompute. Retires the `SYSTEM_WRITE_SKIP_NOTICE` channel that announced the
skip.

`afterDelete` keeps its skip on separate grounds — its payload is revocation,
which `record-share-cascade.ts` delivers for system writes on its own account.

The two pins of the old behaviour are reversed and registered rather than
deleted: `system-write-skip-notice.test.ts` is renamed to
`system-write-materialisation.test.ts` and carries a row-by-row register of
every old expectation against its new counterpart, and `bulk-recompute.test.ts`
keeps its case under a docblock recording what it used to assert and why.

`content/docs/permissions/system-context.mdx` is updated because
`check:system-context-census` requires it: the page anchors every `isSystem`
read site and states census-derived counts, and three sites vanished.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
…uble ledger after merging main

Discharges the os-regen deferral the merge commit recorded.

`content/docs/permissions/system-context.mdx` is os-regen-driven and both sides
moved it, so the merge sequence took main's side and this commit re-applies the
branch's hand-written half on top: row 30 now describes the one surviving skip
(`afterDelete`, whose payload `record-share-cascade.ts` delivers) instead of all
four, rough edge 2 records the reversal rather than the retired INFO notice, and
the seven census-derived counts move with the population. Re-derived on the
merged tree rather than carried over — `check:system-context-census` reports
"OK — 106 elevation read sites in 20 packages across 45 files, all anchored;
140 anchors resolve, 27 declared non-read".

`scripts/engine-double-contract.pinned.json` learns the test file's new path.
The two rows are repathed, not renumbered: `--write` reports "692 (file, verb)
row(s), 2 added or grown, 2 lost. No seam losses — this regeneration only
records new seams."

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
…ain merge

Patch round 1. `content/docs/permissions/system-context.mdx` is os-regen-driven
and both sides moved it again, so the merge stopped there and neither side could
simply win: main's #14383 (#12981 batch 9) moved row 37's `share-link-service.ts`
anchors by +6, while this branch moved row 36's `sharing-plugin.ts` anchor by +11
and rewrote row 30 plus rough edge 2. The resolution takes row 37 from main and
row 36 from this branch — the two sides moved different rows, so there was no
real overlap, only the driver's refusal to text-merge a generated page.

Verified by re-deriving rather than by inspection: `check:system-context-census`
reports "OK — 106 elevation read sites in 20 packages across 45 files, all
anchored; 140 anchors resolve, 27 declared non-read".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/plugins/plugin-sharing/src/bu-tree-recompute.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/plugins/plugin-sharing/src/bu-tree-recompute.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 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.

Coarse fallback — 8 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 4d0d9445a8ed0240e7ca6a393bbe7f4c637e6bd6packageMentionDocs.

Which tree this was computed on

This run read content/docs from 8679ceb0669befec570997248e0c6e199c7f51ee — the merge of head 645b14e9eeb0d898ba2ecd52c020a0564d1cc10d into base 4d0d9445a8ed0240e7ca6a393bbe7f4c637e6bd6, 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 8679ceb0669befec570997248e0c6e199c7f51ee && git checkout 8679ceb0669befec570997248e0c6e199c7f51ee
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4d0d9445a8ed0240e7ca6a393bbe7f4c637e6bd6 645b14e9eeb0d898ba2ecd52c020a0564d1cc10d && git checkout -B drift-repro 4d0d9445a8ed0240e7ca6a393bbe7f4c637e6bd6 && git merge --no-ff 645b14e9eeb0d898ba2ecd52c020a0564d1cc10d

node scripts/docs-audit/affected-docs.mjs --json 4d0d9445a8ed0240e7ca6a393bbe7f4c637e6bd6

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

…stem-write-sharing-materialization

# Conflicts:
#	content/docs/permissions/system-context.mdx
…block, operator cost sentence

Documentation-only follow-up on the contract review's non-blocking §5 notes
2, 3 and 4. No code path changes: the only .ts edit is a docblock body.

- system-context.mdx row 30: "Lose: nothing" glossed the one shape where the
  cascade's delivery is the queued orphan sweep rather than a synchronous
  revoke. The cell now says the revoke is delivered but deferred on the
  unbounded shape.
- bu-tree-recompute.ts: the docblock still described bindRuleHooks'
  materialisation skip as current. It now says what bindRuleHooks does after
  this change and why this file's own hooks never skipped system writes.
- changeset: one sentence on the operator-visible cost of seed- and
  import-time system writes on rule-covered objects, and "unexported" becomes
  "not exported from the package entry point". Level stays patch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8

os-sales commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Landing provenance — ready + auto-merge at head e9b612a7a


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33674211749 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test:  FAIL   integration  test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
      ↳ 失败原因: @objectstack/cli:test: AssertionError: expected 'SIGKILL' to be null
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 18 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 2, 2026

os-sales commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Queue ejection triaged — this failure is not this PR's, and I am standing down on it rather than patching

Queue build 33674211749 ejected this PR. The failing job is Test Core (1/6), and the failing test is:

@objectstack/cli:test:  FAIL   integration  test/run-dev-unbuilt-workspace.e2e.test.ts
  > the mirror direction: a reader that is never coming back
  > gives up and exits instead of waiting forever
    ↳ AssertionError: expected 'SIGKILL' to be null

Why it is not this PR's, on three readings rather than an assertion:

  1. Package. The failing test is in @objectstack/cli. This PR's whole diff is packages/plugins/plugin-sharing, content/docs/permissions/system-context.mdx and a changeset. It touches no CLI file, no runtime the CLI boots, and no shared script that test loads.
  2. Cross-PR signature. The same test file has ejected 8 independent pull requests in a rolling 24 hours — fix(objectql,runtime,rest): store a serializable manifest projection in the package registry #14499, fix(devx): guard the last two run-on-import gate scripts — KNOWN_IMPORT_UNSAFE 2 to 0 #14593, lint(flows): warn on an uncontained loop body and on a try_catch with no catch, and document loop { try_catch } #14617, test(driver-sql): give the seven unconditionally-live hooks an explicit 60_000 budget #14629, fix(cli): let the dev artifact door own stack-declared security metadata on a host config #14631, fix(metadata): drop the leftover boot-time debug probe from MetadataPlugin.init #14649, fix(objectql,metadata): derive a view container's object through the shared helper, so the row's own name is LAST at every SOURCE registrar (#14399) #14665, fix(cli): remove the ghost field types from generate.ts's three vocabularies #14675 — across unrelated lanes. Anchor: Queue-flake anchor: test/run-dev-unbuilt-workspace.e2e.test.ts #14706. A break that hits eight unrelated diffs is a property of the test, not of any of them.
  3. The failure class. The assertion's subject is the kill signal the harness had to send, i.e. whether the child exited inside a time budget — the load-sensitive class the triage comment distinguishes from an assertion on product behaviour. It is a timeout wearing an AssertionError's clothes.

No fix exists to port. #14706 is explicit that it is a name and not a diagnosis: nothing there is graded, assigned, or in flight, and no PR proposes a repair. There is therefore nothing for this PR to carry, and ⛔ widening this PR to repair a CLI e2e test is not on the table — it would put an unrelated, unreviewed change inside a diff that carries a maintainer ruling.

What I am doing: re-queueing once, and saying so here so the count is on the record. That is the single re-run this seat allows for a load-sensitive failure, and it is the discriminating act the queue-triage comment itself sanctions for this class. ⛔ If the same signature ejects this PR again, I will not re-queue a second time — it would burn a full queue round for every PR behind it. The PR would then wait on #14706, and this comment is where that wait is recorded rather than left in a session's memory.

The seat's evidence has been added to #14706 for whichever lane owns packages/cli.


Generated by Claude Code

@os-sales
os-sales added this pull request to the merge queue Sep 2, 2026
Any commits made after this event will not be merged.

os-sales commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Correction to my earlier standing-down comment: a fix does exist and it has an owner

At 19:59Z I wrote that "no fix exists to port" and that #14706 was ungraded with nothing in flight. That was accurate when written and is no longer true, so I am correcting it rather than leaving the PR's record stale.

#14706 has been graded through the emergency direct-triage channel and closed as a duplicate of #14648, which is the working anchor for the same test: priority:p1, domain:cli, dispatched, with a dev on claude/issue-14648-run-dev-unbuilt-workspace-flake since 18:15Z. So the wait this PR is recorded as having has a named owner and an in-flight branch, not an empty queue.

Why the fix still is not ported into this PR. The rule that says to port an existing fix rather than wait assumes the fix is one this PR may carry. It is not, on two independent counts:

  1. The repo's own gate forbids it. The fix lands in packages/cli/test/run-dev-unbuilt-workspace.e2e.test.ts, which Queue-flake anchor: test/run-dev-unbuilt-workspace.e2e.test.ts #14648's branch already claims. CI's No other open PR may claim the same single-writer path check exists to refuse exactly that, so carrying it here would trade a queue ejection for a red gate.
  2. It would cross a lane. packages/cli is domain:cli; this PR is the domain:services seat's. Porting another lane's in-flight change into this diff would put an unreviewed edit inside a PR that carries a maintainer ruling.

Failure class, now read from the test rather than from the bot's summary: the asserted SIGKILL is produced by the harness's own setTimeout(() => child.kill('SIGKILL'), UNREAD_HARD_CAP_MS) at run-dev-unbuilt-workspace.e2e.test.ts:186, with the 40 s cap at :153 derived from a measured ~22 s uncontended run. Under a loaded queue shard the budget is simply gone. It is a stopwatch firing, not a product behaviour — which is what makes it not this PR's, on any diff.

This PR is still queued and still not being manually re-queued. Confirmation that it is not stuck on itself: #14687, this seat's other victim of the same test, merged at 20:21Z on a later auto-re-queue of the same unchanged head.


Generated by Claude Code

os-sales commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Dequeued with MERGE_CONFLICT — real, measured, and it is on the census page again

This is a different blocker from the flake, and it is this PR's to fix. GitHub removed it from the queue at 20:56:07Z with reason: MERGE_CONFLICT. Verified independently rather than taken from the notice — git merge-tree --write-tree --name-only origin/main <head> at origin/main 279431e7a against head e9b612a7a:

⚠ content/docs/permissions/system-context.mdx
   NOT deferred: the incoming side carries hand-written changes that no regeneration can restore.
   This file is MIXED — a generated half plus hand-written prose …
   Text-merged instead, and it CONFLICTS.
Auto-merging content/docs/permissions/system-context.mdx
CONFLICT (content): Merge conflict in content/docs/permissions/system-context.mdx

Both readings agree, which is the test that matters here: the os-regen driver text-merged and got a content conflict, not merely refused the file, and GitHub reached the same verdict on its own. One path, no code file. This is the third time this page has conflicted on this PR (patch round 1, patch round 2, now) — it is the repo's hottest path and five open PRs currently claim it.

The resolution is already prescribed and is not a judgement call: resolve the prose by hand taking each side's own rows, commit the merge first, then run pnpm gen:system-context-census on the merged tree and let check:system-context-census be the proof. ⛔ The page is never hand-edited for its anchors — the generator owns those.

What is holding it, stated plainly rather than left as silence. This seat is at its dispatch ceiling: the maintainer set the concurrent-dev cap at 3 and four dev agents are running right now (#14484, #14373, and patch rounds on #14712 and #14718 — a patch round counts). I over-committed by one when I opened the second patch round, and the honest correction is to drain rather than to interrupt work already in flight or to add a fifth. The merge round for this PR is first in line the moment a slot frees, and nothing else will be dispatched before it.

No manual re-queue. A merge conflict is not something the queue can retry around — unlike the flake, this one does not clear itself, so re-queueing would be pure noise.

The PR's content is unchanged and still carries its adopted ruling: the review's DECISION was read as PASS after the maintainer ruled A at 15:11Z, patch round 2 is documentation-only, and both Clause-② carriers were cleared with a read-back and check-clause2-carriers --pair 14528 at exit 0. Nothing about the change is reopened by this — only the page needs re-merging.


Generated by Claude Code

…stem-write-sharing-materialization

Resolves the single CONFLICT (content) in
content/docs/permissions/system-context.mdx (the os-regen driver flags
this file as MIXED — generated anchors plus hand-written prose — so it
text-merges instead of taking one side whole).

Row 37 (share-link policy `enabled` check, isSystem) diverged: this
branch's copy already carried the #14033 prose ("resolution is not
bypassed since #14033 ... a link minted this way does not resolve
until the block is enabled"), added when 13bf05d landed on both
branches. main's copy had reverted to the pre-#14033 wording via an
unrelated commit (8ce628a, "store a serializable manifest projection
in the package registry (#14499)") whose own docs-regen step appears
to have run against a stale pre-#14033 checkout of this file.

Kept this branch's row 37 text: packages/plugins/plugin-sharing/src/
share-link-service.ts:652-697 (the `[#14033]` docblock and the
`if (!policy.enabled)` redemption-time check it documents, citing the
maintainer's 2026-09-01 ruling and this exact ledger row by number)
confirms it is what the current source does, not the reverted main
copy. Row 36 differed only in its generated anchor line number
(1088 vs 1077) — anchor numbers are recomputed by
`pnpm gen:system-context-census` below, so either side was equivalent;
kept this branch's.

Anchors re-derived on the merged tree with
`pnpm gen:system-context-census` in a follow-up commit, per the
os-regen driver's documented recovery for this file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
@os-sales
os-sales added this pull request to the merge queue Sep 2, 2026
Any commits made after this event will not be merged.

os-sales commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Landing provenance — merge round complete, re-armed at 22:1xZ

domain:services seat, session session_01AUF1NoViznQK32gqpK8wS8.

The conflict is resolved, verified by the seat rather than taken from a report

The dev agent that ran this merge round was killed by a container restart before it could report, so the seat verified the work directly against the tree:

check reading
conflict cleared git merge-tree --write-tree --name-only origin/main <head>exit 0, tree oid only, no conflict paths, against origin/main 4d0d9445a
GitHub agrees mergeable_state moved dirtyblocked (waiting on checks, not conflicting)
how the page was resolved the merge commit's effect on content/docs/permissions/system-context.mdx is 18 insertions / 18 deletions — pure line-number re-anchoring, the generator's own signature. ⛔ No prose was hand-edited, which is what the os-regen driver requires and what would otherwise have silently deleted one side's rows

CI on 645b14e9e

34 check runs, all success or skipped, zero failures — including Lint & Repo Gates (so the census regeneration held on the merged tree), TypeScript Type Check, all six Test Core shards, Dogfood Regression Gate, Temporal Conformance (live PG + MySQL), and No other open PR may claim the same single-writer path — the last of which matters here because scripts/engine-double-contract.pinned.json is currently contended three ways (this PR, #14726, #14572).

⭐ Worth noting: the six Test Core shards passed this time. The run-dev-unbuilt-workspace.e2e.test.ts flake that ejected this PR from the queue at 19:35Z, and again took #14687, #14712 and #14718, did not bite on this run — consistent with the load-sensitive reading, and with #14687 having merged on an unchanged head via a later auto-re-queue.

Nothing about the content was reopened

The merge round was scoped to the conflict and the regenerated artefacts. The PR's substance is unchanged and still carries what it carried before it was dequeued: the maintainer's A ruling of 2026-09-02, the contract review's DECISION read as PASS after that ruling, patch round 2 verified documentation-only (every changed .ts line a JSDoc body line), and both Clause-② carriers already cleared with a read-back, check-clause2-carriers --pair 14528 at exit 0.

Auto-merge is armed. ⛔ No manual re-queue will be spent on the flake if it ejects this PR again — that is #14648's, already dispatched in domain:cli, and a manual re-queue costs every PR behind this one a full rebuild in exchange for a coin flip.

On MERGED: strip pm:dispatched from #13533 and release rule-hooks.ts / sharing-plugin.ts / bu-tree-recompute.ts.


Generated by Claude Code

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33689105623 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test:  FAIL   integration  test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
      ↳ 失败原因: @objectstack/cli:test: AssertionError: expected 'SIGKILL' to be null
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️ 断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 23 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

os-sales commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Re-queued at 23:0xZ — and this reverses a written commitment of mine, on a measurement that falsified its premise

At 22:32Z this PR was dequeued when its merge-queue batch failed on the repo-wide run-dev-unbuilt-workspace SIGKILL flake (7th occurrence this shift; anchor #14648). I stated then that I would not manually re-queue it, on the stated premise that the repo re-queues a flake-dequeued PR on its own, so a manual re-queue only spends a second queue slot on the same flake.

That premise is false, measured, not assumed. At 23:00Z:

  • The last merge_group run for this PR is gh-readonly-queue/main/pr-14528-4d0d9445a, created 22:11:25Z, conclusion failure. No pr-14528-* batch exists after it.
  • The queue was neither stalled nor empty in that window — it formed and started five other batches: pr-14658 (22:49:59Z), pr-14652 (22:50:33Z), pr-14664 (22:51:08Z), pr-14660 (22:51:09Z), pr-14681 (22:52:25Z). So a live, moving queue passed this PR by for ~28 minutes.
  • And enable_pr_auto_merge succeeded just now rather than reporting an existing arming — i.e. the dequeue had also cleared auto-merge. There was nothing left armed for the repo to re-queue with. My premise was wrong on both halves.

I am recording the reversal rather than quietly re-queueing, because the commitment was made in public on this PR and the reason it is being dropped is a fact anyone can re-measure from the merge_group run list.

State at re-arm, both readings taken at 23:0xZ, not recalled:

  • 34 / 34 check runs success on head 645b14e9e (Lint & Repo Gates, all four Type Check lanes, Test Core 1–6/6, Dogfood Regression Gate 1–3/3, Temporal Conformance (live PG + MySQL), Build Core, Build Docs, and the repo guards); two skipped by their own conditions (Console Pin Gate, Packed-tarball smoke (opt-in)).
  • Mergeable against current origin/main dbf115284 — not against the base the last batch used: git merge-tree --write-tree origin/main 645b14e9e exits 0 with zero CONFLICT lines. The 22:11Z batch was built on 4d0d9445a; main has moved twice since and the merge is still clean, so no merge round is owed.

Auto-merge re-armed, SQUASH. This is the one manual re-queue this PR gets: if the next batch fails on the same flake again, that is a second data point about the flake and not a reason for a third arming, and it goes to #14648 rather than back into the queue.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants