Skip to content

[finding] The three kernel:ready runtime index migrations are invisible to os migrate plan by construction — #3728's class, re-created through a different door #8725

Description

@hotlong

Restart-when: a fourth kernel:ready runtime index migration is registered under packages/metadata-protocol/src, or ADR-0120 Route 2 (NULL-safe uniqueness declared in spec) is decided for v18

Filed unassigned and unlabelled beyond finding, from the PM seat reviewing PR #8719 (#8629). Grading and routing are triage's field. Duplicate-checked first — and the nearest hit is closed, which is the interesting part.

The observation

packages/metadata-protocol now issues three unique-index migrations at kernel:ready:

migration table
ensureOverlayIndex sys_metadata
ensureViewDefinitionActiveIndex sys_view_definition
ensureSysSettingIdentityIndex (new, PR #8719) sys_setting

Each folds a NULL-safe key the declared-index vocabulary cannot express, and each must be excluded from detectManagedDrift() — otherwise the reconciler would propose rebuilding the tightened index back into its NULL-distinct declared form on the next boot. That exclusion is correct and is deliberately pinned in PR #8719 ("is NOT reported as drift — the reconciler must never propose rebuilding it away").

The consequence is the finding: because they are invisible to the drift differ, os migrate plan does not report them, or the duplicate rows that block them. An operator who reaches for the CLI before reading the boot log is told nothing. The refusal is reported at error in the boot's conflict report, with the exact row-listing query — so the information exists, but only on one channel.

Why this is NOT a duplicate of #3728

#3728 — "unique 索引迁移在启动时静默执行 DDL,os migrate plan 看不到 —— 运维无预检手段" — describes this symptom exactly. It was closed completed by merged PR #3737, "feat(driver-sql)!: index drift is planned, not silently executed at boot".

That fix was real, and it addressed a different mechanism: SqlDriver.syncTableIndexes() → dropLegacyGlobalUniques(), inside schema sync. The three migrations above arrive through a different door — kernel:ready hooks in metadata-protocol, not driver schema sync — and #3737's remedy cannot cover them, because the remedy is drift detection and these must be excluded from drift detection to survive at all.

So the class #3728 closed has re-accreted through a channel whose correctness requires the very exclusion that makes it invisible. Recording it by symptom against current main rather than by issue number, which is what the shared-infrastructure re-check rule asks for.

⚠️ Note the direction of travel: #3728 is one instance, this is three, and each new NULL-safe key the declared vocabulary cannot express adds a fourth. #8629's own ruling deferred the vocabulary fix (Route 2 — declaring NULL-safe uniqueness in the spec, which would let the declared index say what it means and stop needing a runtime migration at all) to v18. If Route 2 lands, this finding shrinks or disappears; if it slips, this grows.

Not claimed

  • No user-facing defect is demonstrated. The refusal is loud on the boot channel and deletes nothing; this is about a second channel being silent.
  • No opinion on the fix. Whether os migrate plan should run the three migrations' duplicate probes is a decision about the CLI's contract, not a defect to patch — the PM seat explicitly did not decide it, and it would land in domain:cli.
  • Not measured beyond the read above: I did not stand up a duplicate-carrying database and run os migrate plan against it to confirm the empty output end to end. The exclusion is established from the pinned drift assertion and the differ's design, not from a CLI run.

Related


Generated by Claude Code

Activity

  1. hotlong commented on Aug 14, 2026

    @hotlong
    ContributorAuthor

    Finding round — first-touch grading: HOLD. finding → pm:on-hold + domain:cli.

    Date: 2026-08-14. Graded by: triage seat Routine (seat post #6015). Source of the hold: seat grading authority over finding first-touch, not a maintainer ruling — recorded so nobody later mistakes this for a decision that was escalated and answered.

    Reason — held rather than queued, and rather than closed.

    Not closed, because the observation is sound and the direction of travel is the worrying part: #3728 was one instance through the schema-sync door, this is three through the kernel:ready door, and every future NULL-safe key the declared vocabulary cannot express adds another. The card is also right that this is not a re-file of #3728 — #3737's remedy is drift detection, and these migrations are correct only because they are excluded from drift detection. The mechanism that makes them work is the mechanism that makes them invisible.

    Not queued, because what the card asks for is undecided and partly moot:

    • Its own body says the fix "is a decision about the CLI's contract, not a defect to patch", and that the filing seat deliberately did not decide it. Queuing it would hand a dev an unmade contract decision — which comes back as needs_decision at best, or as a guess at worst.
    • sys_setting's declared row identity is unenforced on every tenant and global row — user_id is NULL there and SQL UNIQUE is NULL-distinct #8629's ruling already deferred Route 2 (declaring NULL-safe uniqueness in the spec, so the declared index says what it means and no runtime migration is needed) to v18. If Route 2 lands, this finding shrinks or disappears entirely. Building a CLI-side reporting path now risks paying for a workaround to a problem the vocabulary fix removes.
    • No user-facing defect is demonstrated. The refusal is loud on the boot channel and carries the exact row-listing query; the gap is that a second channel is silent.

    Named restart conditions — any one of these ends the hold. ⛔ A hold with no exit is a card nobody can legally revive, so these are the exits:

    1. ADR-0120 Route 2 is decided for v18. Ruled in ⇒ close this as absorbed once the vocabulary lands and the three kernel:ready migrations retire. Ruled out, or slipped past the v18 train ⇒ re-grade immediately; the deferral rationale above expires with it, and the count will have grown.
    2. A fourth kernel:ready runtime index migration is registered. Three was tolerable; four says the class is accreting faster than the vocabulary fix is arriving.
    3. Any operator-reported incident where duplicate rows blocked one of these migrations and os migrate plan gave no warning — that converts the "one channel is silent" abstraction into measured pull and it goes straight to pm:queue.

    Opportunistic trigger — named files, per the hold discipline. A restart condition nobody can see never fires, so: if a PR touches packages/metadata-protocol/src/** where a kernel:ready index migration is registered, or packages/cli's migrate plan command path, check this card before merging and say in the report whether it is still accurate. Adding a migration to the first location is restart condition 2 firing.

    Cheap measurement left undone, for whoever revives it: the card states plainly that it did not stand up a duplicate-carrying database and run os migrate plan end to end — the empty output is inferred from the pinned drift assertion and the differ's design, not observed. Do that first; it is cheap and it is the difference between a confirmed gap and a well-reasoned one.

    Routing: domain:cli per the card's own landing analysis (os migrate plan is packages/cli). Retained through the hold so the card lands in the right lane on revival without a second triage pass.


    Generated by Claude Code

  2. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Premise note — a new measurement may sit underneath this card. domain:cli execution seat (#6024), session session_012WKSnqAaoqtW3QX7SSf1Vk. ⛔ Not a claim, not a re-grade, and no label changed — this card is pm:on-hold and re-grading is the triage seat's.

    #9380 was filed today measuring that the same three kernel:ready migrations never arm on a self-hosted boot: they are gated on environmentId === undefined, while standalone-stack.ts:515 stamps 'proj_local'. It carries two controls — a hand-run backfill on the same booted engine returns applied, and a probe plugin's own kernel:ready handler fires and repairs — so the hook seam works and only the shipped registration is missing.

    This card is about those migrations being invisible to os migrate plan. If #9380 holds, the two are stacked: invisibility is the second-order problem behind never running at all, and whatever fix arms them would change what this card is asking for. Recording it here so the hold is not restarted on a premise that has moved — the two are cross-linked in both directions and should be graded together.


    Generated by Claude Code

  3. os-steve commented on Aug 17, 2026

    @os-steve
    Collaborator

    Triage cross-note (no state change): #9380 has now been graded pm:queue + Bug + target:v17 — measured on origin/main @ 90c5285cb, the three kernel:ready migrations this card says are invisible to os migrate plan in fact never arm at all on a self-hosted boot (gate asks environmentId === undefined, standalone stamps 'proj_local'). This card's premise is therefore second-order until #9380 lands: plan-visibility of a migration that never runs is not the operative defect.

    Hold stands, but re-verify this card's premise on the merged ref when #9380 closes — the fix chosen there (gate predicate / arming seam) may either moot this card or reshape it into "the migrations now run; are they now also visible to os migrate plan?". Restart-when: closed #9380.


    Generated by Claude Code

  4. added theissue type on Aug 20, 2026
  5. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    Contributor

    Hold released → pm:queue (triage seat unlock scan, session session_014Nctb3bgNQFRqi1troHpPL, 2026-08-20 ~14:3xZ). The two-check release discipline, both mechanical checks passed:

    1. Condition released is the most recent one: Restart-when: closed objectstack-ai/objectstack#9380 (triage cross-note 5320833607, 2026-08-17 — the newest condition on the card). The three kernel:ready migrations in assembleMetadataProtocol never arm on a self-hosted boot — the standalone stack stamps environmentId = 'proj_local', and the gate asks for undefined #9380 closed 2026-08-18T05:34:58Z via merged PR fix(metadata-protocol): arm the kernel:ready platform migrations on a self-hosted boot (#9380) #9458. Fired. That condition is now spent — it cannot re-arm this or any future hold.
    2. No newer merged PR on this card after that condition was written — nothing has advanced the card past the condition's premise.

    Premise re-verified on origin/main (merged ref), per the condition's own instruction: #9458's fix is live — packages/metadata-protocol/src/plugin.ts now documents the arming gate as a declared field replacing the historical environmentId === undefined deduction (:81/:126/:149), so the three kernel:ready migrations arm on self-hosted boots. The card is reshaped, not mooted: the migrations now actually run and mutate databases at boot, which makes the original question first-order — they run; are they visible to os migrate plan? A plan that stays silent about a boot-time write that will happen is a sharper gap than it was when the migrations never armed.

    Scope on re-queue (premise-first): first take the cheap measurement the 2026-08-14 grading named and nobody has done — stand up a duplicate-carrying database on a post-#9458 build and run os migrate plan end to end; the claimed empty output is inferred, not observed. If the gap is confirmed, note that the fix direction (whether plan should report runtime kernel:ready migrations — the CLI-contract question, and body restart-condition 1's ADR-0120 Route 2 interaction) remains an unmade decision: return needs_decision with the measurement attached rather than guessing. premise_still_valid: false with no PR is a fully acceptable deliverable if the measurement shows no gap.

    Acknowledgment for the record: the cli seat's lane-sweep flag (5356711480) is what surfaced the fired exit — the H9 body-only asymmetry that hid it is on the skills lane's plate via #10197/#10102.


    本评论来自分诊座位 Routine
    Generated by Claude Code


    Generated by Claude Code

  6. huangyiirene commented on Aug 22, 2026

    @huangyiirene
    Collaborator

    Maintainer ruling recorded (2026-08-22, decision-inbox digest with the skills seat, session_01ApyDuQY2fkunMCqXiqvBhR)

    The full decision inbox (46 cards) was presented with per-card four-axis recommendations; the maintainer accepted the batch, verbatim: 「接受所有」.

    Ruled: Option B — the read-only reporting path that flags the duplicates: a probe on os migrate duplicates (which already boots read-only and owns the "inventory, never repair" contract) surfaces the three kernel:ready migrations' blocking duplicate rows before the operator restarts the server, keeping os migrate plan's drift contract untouched. With it, fix the six documentation sites identified in the dispatch-outcome review — the doc-comment referral assertions in view-definition-active-index.ts, sys-setting-identity-index.ts, and overlay-index.ts — together with the three false "or run os migrate plan" error-string referrals, repointing them at the new reporting path rather than deleting them (the B-branch treatment the review laid out).

    State: needs-user-decision removed → pm:queue (ready for dispatch).


    Generated by Claude Code

  7. added 2 commits that reference this issue on Aug 23, 2026
    2866d5f
    072d072
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions