Skip to content

fix(pm): derive the always-runs tail dispatch-gates could never name - #13501

Merged
os-project-manager merged 2 commits into
mainfrom
claude/issue-13333-dispatch-gates-always-runs-tail
Aug 30, 2026
Merged

os-project-manager merged 2 commits into
mainfrom
claude/issue-13333-dispatch-gates-always-runs-tail

Conversation

@claude

@claude claude Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #13333

Triage adjudicated option (b) on this card — have dispatch-gates enumerate the
unconditional Lint & Repo Gates steps as an "always runs" tail — and explicitly
refused option (a), adding check-reference-carrier-shape to the derivation. This
implements (b), derived rather than hand-listed.

What was measured first

Reproduced on current origin/main, with positive controls, because the card's own
figures had drifted:

reading card measured now
reference-carrier in dispatch-gates.mjs 0 0
positive control where-matcher, same file 12 14 (triage saw 14 too)
check-reference-carrier-shape in lint.yml :3281-3284 :3338-3339
the gate anywhere in a full --residue universe of 187 families — 0 hits; controls turbo-task-graph 1, where-matcher 2

So the gate is not in the silent bucket and not in undetermined — it is in no
bucket at all
, the one output shape this file's contract forbids.

H1 as stated is FALSIFIED, and the falsification changes the fix

The sharpened mechanism on the card is "every package-local script invoked by path
inside a multi-line run: | block"
. Measured over all 26 workflows:

A fix built on that mechanism would therefore have been option (a) with extra steps.
The population that is real is the one triage named — unconditional steps — and
it is invisible for at least three unrelated reasons: a package-local path, an
interpreter that is not node, and a root script not named check:*. That is why no
fourth widening of the three matchers was going to be the last one.

The coverage measurement triage asked for

Derived live, on the merge commit of this branch:

  1. unconditional steps CI runs on every PR — 191, across the pull-request workflows
    with no paths: filter, in jobs and steps with no if: (11 conditional jobs and 12
    conditional steps excluded, and both counts printed rather than dropped);
  2. accounted for by a discovered family — 164;
  3. named by nothing, i.e. the new tail — 27 step instances / 16 distinct commands.

Proof that it reaches the CLASS, not the instance

The tail names the card's gate and members invisible for different reasons:

- [lint.yml · Lint & Repo Gates] ESLint
      pnpm lint
- [lint.yml · Lint & Repo Gates] Verify-lock entry-point self-test
      bash scripts/pm/os-verify-lock.sh --self-test
- [lint.yml · Lint & Repo Gates] os-regen-merge self-test
      bash scripts/pm/os-regen-merge.sh --self-test
- [lint.yml · Lint & Repo Gates] Claude hook guard self-tests (worktree-first · stash ban)
- [lint.yml · Lint & Repo Gates] Relationship carriers are spelled as the string the spec declares
      node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test
- [lint.yml · Type Check · consumer gates] Root-entry type nameability pin (built dist, declaration emit)
      pnpm --filter @objectstack/spec exec vitest run scripts/root-entry-type-nameability.pin.test.ts

pnpm lint and the two bash … --self-test steps are real verification that no
widening of the node scripts/… matchers would ever have reached
.

That claim is a self-test case, derived live, not a sentence here: the tail must contain
a member whose interpreter is not node and that member must not be the card's own
gate. Reverse verification (ablation, run against the committed implementation, the
mutation confirmed on disk by object hash 8e8d9489 -> ac39a3df before any reading,
restored and re-proved byte-identical to the HEAD blob afterwards):

✗ a gate run by another interpreter IS in the tail
✗ the live tail reaches the CLASS: a second member invisible for a DIFFERENT reason (another interpreter)
✗ and that second member is not the card's own gate wearing a different name
✗ dispatch-gates self-test: 4 of 976 case(s) failed.

The instance case (a package-local gate invoked by path IS in the tail) stayed green
through that ablation — which is exactly the discriminator between (a) and (b).

Why this is not "22 leads is the same as none"

The header's refusal stands: 166 of 189 discovered families sit outside both path
declarations CI obeys, and naming them per card would say nothing. This tail is the
complement of that set, not a slice of it — a step leaves the list the moment the
derivation names any family for it, so it is bounded by the derivation's own blind spot
and shrinks as discovery improves. It carries no per-card claim and says so.

What it deliberately does NOT do

Verification

All on 263b4923, exit codes captured before any pipe.

  • check:pm-dispatch-gates — EXIT=0, ✓ dispatch-gates self-test: 976 cases pass.
    (944 -> 976, +32 cases: the walk's fixtures, the renderer's refusals, and the live
    class pins.)
  • bare-root-worklist.mjs --self-test — EXIT=0,
    OK self-test: 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted.
  • The 14 families dispatch-gates derives for this diff — all EXIT=0, including
    check:watch-hint-literal, check:entry-guard, check:parse-guard,
    check:self-test-wired (✓ every one of the 152 script(s) CI runs that ship a --self-test has that self-test run by CI)
    and check:nul-bytes.
  • check-test-completeness.mjs — EXIT=3 = NOT MEASURED, neither red nor green, per
    its own printed refusal.
  • ESLint, narrowed and declared: eslint scripts/pm/dispatch-gates.mjs --no-inline-config --format json -> EXIT=0, 1 file, 0 errors, 0 warnings, 0 suppressed. The narrowing is
    sound because (i) the linted population and the rules are read from ESLint's own
    --print-config for this path, not guessed — 2 rules apply,
    comment-swallow/no-code-inside-block-comment and no-restricted-imports; (ii) the file
    count is ESLint's own --format json output; (iii) eslint.config.mjs states, with a
    recorded positive-control measurement, that this repo never enables type-aware linting
    for any file
    (no parserOptions.project, no typed @typescript-eslint rules), confirmed
    live here (parserOptions.project present: false), so a one-file diff cannot move any
    untouched file's verdict. The repo-wide eslint . is CI's run.
  • No changeset: the diff is scripts/pm/** only and publishes from no package, so
    skip-changeset is applied on this PR.

Generated by Claude Code


Generated by Claude Code

os-project-manager and others added 2 commits August 30, 2026 14:48
…13333)

Report the unconditional CI steps the family derivation names NOTHING for,
instead of widening a matcher for the one gate that reddened.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 30, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 30, 2026 15:17
@os-project-manager
os-project-manager added this pull request to the merge queue Aug 30, 2026
Merged via the queue into main with commit b95ff78 Aug 30, 2026
34 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-13333-dispatch-gates-always-runs-tail branch August 30, 2026 15:41
This was referenced Aug 30, 2026
os-tesla pushed a commit that referenced this pull request Oct 25, 2026
…escription to the viewer's language (objectui#13488) (#13501)

<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DRqbNi4iARYYtqZmwLSPhaR)_

- [x] Adds no product-specific widget, dialog, hook or string (see
AGENTS.md › Product-neutral renderer)

Fixes #13488
Clause-②: no
Metadata-protocol: none

## What changed

`PageSchema` in `@objectstack/spec` types a page's `label` and
`description` as `I18nLabel`, so an inline locale map such as `{
'zh-CN', en }` is valid metadata, and the `/meta` read serves it
unchanged. Two packages drew it raw on a page route, and React refused
the object as a child (error #31), so the route was blank for every
viewer:

- **`@object-ui/app-shell`, `AppHeader`'s page branch.** It handed the
served label to `pageLabel` as its fallback, and `pageLabel` returns an
unmatched fallback untouched, so the map became the phone bar's title.
It now resolves through `metadataText` in the viewer's language
(`useObjectTranslation().language`), the call the report branch has used
since objectui#13470, and still passes through the `pages.NAME.label`
bundle lookup. The humanized slug stays the fallback when no label
resolves.
- **`@object-ui/components`, `PageRenderer`.** The page's own implicit
heading (`schema.title ?? schema.label`), the line under it
(`schema.description`), and the description it hands a titled
`page:header` through `PageDescriptionContext` were all drawn raw. Each
now resolves through `pickLocalized` in the viewer's language, the
resolver `page:header` already uses for its own title in the same
package. With only the app bar fixed, an `app`, `home` or `utility` page
with a map label stayed blank through this heading (measured, below), so
the claim was widened to this file (claim amendment on #13488).
- **Swept, same file, same call:** `AppHeader`'s dashboard branch drew a
served dashboard label straight into the phone bar's title.
`DashboardSchema.label` is `I18nLabel` too, so it is now
`metadataText(…) ?? dashboardLabel(…)`.

A plain-string label or description draws exactly as before:
`metadataText` and `pickLocalized` both pass a string through, and an
empty one still falls to the slug or hides the description. Studio's
page previews (`SourcePageEditor`, `PagePreview`) render through the
same `PageRenderer`, so they get the fix too. One changeset:
`@object-ui/app-shell` and `@object-ui/components` patch.

## Tests

New pins, each mounting the real component under a real `I18nProvider`,
each leg in `en` and `zh-CN` reading different strings out of one map,
with plain strings as the control:

-
`packages/app-shell/src/layout/__tests__/AppHeader.pageDashboardTitleInlineLocale-13488.test.tsx`:
the `AppHeader.reportCrumbInlineLocale-13470` harness with
`useObjectLabel` real. Legs `PAGE_LABEL_MAP_RESOLVES`,
`DASHBOARD_LABEL_MAP_RESOLVES`, two `PLAIN_STRING_CONTROL`s.
-
`packages/components/src/__tests__/page-inline-locale-label-13488.test.tsx`:
an `app` page through the real `SchemaRenderer` and `PageRenderer`. Legs
`HEADING_MAP_RESOLVES`, `DESCRIPTION_MAP_RESOLVES`,
`HEADER_DESCRIPTION_MAP_RESOLVES` (titled `page:header`),
`PLAIN_STRING_CONTROL`.

Readings, final head `2c82e03` unless stated:

- The two new files: Test Files 2 passed (2), Tests 16 passed (16).
- Every test file that names `AppHeader`, `PageView`, `PageRenderer`,
`SourcePageEditor`, `PagePreview` or `pageKindNode`, plus every
`packages/components` test that names `PageRenderer`, `page:header`,
`pageType`, the page renderer module or an `app`/`home`/`utility` page
node (212 files, found by `git grep -lE` over test files), with `pnpm
exec vitest run --maxWorkers=2`: Test Files 212 passed (212), Tests 2161
passed (2161). Run at `bf37518`; the only later commit, `2c82e03`,
changes the type annotation of one new test's fixture, and both new
files were re-run at `2c82e03` above.
- `turbo run type-check --filter=@object-ui/app-shell
--filter=@object-ui/components --concurrency=2 --continue`: 31
successful, 31 total. Both packages' `type-check` chain
`tsconfig.test.json`, so the new test files are type-checked.
- eslint on the five touched files: 0 errors. Every warning is on a line
this PR does not add; the one on a changed line in `page.tsx` is the
`(schema as any).label` read the old line already had.
- `node scripts/check-changeset-presence.mjs`: pass (4 source files of 2
released packages, 1 changeset). `node
scripts/check-changeset-no-major.mjs`: pass. `pnpm
check:new-line-citations`: 0 new citations.

Narrowed, declared: the whole `packages/app-shell/` and
`packages/components/` suites were not run locally; CI's sharded `pnpm
test` runs them. The narrowed set's blind spot is a test that mounts
these components through another import path without naming them.

Probe before the fix, on main `85b6a48`, through the real
`SchemaRenderer` and `PageRenderer` (a throwaway file, not committed):
an `app` page with a map `label`, one with a map `description`, and one
with a titled `page:header` and a map `description` all failed on
`Objects are not valid as a React child (found: object with keys {zh-CN,
en})`.

Ablation, one site at a time on the committed fix, through
`scripts/ablation-replace.mjs` (anchor must hit once, blob must change,
restore proven by blob equal to `HEAD` and an empty `git diff HEAD`),
running the two new files each time:

- page branch reverted (`pageDef?.label || humanizeSlug(pageName)`): 2
failed, 14 passed. Only `PAGE_LABEL_MAP_RESOLVES` fails, in both
languages, on React error #31.
- dashboard branch reverted (`dashboardDef?.label ||
dashboardLabel(…)`): 2 failed, 14 passed. Only
`DASHBOARD_LABEL_MAP_RESOLVES` fails, on the same error.
- `PageRenderer` heading reverted (`schema.title ?? schema.label` raw):
2 failed, 14 passed. Only `HEADING_MAP_RESOLVES` fails, on the same
error.
- `PageRenderer` description reverted (raw `schema.description`): 4
failed, 12 passed. `DESCRIPTION_MAP_RESOLVES` and
`HEADER_DESCRIPTION_MAP_RESOLVES` fail, on the same error.

## Acceptance notes

- `InterfaceListPage` (an interface page's list surface) reads `typeof
page.label === 'string' ? page.label : page.name` for its heading and
its record drawer's title, and draws `page.description` only when it is
a string. A page with a map label does not blank there, but it shows the
page's machine name instead of its label, and a map description is not
shown. Read from the code on `85b6a48`, not measured; outside this
claim's file surface. Handed to the seat.
- `AppHeader`'s view branch hands `viewDef?.label || viewDef?.title` to
`viewTabLabel`, and a list view's `label` is `I18nLabel` in the spec as
well. The object page's tab bar (`ObjectView`) hands the same label to
the same function, so whether a map view label blanks a view route is a
question about `ObjectView`, not this header alone. Read from the code,
not measured; not changed here.
- The page's phone-bar title still passes the resolved label through the
`pages.NAME.label` bundle lookup, while the page's own heading draws the
served label. That split predates this PR and is unchanged, as on the
report route.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_012oUFcAgU5BgTKe6fa7RxD8

---------

Co-authored-by: Claude <noreply@anthropic.com>
os-tesla pushed a commit that referenced this pull request Oct 25, 2026
…ke test-only reset helpers off the barrels (objectui#13453, part 1) (#13506)

<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9D4nNtkcxk6i4sCKHsvYsQgG)_

Part of #13453
Clause-②: no (nothing added a consumer or author can target)
Metadata-protocol: none

First of the two PRs the claim amendment (comment 6393918010) cuts on
the governed line. This one is Tier S: no file under `.claude/**`,
`skills/**`, `AGENTS.md` or `docs/adr/**`. #13453 remains open for PR 2
(the providers trio, the contributor-guide sentence and the two
`skills/objectui/guides` files, Tier H) and for the 122 dead i18n keys.

## What this removes

**1. Nine whole modules with no reader** (2,211 lines plus their own
tests):

- `@object-ui/core`: `builder/schema-builder.ts`
(`form`/`button`/`input`/`card`/`grid`/`flex` and the six `*Builder`
classes) and the whole `protocols/` directory (`DndProtocol`,
`KeyboardProtocol`, `SharingProtocol`, and its now-empty `index.ts`).
The barrel lines become short removal notes, the same disposition
`NotificationProtocol` and `ResponsiveProtocol` got.
- `@object-ui/react`: `useFocusTrap`, `useFocusManagement`
(`useFocusReturn`, `useRovingFocus`), `usePageTransition`,
`useSchemaPersistence` (+ `createLocalStorageAdapter`), with the two
`useSchemaPersistence.*.test.tsx` files. The stale
`check-doc-example-types.mjs` row for the deleted hook goes too.
- `@object-ui/collaboration`: `LiveCursors` (+ `LiveCursorsProps`); the
package README drops its section. `usePresence` still reports cursors.
- Comments that named the deleted files (`types/src/index.ts`,
`crud-retirement-5373.test.ts`, `useAnimation.ts`, `useETagCache.ts` and
its test) now say "since-retired".

**2. `@object-ui/runner` is private and out of the release group.**
`private: true`; it leaves the `fixed` group for `ignore`, because
`check-changeset-fixed.mjs` wants every workspace package classified.
Two consequences handled here:

- Two pending changesets, `13464-declare-side-effects.md` and
`25685-one-react-line.md`, each listed `"@object-ui/runner": patch`.
With runner ignored, changesets refuses them as mixed ("Found mixed
changeset 13464-declare-side-effects … Mixed changesets that contain
both ignored and not ignored packages are not allowed", read from `pnpm
exec changeset status` on this tree before the edit). The runner line is
deleted from both; nothing else in them changes.
`check-changeset-overwrite` reports the modification (report-only);
after it, `pnpm exec changeset status --since=origin/main` exits 0.
- `check-published-tsconfig-tooling-exclude.mjs` reported `FAIL
[stale-carve-out] @object-ui/runner`, so its runner carve-out entry is
deleted and the shipped-list pin in its test shrinks to four.
`check-changeset-presence.test.ts` pinned `packages/runner/index.html`
as a guarded file; an unreleased package's entry is not guarded, so the
pin moves it to the not-guarded list.
- Docs: `content/docs/utilities/runner.mdx` and
`packages/runner/README.md` no longer say it is on npm; the root README
table marks it private.

**3. Test-only reset helpers off the public barrels:**
`resetColumnIdentityWarnings`, `resetRetiredSortSpellingReports`,
`resetDashboardFilterWarnings`, `resetReferenceKeyWarnings` (core),
`resetRuntimeConfigForTesting` (app-shell),
`resetInflightGetsForTesting` (types), `__clearRecordEditableCache`
(plugin-detail). The module keeps the export and the barrel stops
re-exporting it; the 15 test files outside the owning package that
imported one from a package root now import the module's `src` path
relatively, the pattern `__resetVisibilityPredicateWarnings` and
`useRecordEditable` already follow. Core's four `export *` lines for
those modules become named lists, with a one-line note on each.

⛔ Not here, per the claim: `resetActionKeyWarnings` (its barrel path is
`core/src/actions/index.ts`) and `clearSharedDiscoveryCache`
(`data-objectstack`) wait for #13442, which holds those files.

## Census before deleting (lit controls)

| Repository | Read at | Searched | Hits | Control |
|---|---|---|---|---|
| objectui | `76c560e` (branch base) | `git grep -w` for every exported
name of the nine modules and the seven helpers; generic builder names
(`form`, `card`, …) searched as named imports from `@object-ui/core` |
only the module, its own tests, its barrel line and comments |
`SchemaRenderer` lit |
| objectstack | `d1e329e` | same name list | one prose mention of
`LiveCursors` in `docs/qa/platform-checklist/FOLLOW-UPS.md`; no import |
`@object-ui/` lit in 150 files, `SchemaRenderer` in 38 |
| hotcrm | `700b567` | same name list | none | `@object-ui/` lit in 5
files, `SchemaRenderer` in 3 |
| cloud, npm dependents | n/a | **NOT MEASURED**: not reachable from
this session | n/a | n/a |

## Changesets

One per package, all `minor` with the removal named
(`13453-core-retire-dead-modules.md`,
`13453-react-retire-dead-hooks.md`,
`13453-collaboration-retire-live-cursors.md`,
`13453-test-helpers-off-barrels.md`). Runner gets none: it is ignored
now, and naming it would be a mixed changeset.

## Verification (local)

The type-check and vitest readings below were taken on the branch while
it still carried PR 2's providers commits too, so the tree they read is
this PR plus the providers retirement. The gates line was re-run on this
PR's own head.

- **type-check** (`turbo run type-check --continue`, under the verify
lock): core, react, collaboration, providers, types, app-shell,
plugin-detail, plugin-grid, plugin-view, plugin-calendar, console,
runner and the byo example. First run caught `http-inflight.test.ts`
importing the helper from `../index.js`; fixed in `44c01fe`, after which
`packages/types` type-check exits 0.
- **vitest, whole packages** core, types, providers, collaboration,
plugin-detail, react: Test Files 1028 passed, 1 skipped; Tests 19019
passed.
- **vitest, files** (every touched test file outside those packages,
plus the script tests that read runner or the release config): 21 files
/ 432 tests, one red, `check-changeset-presence.test.ts` (the runner pin
above), fixed in `45580a0` and re-run green (53/53). Console project,
the three touched files: 28/28. A wider sweep of 95 `scripts/__tests__`
files that mention runner, providers or the release config: green apart
from `check-doc-links.test.ts` and `check-skills-paths.test.ts`, which
read paths PR 2 deletes; both scripts exit 0 on this PR's head.
- **gates**, at `a5dc7bb`: `check-changeset-presence`,
`check-changeset-no-major`, `check-changeset-fixed`,
`check-changeset-claims`, `check-pending-changeset-literals`,
`check-published-tsconfig-tooling-exclude`,
`check-new-cross-file-line-citations`, `check-control-bytes`,
`check-skills-paths`, `check-doc-links` all exit 0; earlier on this
branch also `check:phantom-deps`, `check:unused-deps`,
`check:lockfile-integrity`, `check:lockfile-dedupe`,
`check:test-path-roots`, `check:unreferenced-sources`,
`check:side-effects-array`, `check:sdui-registration-pins`,
`check:self-import`, `check:vi-mock-specifiers`,
`check:lint-rule-coverage`, `check:node-esm-load`.
- **eslint** on the touched files with the repo config: 0 errors. With
`--no-inline-config` there are 2 errors, both on untouched lines and
both present at the base (`useETagCache.ts` preserve-caught-error,
`types/src/index.ts` restricted `FormField` import).
- **NOT MEASURED locally:** the whole `app-shell` package suite (its
touched files ran), `check:readme-exports` and `check:doc-snippets`
(need every package's `dist`); CI runs them.

## Follow-ups

- **Stale published guides until PR 2 lands:**
`skills/objectui/guides/project-setup.md` and
`skills/objectui/guides/architecture.md` still describe
`@object-ui/runner` as a standalone runtime; PR 2 fixes them (Tier H).
- **Release-lane hazard to watch:** any open PR whose new changeset
lists `"@object-ui/runner"` alongside other packages becomes a mixed
changeset once this merges, and nothing on a PR runs `changeset status`.
None of the pending changesets on `main` at `5399bbc` names it.
- `origin/main` moved two commits (#13501, #13503) after the branch
base; neither touches a file in this diff.

---
_Generated by [Claude
Code](https://claude.ai/code/session_011yfYeuX2ptMSCcYZiq61tS)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants