Repository navigation
fix(pm-dispatch): gate the worktree-rescue step with the three adjudicated fences - #13473
Merged
Merged
Conversation
…cated fences Fixes #12637 The stale-claim reclaim step prescribed committing a rescue WIP into any dispatch worktree with no liveness precondition, and its message template implied absence claims the rescuer cannot observe. Write the three fences from the 2026-08-28 grading into the standing step: 1. Liveness precondition: any actor passes a liveness/ownership check or an explicit age threshold on the worktree's newest mtime before committing; GitHub-side silence alone is never grounds (a working unpushed seat shows the identical signals; uncommitted changes evidence work, not death). 2. Message boundary: the WIP message states only rescuer-observables (dirty paths, line counts, sha); no present-tense claims about what the seat did or did not run. The INCOMPLETE AND UNREVIEWED header stays, scoped to the rescuer's own act. 3. Mis-hit remedy: a rescue that hits a live seat is corrected additively by that seat in the PR body, never by history rewrite (force-push is forbidden from a dev seat). Funding per the R1 PM disposition (option A, 2026-08-30): the fences land in the headroom freed by #13389 (943 -> 947 of 1005, net +4 faithful spelling); the one in-passage deletion is the last-line-of-defense rationale clause, whose dev-already-dead premise is exactly what fence 1 corrects. No ceiling change, no re-wrap, no unrelated deletions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EXxTW8mvPBhoHxmyPZ63de
os-zhuang
approved these changes
Aug 30, 2026
os-zhuang
marked this pull request as ready for review
August 30, 2026 14:09
os-zhuang
enabled auto-merge
August 30, 2026 14:10
os-tesla
pushed a commit
that referenced
this pull request
Oct 25, 2026
…namic import (objectui#13450) (#13473) Fixes #13450 Clause-②: no (narrowing) Metadata-protocol: narrowed Retires the `module` widget source. `WidgetRegistry.load()` resolved `{ type: 'module', url }` with `new Function('url', 'return import(url)')`. That is the runtime dynamic import AGENTS.md commandment `#6` forbids ("No `eval()` / runtime dynamic imports to load components"). The claim is the PM's comment `6386913269` on the card, for branch `claude/issue-13450-retire-widget-module-source`, session `https://claude.ai/code/session_01432GBvPzfZ6CsLanp9xzkZ`. ## What changes - **`@object-ui/types`**: the `module` member and its `WidgetSourceModule` interface are removed. `RuntimeWidgetSource` is now `WidgetSourceInline | WidgetSourceRegistry`, and the barrel no longer exports `WidgetSourceModule`. The union's docblock records the retirement and the migration. The manifest's JSDoc `@example` now uses `{ type: 'inline', component }` with a locally defined `CustomChart` component (the build-time import is named in a comment, so the block compiles in isolation under `check-doc-example-types`) instead of a URL. - **`@object-ui/core`**: the `case 'module'` arm and its Function-constructor sink are removed from `WidgetRegistry.resolveComponent()`. The `default:` arm is now exhaustive (`const unhandled: never = source`), so a union member added without an arm fails to compile. - An untyped manifest that still reaches `load()` with `type: 'module'` is refused by name before anything is fetched. Untyped means plain JS, a cast, or an older `@object-ui/types`. The error names the widget, the retired `"module"` source and both replacements. - Any other unknown type is refused with the supported types listed. - In both cases `load()` emits `widget:error` and `loadAll()` reports the error. The class docblock and its example no longer mention URL loading. - **Changeset** `.changeset/13450-retire-widget-module-source.md`: `minor` for core and types. objectui never declares `major` (`check-changeset-no-major`); the break is stated in the body as **BREAKING**. The changeset gives the FROM → TO migration and the one-line fix, and carries the `Clause-②:` / `Metadata-protocol:` lines. - `page-nav-misc-spec-parity.test.ts`: the reason string in one `it.each` row said "module/inline/registry loader union" and now says "inline/registry". It is a test title, not an assertion. ## Migration (FROM → TO) - FROM `source: { type: 'module', url: '/widgets/chart.js', exportName: 'Chart' }` - TO `import { Chart } from './widgets/chart'` and `source: { type: 'inline', component: Chart }` (imported at build time, so the bundler sees it) - OR `ComponentRegistry.register('chart', Chart)` once, and `source: { type: 'registry', registryKey: 'chart' }` ## Premise check: the card's "four emitting sites" are not widget sources The card and the triage route name four `type: 'module'` producers. Each one was read in context at `a3c0da9`. All four are the generated **`package.json`'s ESM marker** (`"type": "module"`), not a widget manifest source. Deleting them would make the scaffolded packages CommonJS and break their `import` syntax. **None was changed:** | Site (by symbol) | What it builds | Verdict | |---|---|---| | `buildAppPackageJson` in `packages/cli/src/utils/app-generator.ts` | temp app `package.json` (`name`, `private`, `type`, deps) | package.json ESM marker; kept | | `buildRoutedAppPackageJson` in the same file | routed temp app `package.json` | package.json ESM marker; kept | | `buildInitPackageJson` in `packages/cli/src/commands/init.ts` | `objectui init` project `package.json` (`scripts.dev: 'vite'`) | package.json ESM marker; kept | | `buildPackageJson` in `packages/create-plugin/src/templates.ts` | generated plugin `package.json` (`main`, `module`, `exports`) | package.json ESM marker; kept | So `@object-ui/cli` and `@object-ui/create-plugin` carry no change and no changeset, which narrows the route. No widget-manifest producer of a `module` source exists in this tree. A `git grep` for `WidgetSourceModule` before this change hit only its declaration and the barrel. `apps/console/src` and `packages/app-shell/src` construct no `module` source (triage's reading). `objectstack` and `hotcrm` reference neither `WidgetRegistry` nor `RuntimeWidgetSource` in code (the only `objectstack` hit is a doc paragraph that does not name the `module` source). Out-of-tree consumers are NOT MEASURED. No user-facing doc names the `module` widget source. `content/docs`, the package READMEs and `docs/` were checked with `git grep`; the control `ComponentRegistry` hits in `content/docs`. The two JSDoc examples were the only documentation of it, and both are rewritten. ## Tests (head `e15036c`) - `be01ada` (patch round): `check-doc-example-types` exit 1 → 0 (34 → 35 compile; `WidgetRegistry #1` back on its TS2304 ledger row). JSDoc text only. New `describe('the retired \`module\` source (objectui#13450)')` in `packages/core/src/registry/__tests__/WidgetRegistry.test.ts`: 1. A manifest with `source: { type: 'module', url }` is refused. The rejection is an `Error` that names `"test-widget"`, `"module"`, `type: 'inline'` and `type: 'registry'`; these are the named subjects, not the wording. The global `Function` constructor, proxied for construct and apply, records zero calls. Nothing is marked loaded or synced into the component registry. The literal carries `@ts-expect-error`, so the type-level refusal is pinned too: core's `type-check` runs `tsconfig.test.json`, which includes `src/**/*.test.ts`. 2. `loadAll()` reports the refusal for that widget, a sibling still loads, and `widget:error` fires. 3. An unknown type (`remote`) is refused with `"inline"` and `"registry"` named and no "retired" wording. - `pnpm exec vitest run packages/core/ --maxWorkers=2`: `Test Files 211 passed (211)`, `Tests 4089 passed | 27 skipped (4116)` - `pnpm exec vitest run packages/types/ --maxWorkers=2`: `Test Files 388 passed (388)`, `Tests 10568 passed (10568)` - `pnpm --filter @object-ui/types --filter @object-ui/core run type-check`: both `Done`, exit 0. This ran after both packages were rebuilt, so core's test program read the new `dist/*.d.ts`. **Ablation, runtime** (one-shot, through `ablation-replace.mjs`, restored to the HEAD blob): a `case 'module'` arm with the Function-constructor import was re-planted ahead of `default:`. On-disk marker count was 1, sink count 1. Result: `Tests 2 failed | 24 passed (26)`. Test 1 received `"A dynamic import callback was not specified."` instead of the refusal; test 2 failed the same way. Test 3 stayed green, which is the expected direction. After the restore, `git diff HEAD` was empty and the blob equalled HEAD. A first attempt used an anchor that the replacement re-contained; the tool refused it and nothing ran. **Reverse verification, types** (cross-package): a `{ type: 'module'; url: string }` member was re-added to `RuntimeWidgetSource` and `@object-ui/types` rebuilt; the dist marker count was 1. Core's `tsc -p tsconfig.test.json` then reported `TS2578: Unused '@ts-expect-error' directive` on the new test and `TS2322 ... is not assignable to type 'never'` on the exhaustive arm. After the restore and a rebuild, the dist marker count was 0 and the same `tsc` was green. ## Gates (local, at `e15036c`) Exit codes were captured to a file before reading: - `lint` (core + types): exit 0. 0 errors; the warnings are all pre-existing `no-explicit-any`, and none is on a changed line. - `check:new-line-citations` 0 · `check:control-bytes` 0 · `check-changeset-presence` 0 · `changeset:check` (fixed + no-major) 0 · `check:changeset-claims` 0 · `check:pending-changeset-literals` 0 · `check:spec-symbols` 0 · `check:unreferenced-sources` 0 · `check:doc-example-readers` 0 · `check:shell-escape-residue` 0 · `check:test-path-roots` 0 - `check:readme-exports`: exit 1, NOT MEASURED for the repo as a whole. The only unjudged population is `@object-ui/app-shell`, which was unbuilt; every other package was judged with `0 fabricated, 0 wrong-path`. No README names `WidgetSourceModule`. - `check:published-dist`: exit 1, NOT MEASURED. Its internal whole-workspace `turbo run build` hit a heap OOM in `@object-ui/app-shell`, so it judged nothing. That is environmental and not this diff. CI owns this gate. `origin/main` was merged before opening; it had not moved from the branch base `a3c0da9`, so the merge was a no-op. objectui#13442 is in flight in the same batch on disjoint files (`packages/core/src/actions/**` and the `DataSource` type). The only shared surface is the `@object-ui/types` barrel, where this PR removes a single line in the widget block. ## Acceptance notes - Remote widgets as a product capability (a signed manifest and an allow-listed origin, decided in spec) are the maintainer's call and are not part of this card, per the triage ruling. - `register()` still accepts a manifest with a retired or unknown source, and the refusal happens at `load()`. That matches the ruling's "refuse an old manifest loudly in `default:`" and how the registry already treats a missing `registryKey`. --- _Generated by [Claude Code](https://claude.ai/code/session_01432GBvPzfZ6CsLanp9xzkZ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #12637
Writes the three adjudicated fences from the card's 2026-08-28 grading (取卡者围栏, 缺一不可) into the standing worktree-rescue step of the stale-claim reclaim passage,
.claude/skills/pm-dispatch/SKILL.md(passage now lines 553-562; anchorINCOMPLETE AND UNREVIEWEDre-derived on merged mainc38b7eff7at :556 before editing).dispatch-runbook.mduntouched — no split was needed.The three fences, and where each landed
INCOMPLETE AND UNREVIEWEDheader stays, now explicitly scoped on line 559: 「抬头限救树者行为,保留」.The one sanctioned in-passage deletion (per the R1 PM disposition, option A, 2026-08-30T07:16Z): the last-line-of-defense rationale clause 「死在编辑中途的 dev 没来得及自己 WIP 推,这一步是最后防线」 (79 bytes at line 557 pre-edit) — its dev-already-dead premise is exactly what fence 1 corrects. Everything else in the passage is preserved verbatim, including 「续派者 diff 它,⛔ 不无审续建」 and 「有带提交活分支的认领永不回收」. No ceiling change, no re-wrap-as-funding (2026-08-17 ruling), no unrelated deletions. Faithful (+4) spelling chosen over the floor (+2) per the dispatch's preference: this is protocol text about honest evidence bounds, and the two faithful adds are the rationale and the no-force-push note.
Line budget
Net +4 lines (8 insertions, 4 deletions), inside the headroom freed by #13389. Every touched line is 116 bytes or less (widest new line: 116).
Gates — union run at final commit
01769913b, clean tree, exits captured by redirect-then-captureDerived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackin the worktree (change set: exactly this one file). All nine matched families:@objectstack/formulaand@objectstack/lintbuilt first; the first two runs printed the gate's own PREREQUISITE NOT MET banner — not-measured, not red)Stale-tree note: origin/main advanced to
d38ad7fc5during the run (three commits); none touches.claude/skills/pm-dispatch/or any matched gate script, so the family list and the budget arithmetic stand. Base refresh before the human merge is the PM's landing step per the grading.Reverse verification (fix committed first; both restores proven by hash equality against the HEAD blob plus empty
git diff HEAD)The gate reads the source file directly from the working tree (no dist, no exports resolution), so no build leg applies; each mutation was proven on disk before reading the gate (marker grep hit count plus measured line count/width).
git hash-objectequals HEAD blobd4abba29c2f7b1385d9d363cc7dd81148a40fec3,git diff HEADempty, under an EXIT/INT/TERM trap with absolute paths.Governed surface posture
Diff is
.claude/**only — this PR stays DRAFT; no ready flip, no queue, no auto-merge, no review from any seat. The maintainer lands it by hand (or via the pinned-approval path) after the PM's ACCEPT review.skip-changesetapplies (publishes nothing from any package; closed-list diff class).Generated by Claude Code
Generated by Claude Code