Repository navigation
nightly-tiers: red on main #17633
Description
os-nightly-tiers — machine-findable marker for this generated card. ⛔ Do not delete this line: it is how the nightly finds this card instead of filing a new one every night.
nightly-tiers: red on main
Swept 2026-09-16T05:57:39.493Z · expected every 24h while this card stands (cron 29 5 * * * UTC) · next by 2026-09-17T05:29Z · run log · commit b57671edc1166d49be40993000894128b3a63b44 · trigger schedule · tiers job result failure.
The Swept line above is this nightly's heartbeat, and it states the cadence that makes
「stalled」 decidable: while the tiers stay red this card is refreshed on that schedule, so a
timestamp still sitting there past the next by deadline means either the standing caller
died or the nightly went green — nothing here files or closes anything on green, so both
readings end at this card. ⛔ Do not carry a cadence over from a sibling patrol anchor: they
differ by up to 4× and each states its own.
The e2e and live test tiers — the files named *.e2e.test.* and *.live.test.* — run here
nightly on main under OS_TEST_TIERS=nightly and nowhere else (the per-PR and merge-queue
Test Core runs under queue). A red here is a real suite that no pull request will red on;
this card is the channel that sees it. Nothing is blocked by it.
⛔ The remedy is never to rename, skip or delete the failing test to make the nightly green.
Reproduce locally with OS_TEST_TIERS=nightly pnpm --filter <pkg> test, fix what it names, and
let the next nightly refresh this card.
Failing files
packages/cli/test/serve-port-readback.e2e.test.ts
shard-1 — last lines of the test log
… (8150 chars elided) …
s�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m 2b (--strict-body) — ⭐ carries the notice, where `warnings` is empty by construction �[33m 9969�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m 3 (protocol parse) — ⭐ carries the notice, where `warnings` is empty by construction �[33m 9966�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⭐ converts nothing — the SAME exit reports `[]`, so the field tracks the run �[33m 10462�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m 3b (author-time rules) — the notice rides the rule gate �[33m 10238�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m 3c (capability preflight) — the notice rides the preflight gate �[33m 10003�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m 3e (access-matrix drift) — the notice rides the drift gate �[33m 9865�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m 3f (package docs) — the notice rides the docs gate �[33m 10530�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m 4b (--no-runtime-bundle) — a late exit past every step carries the notice �[33m 9903�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m the catch-all — a THROWN failure still reports the computed notice �[33m 10149�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m catch-all (throw at load) — `conversions` is PRESENT and empty, because step 2 never ran �[33m 9974�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m REGRESSION GUARD — `warnings` and `conversions` stay separate fields �[33m 10370�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/lint-eval-generator-load-envelope.e2e.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[33m 103511�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m a coded failure at import surfaces BOTH carriers on the --json face �[33m 10530�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m an errno thrown at import passes through as `code` �[33m 10111�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m the key set is exactly the carriers — no `conversions`, no filler �[33m 10638�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m the hand-thrown "must default-export a function" stays exactly `{error}` �[33m 10089�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m esbuild's BuildFailure carries neither key, so the payload stays bare �[33m 10446�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m the unresolvable path leaks nothing to stderr — and still refuses on stdout �[33m 10444�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m a generator that only WARNS leaks nothing either — and the human face still shows it �[33m 20548�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m the human path is unchanged: still exit 1, still no JSON document �[33m 10294�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m offline eval is untouched: exit 0 and a report, not an error envelope �[33m 10395�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/lint-strict-warnings.e2e.test.ts �[2m(�[22m�[2m8 tests�[22m�[2m)�[22m�[33m 112785�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⭐ the fixture REALLY warns — ≥1 warning and 0 errors on the run under test �[33m 9959�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m without --strict the same stack exits 0 — the default is unchanged �[33m 10322�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⭐ with --strict the same stack exits 1 — passed:false, strict:true, failing === warnings �[33m 10215�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⭐ the console face says why — the count and the flag, and the count is the measured one �[33m 20219�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m the console face without the flag exits 0 and claims no strict failure �[33m 10463�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m suggestions stay advisory under --strict �[33m 9873�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m an error still fails with and without the flag, and --strict counts the warnings on top �[33m 21010�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m a clean stack exits 0 under --strict, in both faces �[33m 20712�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/lint-hook-rules-reach-handler-hooks.e2e.test.ts �[2m(�[22m�[2m10 tests�[22m�[2m)�[22m�[33m 102391�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m INTAKE — a handler-authored hook writing a readonly field is refused (error, exit 1) �[33m 9742�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m CONTROL — the same statement as an explicit body is refused identically �[33m 9999�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m INTAKE — the lowered handler is refused at build, exit 1 — unchanged by this card �[33m 10285�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m CONTROL — the explicit body is refused at build identically �[33m 10214�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m INTAKE — the handler-authored hook IS refused here (error, exit 1) — the red-first leg of #16544 �[33m 10572�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m CONTROL — the explicit body is refused here, before and after, so the intake reading is about the door �[33m 10491�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m NEGATIVE CONTROL — a handler-authored hook the family has nothing to say about still passes (exit 0) �[33m 10232�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m INTAKE — `os validate` accepts the inline action target (exit 0, valid, no invalid_type) �[33m 10556�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m PARITY — `os build` accepts the same config (it lowered before its parse all along) �[33m 10173�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m INTAKE — a nameless `functions` array entry is accepted too (the pass names it `anon_fn`; exit 0, valid) �[33m 10118�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/lint-conversion-notices.e2e.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[33m 72507�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⭐ the fixture REALLY converts — `--json` publishes the notice the run produced �[33m 10055�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⭐ converts nothing — the field is PRESENT and EMPTY, so it tracks the run �[33m 10445�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⭐ the human face says it too — the notice is printed, with its expiry �[33m 10466�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⭐ the human face stays SILENT when nothing converted �[33m 10256�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m catch-all at load — `[]` honestly, because the normalize step never ran �[33m 10440�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m REGRESSION GUARD — the notice is NOT folded into `issues`, and the counts are unchanged �[33m 20835�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/migrate-apply-refuses-before-ddl.e2e.test.ts �[2m(�[22m�[2m10 tests�[22m�[2m)�[22m�[33m 33496�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/lint-eval-empty-generator-refusal.e2e.test.ts �[2m(�[22m�[2m10 tests�[22m�[2m)�[22m�[33m 132632�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m refuses on the human face, naming the flag and the empty value it was given �[33m 9970�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m the --json face stays a machine face — one JSON document, nothing on stderr �[33m 10037�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m nothing is minted — the payload key set is exactly `error` �[33m 10804�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m differs from the no-flag run once the elapsed token is normalised �[33m 20041�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m both refuse: same exit code, refusal on stdout, generator never loaded �[33m 20450�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m both answer the same --json envelope: exit 1 and a lone `error` key �[33m 19851�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m `--eval` with the flag absent still runs the offline eval �[33m 10553�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m `--eval --generator <module>` still loads the generator and runs live �[33m 10396�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m an unresolvable generator path refuses exactly as it always has �[33m 10002�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m a plain project lint is untouched �[33m 10519�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/lint-eval-json-unscorable-stack.e2e.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[33m 134646�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m a top-level poisoned getter: stdout is JSON, the case FAILED, the cause is named �[33m 10548�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m a poisoned getter BELOW the top level is caught too — the schema parse walks there �[33m 10108�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⛔ a silent swallow would be caught: the unscorable case is not scored as clean �[33m 9755�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m offline mode is untouched: exit 0 and every golden case passes �[33m 10761�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m a generator that THROWS is still a generation error, not a scoring one �[33m 10357�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⭐ every generation threw ⇒ meanScore 0 on the --json face, never 100 �[33m 10898�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m off-shape stack manifest-as-string is a SCORED case with schema errors, never a generationError �[33m 10801�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m off-shape stack objects-as-string is a SCORED case with schema errors, never a generationError �[33m 10411�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m off-shape stack objects-as-number is a SCORED case with schema errors, never a generationError �[33m 9990�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m off-shape stack objects-as-null is a SCORED case with schema errors, never a generationError �[33m 10231�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m off-shape stack nested-wrong-types is a SCORED case with schema errors, never a generationError �[33m 10163�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m off-shape stack bare-string is a SCORED case with schema errors, never a generationError �[33m 10264�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m a generator that cannot be loaded still takes the generator-load JSON exit �[33m 10351�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/artifact-pinned-boot.e2e.test.ts �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[33m 82541�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m boots an https:// artifact, overriding the image's OS_ARTIFACT_PATH default �[33m 14263�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m boots a file:// artifact — the volume-mount workflow �[33m 13446�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m boots unpinned, and says so rather than claiming verification �[33m 13686�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m refuses a hash mismatch, naming expected AND actual, and exits non-zero �[33m 10324�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m refuses an unreachable artifact instead of degrading to an empty kernel �[33m 10145�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m never prints the credential of a pre-signed URL — on the real refusal path �[33m 10330�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m refuses an artifact built for another protocol major (acceptance #4) �[33m 10328�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/run-dev-cwd-tsconfig-redirect.e2e.test.ts �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[33m 31202�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m loads its command set anyway, and says what it re-ran �[33m 12601�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m still explains itself when the pin is defeated, and refuses the rebuild remedy �[33m 8547�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m CONTROL — from the repo root nothing is redirected, so nothing is re-exec'd �[33m 10048�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/build-json-undeclared-key-parity.e2e.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[33m 83627�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m reports the planted key in the payload, and the stack still builds (exit 0) �[33m 10477�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m carries the SAME undeclared-key set `os validate --json` reports on the same tree �[33m 21204�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m folds them in BESIDE the authoring-rule advisories, in the shape `os validate --json` uses �[33m 10189�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m adds NO new top-level key to the payload — this fills a declared key, it is not a new surface �[33m 9958�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m CONTROL — a clean stack reports no undeclared-key line on either face �[33m 21163�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m leaves the TEXT face saying what it always said �[33m 10626�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/diff-usage-error-stream.e2e.test.ts �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[33m 30863�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/serve-ready-degraded-boot.e2e.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[33m 9867�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m prints ready AND the missing core service in the same output, with no unconditional tick �[33m 4614�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m leaves a healthy boot printing exactly the tick it always printed �[33m 5244�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/json-stdout-purity.e2e.test.ts �[2m(�[22m�[2m41 tests�[22m�[2m)�[22m�[33m 147166�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/init-created-files-summary.e2e.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[33m 20513�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/create-refuses-invalid-project-name.e2e.test.ts �[2m(�[22m�[2m6 tests�[22m�[2m)�[22m�[33m 52514�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m refuses the card's input and writes NOTHING �[33m 10355�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m accepts a valid name — the positive control for the absence check above �[33m 10359�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m refuses a name only the COMPOSED length catches, which `os init` must keep accepting �[33m 31795�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/compile-artifact-packages.e2e.test.ts �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[33m 42925�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m writes `manifest` and NO `packages` key �[33m 11386�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m carries both package manifests through to the written artifact, in order �[33m 10596�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m keeps the singular `manifest` beside it — retained, not replaced �[33m 10347�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m exits non-zero rather than writing an artifact in the unreserved shape �[33m 10585�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/migrate-plan-exits.e2e.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[33m 6938�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m returns from a composed host stack that armed an unreleasable handle in init() �[33m 6934�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/i18n-check-platform-bucket.e2e.test.ts �[2m(�[22m�[2m5 tests�[22m�[2m)�[22m�[33m 52606�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m reports 100% and exits 0 under --strict --threshold=100 �[33m 10799�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m is the SAME invocation that failed before — the control is --include-platform �[33m 9849�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m prints the hidden-bucket hint on the console face �[33m 10571�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m ⛔ still gates the package that SHIPS the baseline, with no flag at all �[33m 10367�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m accepts --no-include-platform, so the third state really is three states �[33m 11012�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/generate-agent-retired.e2e.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[33m 31749�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/serve-boot-diagnostics.e2e.test.ts �[2m(�[22m�[2m2 tests�[22m�[2m)�[22m�[33m 27093�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m prints the boot WARN a plugin logged while the banner was being assembled �[33m 13293�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m streams boot output live at --log-level debug instead of hiding it �[33m 13793�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[42m unit �[49m�[39m test/vitest-resolution-base-collapse.e2e.test.ts �[2m(�[22m�[2m13 tests�[22m�[2m)�[22m�[32m 172�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/serve-app-runtime-hooks.e2e.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[33m 19784�[2mms�[22m�[39m
@objectstack/cli:test: �[33m�[2m✓�[22m�[39m runs onEnable, so the declared script action has a handler �[33m 10116�[2mms�[22m�[39m
@objectstack/cli:test: �[32m✓�[39m �[30m�[45m integration �[49m�[39m test/config-miss-stdout-purity.e2e.test.ts �[2m(�[22m�[2m174 tests�[22m�[2m)�[22m�[33m 321544�[2mms�[22m�[39m
@objectstack/cli:test:
@objectstack/cli:test: �[2m Test Files �[22m �[1m�[32m34 passed�[39m�[22m�[90m (34)�[39m
@objectstack/cli:test: �[2m Tests �[22m �[1m�[32m448 passed�[39m�[22m�[90m (448)�[39m
@objectstack/cli:test: �[2m Start at �[22m 05:43:56
@objectstack/cli:test: �[2m Duration �[22m 803.26s�[2m (transform 14.61s, setup 0ms, import 26.86s, tests 2157.44s, environment 5ms)�[22m
@objectstack/cli:test:
@objectstack/cli:test: JSON report written to /home/runner/work/objectstack/objectstack/packages/cli/.nightly-tiers/vitest-report.json
Tasks: 58 successful, 58 total
Cached: 0 cached, 58 total
Time: 19m36.654s
Summary: /home/runner/work/objectstack/objectstack/.turbo/runs/3JOhtqug8NCjhH3Nn9awnbOrBdy.json
shard-2 — last lines of the test log
@objectstack/cli:test: �[31m+ // Read the address by shape, not by which branch matches first. Every�[39m
@objectstack/cli:test: �[31m+ // driver here has a `config` property — SqlDriver's is knex-shaped,�[39m
@objectstack/cli:test: �[31m+ // MongoDBDriver's holds a top-level `url`, InMemoryDriver's is `{}` — so�[39m
@objectstack/cli:test: �[31m+ // returning early on "has a config" meant the mongo and in-memory arms�[39m
@objectstack/cli:test: �[31m+ // below never ran and every non-SqlDriver boot banner read `(unknown)`.�[39m
@objectstack/cli:test: �[31m+ // `driver.url` still covers a driver that keeps its DSN on the instance.�[39m
@objectstack/cli:test: �[31m+ const url = describeDriverConnection(cfg)�[39m
@objectstack/cli:test: �[31m+ ?? (typeof driver.url === 'string' && driver.url ? redactConnectionUrl(driver.url) : undefined);�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ // A memory driver has no address to show — say so, rather than�[39m
@objectstack/cli:test: �[31m+ // `(unknown)`, which reads as "we looked for one and failed".�[39m
@objectstack/cli:test: �[31m+ //�[39m
@objectstack/cli:test: �[31m+ // `sqliteFile` is the same config read a second time asking a DIFFERENT�[39m
@objectstack/cli:test: �[31m+ // question: not "what do I print" but "which file on this filesystem", so�[39m
@objectstack/cli:test: �[31m+ // a caller can `stat` it. Absent for every driver that serves no on-disk�[39m
@objectstack/cli:test: �[31m+ // SQLite file, which is the majority of them.�[39m
@objectstack/cli:test: �[31m+ return {�[39m
@objectstack/cli:test: �[31m+ label,�[39m
@objectstack/cli:test: �[31m+ url: url ?? (name.endsWith('.memory') ? '(in-memory)' : '(unknown)'),�[39m
@objectstack/cli:test: �[31m+ sqliteFile: describeDriverSqliteFile(cfg),�[39m
@objectstack/cli:test: �[31m+ };�[39m
@objectstack/cli:test: �[31m+ }�[39m
@objectstack/cli:test: �[31m+ return null;�[39m
@objectstack/cli:test: �[31m+ }�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ /**�[39m
@objectstack/cli:test: �[31m+ * Decide what the ready banner's `Config:`/`Artifact:` row should say�[39m
@objectstack/cli:test: �[31m+ * (#8978).�[39m
@objectstack/cli:test: �[31m+ *�[39m
@objectstack/cli:test: �[31m+ * `relativeConfig` is derived from `args.config` at the top of `run()`,�[39m
@objectstack/cli:test: �[31m+ * before the artifact-fallback branch is decided, and was being handed to�[39m
@objectstack/cli:test: �[31m+ * {@link printServerReady} unconditionally. On an `OS_ARTIFACT_URL` boot�[39m
@objectstack/cli:test: �[31m+ * the `objectstack.config.ts` in cwd is deliberately never executed — the�[39m
@objectstack/cli:test: �[31m+ * boot diagnostics say so plainly a few lines above the banner — yet the�[39m
@objectstack/cli:test: �[31m+ * banner still named it. On the plain artifact-fallback path (no config�[39m
@objectstack/cli:test: �[31m+ * authored, booting from `OS_ARTIFACT_PATH` or the `<cwd>/dist/objectstack.json`�[39m
@objectstack/cli:test: �[31m+ * convention) the row named a config file that does not exist on disk at�[39m
@objectstack/cli:test: �[31m+ * all. Both are the same defect: the row is the surface an operator reads�[39m
@objectstack/cli:test: �[31m+ * to answer "what is this container actually running", and naming what�[39m
@objectstack/cli:test: �[31m+ * did NOT boot points them at the wrong app (cloud#1292).�[39m
@objectstack/cli:test: �[31m+ *�[39m
@objectstack/cli:test: �[31m+ * - `pinnedArtifact` set (OS_ARTIFACT_URL, #8368) → report it. `.display`�[39m
@objectstack/cli:test: �[31m+ * is already resolved and already redacted for a pre-signed URL by the�[39m
@objectstack/cli:test: �[31m+ * resolver, so it is safe to print as-is.�[39m
@objectstack/cli:test: �[31m+ * - `useArtifactFallback` set with no `pinnedArtifact` (OS_ARTIFACT_PATH,�[39m
@objectstack/cli:test: �[31m+ * the `dist/objectstack.json` convention, or an empty/quick-start boot)�[39m
@objectstack/cli:test: �[31m+ * → no config was read and there is no safely-redacted display in hand�[39m
@objectstack/cli:test: �[31m+ * here (OS_ARTIFACT_PATH may itself be a credentialed URL) — omit the�[39m
@objectstack/cli:test: �[31m+ * row rather than name a nonexistent file or risk leaking a secret.�[39m
@objectstack/cli:test: �[31m+ * - Neither set → the ordinary config-boot path; report `relativeConfig`�[39m
@objectstack/cli:test: �[31m+ * exactly as before.�[39m
@objectstack/cli:test: �[31m+ */�[39m
@objectstack/cli:test: �[31m+ export function resolveBannerConfigRow(opts: {�[39m
@objectstack/cli:test: �[31m+ relativeConfig: string;�[39m
@objectstack/cli:test: �[31m+ useArtifactFallback: boolean;�[39m
@objectstack/cli:test: �[31m+ pinnedArtifact?: { display: string };�[39m
@objectstack/cli:test: �[31m+ }): { configFile?: string; artifactSource?: string } {�[39m
@objectstack/cli:test: �[31m+ if (opts.pinnedArtifact) return { artifactSource: opts.pinnedArtifact.display };�[39m
@objectstack/cli:test: �[31m+ if (opts.useArtifactFallback) return {};�[39m
@objectstack/cli:test: �[31m+ return { configFile: opts.relativeConfig };�[39m
@objectstack/cli:test: �[31m+ }�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ /**�[39m
@objectstack/cli:test: �[31m+ * Collect the automation wiring facts for the startup banner (2026-07-17�[39m
@objectstack/cli:test: �[31m+ * third-party eval: a flow that failed to arm emits no log line to find, so the�[39m
@objectstack/cli:test: �[31m+ * banner reads the binding state off the engine instead).�[39m
@objectstack/cli:test: �[31m+ *�[39m
@objectstack/cli:test: �[31m+ * Every probe is feature-detected so an older `@objectstack/service-automation`�[39m
@objectstack/cli:test: �[31m+ * (without `getTriggerBindingAudit` / `getShadowedFlows` / extended runtime�[39m
@objectstack/cli:test: �[31m+ * states) degrades to the plain count line instead of crashing the banner.�[39m
@objectstack/cli:test: �[31m+ * Returns `undefined` when there is nothing automation-related to show at all.�[39m
@objectstack/cli:test: �[31m+ */�[39m
@objectstack/cli:test: �[31m+ export function collectAutomationSummary(�[39m
@objectstack/cli:test: �[31m+ kernel: any,�[39m
@objectstack/cli:test: �[31m+ declaredFlowCount: number,�[39m
@objectstack/cli:test: �[31m+ ): AutomationReadySummary | undefined {�[39m
@objectstack/cli:test: �[31m+ let automation: any;�[39m
@objectstack/cli:test: �[31m+ try { automation = kernel?.getService?.('automation'); } catch { /* not registered */ }�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ if (!automation) {�[39m
@objectstack/cli:test: �[31m+ return declaredFlowCount > 0�[39m
@objectstack/cli:test: �[31m+ ? {�[39m
@objectstack/cli:test: �[31m+ enabled: false,�[39m
@objectstack/cli:test: �[31m+ declaredFlowCount,�[39m
@objectstack/cli:test: �[31m+ flowCount: 0,�[39m
@objectstack/cli:test: �[31m+ boundCount: 0,�[39m
@objectstack/cli:test: �[31m+ triggerTypes: [],�[39m
@objectstack/cli:test: �[31m+ unbound: [],�[39m
@objectstack/cli:test: �[31m+ unknownObject: [],�[39m
@objectstack/cli:test: �[31m+ shadowed: [],�[39m
@objectstack/cli:test: �[31m+ draftCount: 0,�[39m
@objectstack/cli:test: �[31m+ }�[39m
@objectstack/cli:test: �[31m+ : undefined;�[39m
@objectstack/cli:test: �[31m+ }�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ /**�[39m
@objectstack/cli:test: �[31m+ * One flow body's provenance, as the engine spells it (`FlowContender` in�[39m
@objectstack/cli:test: �[31m+ * `@objectstack/service-automation`): a code-shipped artifact, or an�[39m
@objectstack/cli:test: �[31m+ * ADR-0005 runtime overlay row in `sys_metadata`.�[39m
@objectstack/cli:test: �[31m+ *�[39m
@objectstack/cli:test: �[31m+ * Declared structurally, like every other shape this function reads off the�[39m
@objectstack/cli:test: �[31m+ * engine. The probes below are feature-detected precisely so a host running�[39m
@objectstack/cli:test: �[31m+ * an OLDER automation package still boots its banner, and a nominal import�[39m
@objectstack/cli:test: �[31m+ * would type these reads against the CURRENT package while the runtime�[39m
@objectstack/cli:test: �[31m+ * deliberately tolerates a previous one.�[39m
@objectstack/cli:test: �[31m+ */�[39m
@objectstack/cli:test: �[31m+ type Contender = { source: 'package' | 'runtime'; packageId?: string };�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ let states: Array<{�[39m
@objectstack/cli:test: �[31m+ name: string;�[39m
@objectstack/cli:test: �[31m+ enabled: boolean;�[39m
@objectstack/cli:test: �[31m+ bound: boolean;�[39m
@objectstack/cli:test: �[31m+ status?: string;�[39m
@objectstack/cli:test: �[31m+ triggerType?: string;�[39m
@objectstack/cli:test: �[31m+ object?: string;�[39m
@objectstack/cli:test: �[31m+ // [#12028] `getFlowRuntimeStates()` has attached these two per row since�[39m
@objectstack/cli:test: �[31m+ // #11997 whenever a bare name had more than one contender. Named here so a�[39m
@objectstack/cli:test: �[31m+ // later read is type-checked against the row's real shape — casting past�[39m
@objectstack/cli:test: �[31m+ // this annotation is how the field goes back to being declared and unread,�[39m
@objectstack/cli:test: �[31m+ // which is the whole defect this banner line closes.�[39m
@objectstack/cli:test: �[31m+ armedFrom?: Contender;�[39m
@objectstack/cli:test: �[31m+ shadowed?: Contender[];�[39m
@objectstack/cli:test: �[31m+ }> = [];�[39m
@objectstack/cli:test: �[31m+ try { states = automation.getFlowRuntimeStates?.() ?? []; } catch { /* older engine */ }�[39m
@objectstack/cli:test: �[31m+ if (states.length === 0 && declaredFlowCount === 0) return undefined;�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ let triggerTypes: string[] = [];�[39m
@objectstack/cli:test: �[31m+ try { triggerTypes = automation.getRegisteredTriggerTypes?.() ?? []; } catch { /* older engine */ }�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ let unbound: Array<{ flowName: string; triggerType: string; reason: string }> = [];�[39m
@objectstack/cli:test: �[31m+ try { unbound = automation.getTriggerBindingAudit?.() ?? []; } catch { /* older engine */ }�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ // [#12028] Same-named definitions: which body armed, and which lost. Read�[39m
@objectstack/cli:test: �[31m+ // from the engine's dedicated receipt rather than from `states` above, for�[39m
@objectstack/cli:test: �[31m+ // one measured reason — `getFlowRuntimeStates()` can only attach the receipt�[39m
@objectstack/cli:test: �[31m+ // to a row it is already emitting, i.e. to a name still in the flow map,�[39m
@objectstack/cli:test: �[31m+ // whereas `getShadowedFlows()` returns every receipt the boot pull recorded.�[39m
@objectstack/cli:test: �[31m+ // A contested name is worth saying out loud either way.�[39m
@objectstack/cli:test: �[31m+ //�[39m
@objectstack/cli:test: �[31m+ // Feature-detected exactly like the `unbound` probe above and with nothing�[39m
@objectstack/cli:test: �[31m+ // more: optional call, `?? []`, `catch` for an older engine. No extra�[39m
@objectstack/cli:test: �[31m+ // tolerance — the neighbouring read is the standard here.�[39m
@objectstack/cli:test: �[31m+ let shadowing: Array<{ name: string; armed: Contender; shadowed: Contender[] }> = [];�[39m
@objectstack/cli:test: �[31m+ try { shadowing = automation.getShadowedFlows?.() ?? []; } catch { /* older engine */ }�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ // Dead bindings: a bound record-change flow whose target object nobody�[39m
@objectstack/cli:test: �[31m+ // registered — the hook is filtered to a name that never writes.�[39m
@objectstack/cli:test: �[31m+ const unknownObject: Array<{ flowName: string; object: string }> = [];�[39m
@objectstack/cli:test: �[31m+ let ql: any;�[39m
@objectstack/cli:test: �[31m+ try { ql = kernel?.getService?.('objectql'); } catch { /* absent */ }�[39m
@objectstack/cli:test: �[31m+ if (ql && typeof ql.getObject === 'function') {�[39m
@objectstack/cli:test: �[31m+ for (const s of states) {�[39m
@objectstack/cli:test: �[31m+ if (!s.bound || !s.object || s.triggerType !== 'record_change') continue;�[39m
@objectstack/cli:test: �[31m+ let known: unknown;�[39m
@objectstack/cli:test: �[31m+ try { known = ql.getObject(s.object); } catch { known = undefined; }�[39m
@objectstack/cli:test: �[31m+ if (!known) unknownObject.push({ flowName: s.name, object: s.object });�[39m
@objectstack/cli:test: �[31m+ }�[39m
@objectstack/cli:test: �[31m+ }�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test: �[31m+ return {�[39m
@objectstack/cli:test: �[31m+ enabled: true,�[39m
@objectstack/cli:test: �[31m+ declaredFlowCount,�[39m
@objectstack/cli:test: �[31m+ flowCount: states.length,�[39m
@objectstack/cli:test: �[31m+ boundCount: states.filter((s) => s.bound).length,�[39m
@objectstack/cli:test: �[31m+ triggerTypes,�[39m
@objectstack/cli:test: �[31m+ unbound,�[39m
@objectstack/cli:test: �[31m+ unknownObject,�[39m
@objectstack/cli:test: �[31m+ // A receipt that displaced nothing is not a contested name. The engine�[39m
@objectstack/cli:test: �[31m+ // already refuses to record one, and the banner keeps its own end of that�[39m
@objectstack/cli:test: �[31m+ // guarantee here rather than inheriting it: a benign boot must print no�[39m
@objectstack/cli:test: �[31m+ // shadowing line at all, and this is where "benign" is decided.�[39m
@objectstack/cli:test: �[31m+ shadowed: shadowing�[39m
@objectstack/cli:test: �[31m+ .filter((r) => r.shadowed.length > 0)�[39m
@objectstack/cli:test: �[31m+ .map((r) => ({ flowName: r.name, armed: r.armed, shadowedCount: r.shadowed.length })),�[39m
@objectstack/cli:test: �[31m+ draftCount: states.filter((s) => s.enabled && (s.status ?? 'draft') === 'draft').length,�[39m
@objectstack/cli:test: �[31m+ };�[39m
@objectstack/cli:test: �[31m+ }�[39m
@objectstack/cli:test: �[31m+�[39m
@objectstack/cli:test:
@objectstack/cli:test: �[36m �[2m❯�[22m test/serve-port-readback.e2e.test.ts:�[2m247:9�[22m�[39m
@objectstack/cli:test: �[90m245|�[39m 'the ready banner no longer derives its API row from `resolveA…
@objectstack/cli:test: �[90m246|�[39m + 'if it now uses the REQUESTED port, the #12525 read-back is …
@objectstack/cli:test: �[90m247|�[39m ).toContain('externalBaseOrigin: resolveAuthBaseUrl(boundPort).b…
@objectstack/cli:test: �[90m |�[39m �[31m^�[39m
@objectstack/cli:test: �[90m248|�[39m �[34mexpect�[39m(
@objectstack/cli:test: �[90m249|�[39m serveSource�[33m,�[39m
@objectstack/cli:test:
@objectstack/cli:test: �[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/2]⎯�[22m�[39m
@objectstack/cli:test:
@objectstack/cli:test: ELIFECYCLE Test failed. See above for more details.
::error::command (/home/runner/work/objectstack/objectstack/packages/cli) /opt/hostedtoolcache/node/22.23.2/x64/bin/pnpm run test --shard=2/2 --reporter=default --reporter=json --outputFile.json=.nightly-tiers/vitest-report.json exited (1)
@objectstack/cli#test: ERROR command (/home/runner/work/objectstack/objectstack/packages/cli) /opt/hostedtoolcache/node/22.23.2/x64/bin/pnpm run test --shard=2/2 --reporter=default --reporter=json --outputFile.json=.nightly-tiers/vitest-report.json exited (1)
Tasks: 57 successful, 58 total
Cached: 0 cached, 58 total
Time: 17m2.453s
Summary: /home/runner/work/objectstack/objectstack/.turbo/runs/3JOha9qvpAlJpAtb0pqUOBk2TR5.json
Failed: @objectstack/cli#test
ERROR run failed: command exited (1)
Filed by .github/workflows/test-nightly-tiers.yml. Generated by Claude Code
Activity
claude commentedon Sep 12, 2026
Repair landed —
26e0d81f3200122af15df0db94f18ed55ed418fd— and this card goes topm:on-hold, ⛔ not closedRelease: session
session_01TSf4DV7ziu4V5j73e46b7c· cause: the repair landed, but the instrument that judges this card has not run since · destination:pm:on-holdwith theRestart-when:line below; assignee cleared andpm:dispatchedreplaced in the same write.domain:cliexecution seat (#6024), 2026-09-12T07:29Z.PR #17805 merged 2026-09-12T07:28:23Z. Verdict of record:
5644293947.Landing verified — two readings plus controls
git rev-list --parents -n 1 26e0d81fgit merge-base --is-ancestor 26e0d81f origin/maincce1254093→ exit 1 ⇒ the test can return falsetest(cli): drive os migrate account-issuer --json in the json-stdout-purity family (#17805)Reading 2, on
origin/main:'migrate account-issuer': [],present at:91inpackages/cli/test/json-stdout-purity.e2e.test.ts. Positive control on the same matcher —'migrate apply'→ 1. Fabricated control — a row name that does not exist → 0 ⇒ the matcher can return zero, so the hit is a reading.Why
pm:on-holdand notclosed5643489339was to leave the card dispatched until a nightly confirms. Then:⇒ "leave it
pm:dispatched" would park a card in a dispatched state, holding an assignee, with nothing in flight, overnight. That is not a wait — it is the half-state shape the patrol reports, and 「状态变更不过夜」 is exactly about this.pm:on-holdis the state model's own answer for 「决定已做且答案是暂不做」, and it is legal here because the wake condition below is machine-readable.⛔ And it is still not closed.
test-nightly-tiers.yml:46— 「On green this workflow files nothing, edits nothing and closes nothing」 — and:54— a closed card is never reopened, a later red is filed fresh. Closing on the repair rather than on the confirmation would trade this thread and itsos-nightly-tiersmarker for a brand-new card if the next nightly is red for any reason at all. The instrument, not the merge, decides. That is also why PR #17805 carriedPart of #17633and ⛔ no closing keyword.The wake condition
Restart-when: a
Nightly Tiersworkflow run onmainNEWER than run 34675914967 reportsconclusion: successon a head containing26e0d81f3200122af15df0db94f18ed55ed418fd— then close this cardcompleted, citing that run id.Three outcomes, and only the first is a close:
completed, citing the run id. The repair is confirmed by the only instrument that can confirm it.packages/cli/test/json-stdout-purity.e2e.test.ts⇒ thereportjob has rewritten this body in place. ⛔ Re-read the NEW body; ⛔ do not act on any transcript in this thread, including this comment.migrate-meta.e2e.test.tswas repaired by fix(cli): migrate-meta's semver-label assertion targets the Chain: line, not the whole stdout #17654 andjson-stdout-purity.e2e.test.tstook its place. Grade it fresh; ⛔ do not inherit this card's priority or diagnosis.⛔ The
os-nightly-tiersmarker line at the top of the body must survive all three — it is how the nightly finds this card instead of filing a new one every night.What landed, and the one thing it did not fix
The
FAMILYpin now carriesmigrate account-issuerwith a bare argv, and the member is driven —beforeAlliteratesObject.entries(FAMILY)anddescribe.each(Object.keys(FAMILY))builds its cases from the same object.discoverFamily()was not narrowed (the diff touches it 0 times), which was the one thing the card's own governing line forbade.leaves no kernel-logger record on stdoutis a pair of negative assertions and stays green against a run that never happened — it binds purity, not existence. Measured in the dev's own ablation and reported by it unprompted. ⛔ Not filed and ⛔ not fixed here: redesigning that trio is a pin redesign, not this card.Generated by Claude Code
os-support-ai commentedon Sep 16, 2026
认领 — hold 已失效:修复落地了,但红换了文件,正文已被 nightly 就地重写
Claim: session
session_01DvvamiacK328idtBYJBxV3Branch:
claude/issue-17633-serve-port-readback-nightlyClause-②: no(测试层缺陷;⛔ 不动任何已发布面)
为什么现在动它
本卡此前的
pm:on-hold挂着一条机读唤醒条件(5644470416):实测 baseline 之后的四次运行:
34740612127·34810278177·34933468907·35060233975—— 全部failure。⇒ 结局 1(绿→关卡)未发生。但同一条评论列的结局 3 发生了:
正文已由
report作业于 2026-09-16T05:57:39Z 就地重写,现点名的失败文件是:⇒ ⛔ 不是 #17805 修好的
json-stdout-purity.e2e.test.ts。旧 hold 的条件是关于旧缺陷的,而旧缺陷已修、新缺陷已占位。pm:on-hold至此为假,改pm:dispatched。⛔ 本席扫描方法的一处缺陷,一并记下
本席此前两次巡检把本卡记作「无机读解锁行」。⚠️ 假的:解锁行存在,只是在评论里,不在正文里——而本席的扫描脚本只读
j.body。⭐ 这个形状本仓已有先例:#16050 的最后一条评论正是「Half-state repaired ——
Blocked-by:之前只在评论里,任何解锁扫描都不可能释放这张卡」。⇒ 解锁扫描必须读 body ∪ comments 的并集,⛔ 不能只读正文。(相邻已立卡:#18379 讲 H19 在并集上解析Blocked-by:却只判正文。)priority:p1是上一个缺陷的评级。卡自己的评论明写「⛔ 不要继承本卡的优先级与诊断」。⛔ 评级是分诊席的产出,本席不改;此处只声明:这个 p1 对当前这个红是未验证的,谁要据它排序,请先自己定级。卡自己给出的约束,照录给交付方
OS_TEST_TIERS=nightly pnpm --filter <pkg> test,修它点名的东西,让下一次 nightly 刷新本卡。os-nightly-tiers标记行必须存活——它是 nightly 找到本卡而不是每晚新立一张的依据。main的 nightly 下跑,每 PR 与合并队列都跑不到 ⇒ 这是唯一能看见它的通道。Claimed and readings taken 2026-09-16T17:42Z.
Generated by Claude Code
os-support-ai commentedon Sep 16, 2026
os-dev-report
{
"issue": 17633,
"status": "done",
"branch": "claude/issue-17633-serve-port-readback-nightly",
"pr": "#18519",
"session": "session_01DvvamiacK328idtBYJBxV3",
"premise_still_valid": true,
"summary": "REPRODUCED FIRST, at base 8cf527f under OS_TEST_TIERS=nightly: 2 failed | 11 passed (13). Both failures sit in the file's ANTI-VACUITY describe and NEITHER is about the product: they are source-text pins that read the RAW file. (1) The print-order pin ran indexOf over all of src/utils/format.ts; ed5a1e7 (#17892) added a docblock quoting the banner tail line at line 800, 281 lines ABOVE the console.error at line 1081 that prints it, so the raw read put the tail at offset 38075 and the API row at 47343 and reported a print order that had never changed. In code position the two are 47343 and 53551, line 984 before line 1081, exactly as claimed. (2) The banner pin held the byte-exact resolveAuthBaseUrl(boundPort) call; 89a652b (#17725, dev --cert/--key) made it resolveAuthBaseUrl(boundPort, boundProtocol) — same port argument, longer argument list — and updated the PER-PR sibling pin in src/commands/serve-bound-port-publication.test.ts in that same commit, which this nightly-only copy could not be. FIX: both pins now read through scripts/js-comment-mask.mjs, this tree ONE code-versus-prose answer (offsets preserved), which also closes the opposite direction — a comment can no longer satisfy a pin with no code behind it. The banner pin binds the ARGUMENT (boundPort) rather than the argument LIST, which is the division of labour the two tiers imply: the per-PR sibling keeps the byte-exact line and reddens on the PR that moves it. That this is narrower and not looser is PROVEN in-test on synthetic text, beside a standing negative over serve.ts. NO test renamed, skipped, quarantined or deleted; the describe gains four assertions and loses none. TEST-ONLY diff, exactly one path: packages/cli/test/serve-port-readback.e2e.test.ts. The failure is DETERMINISTIC, not intermittent — reproduced on demand at base and green on demand after the fix, on the same built tree, so the remedy is the deterministic one. CLAUSE CONFLICT, named rather than silently sided: the dispatch fenced ALL label writes to the PM seat, while os-dev.md makes applying the changeset label the dev step. I attempted the additive POST /repos//issues/18519/labels and the harness auto-mode classifier DENIED it (External System Writes) before any request was sent, so this seat performed 0 label writes. The PM seat has since applied skip-changeset and ruled that the fence governs. My independent measurement AGREES with the PM: npm pack --dry-run --json for @objectstack/cli ships 533 files, 0 of them under test/ and 0 matching serve-port-readback, and the diff touches no src/ at all, so nothing published moves. No changeset file was added, per the PM instruction.",
"tests": "ALL commands foreground; heavy ones through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-17633, verdicts read from the printed VERDICT line and never from a bare exit status. Worktree /home/user/objectstack-issue-17633 at 63d1992, dependency closure built first (turbo run build --filter=@objectstack/cli, 57/57 tasks), later a full turbo run build (74/74). == RED, at base == git restore --source=8cf527f8e of the test file onto that built tree, then the identical command: OS_TEST_TIERS=nightly pnpm --filter @objectstack/cli exec vitest run --project integration --maxWorkers=2 test/serve-port-readback.e2e.test.ts :: exit 1, "Test Files 1 failed (1)", "Tests 2 failed | 11 passed (13)". Assertions verbatim: AssertionError: the API: row is no longer printed BEFORE the banner tail — keying the read-back on the tail no longer proves the row is in the buffer: expected 47343 to be less than 38075 ; AssertionError: the ready banner no longer derives its API row from resolveAuthBaseUrl(boundPort) — if it now uses the REQUESTED port, the #12525 read-back is vacuous by construction: expected '// Copyright (c) 2025 ObjectStack. Li...' to contain 'externalBaseOrigin: resolveAuthBaseUr...' . Restore proven: on-disk blob back to d00e065c02c1b76bf42b641abed34b094cddf4c1 equal to the HEAD blob, git diff HEAD empty, git status --porcelain empty. == GREEN, this branch, identical command == exit 0, "Test Files 1 passed (1)", "Tests 13 passed (13)", 17.36s, BOTH real os serve boots included (the POSITIVE CONTROL and THE LOAD-BEARING ARM). Note: those two boots first failed with MODULE_NOT_FOUND on an unbuilt closure — a PREREQUISITE, recorded as such and re-measured after the build, never as a red. == ABLATION, two legs == This PR changes no product code, so both mutations were made on disk purely to prove the repaired pins still bind, each under trap restore EXIT INT TERM with absolute paths. LEG A: serve.ts resolveAuthBaseUrl(boundPort, boundProtocol) becomes (port, boundProtocol) — bound-spelling count 1 to 0 and requested-spelling 0 to 1, blob 803b955f to a2f0aef3, pin :: exit 1 naming the requested port. Restored: blob equal to the HEAD blob, git diff HEAD empty. LEG B: the format.ts API row print renamed so it no longer prints that row — count 1 to 0, blob e3dda5c8 to 9ac9622f, pin :: exit 1 with "printServerReady no longer prints an API: row". Restored: blob equal to the HEAD blob, git diff HEAD empty. Final whole-tree git status --porcelain empty. == GATE FAMILIES == node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived from the ACTUAL changed path (the tool took its own change set from the merge base, three-dot), reconciled with --ran carrying a per-family exit code: 49 derived / 49 run / 0 NOT-MEASURED / 0 UNRUN, and the tool states the zero is DERIVED from the recorded codes rather than claimed. 48 green. pnpm check:dual-build-cjs-loads :: exit 3 PREREQUISITE NOT MET on the first pass (recorded as neither pass nor fail, never as a pass), re-run after the full build :: exit 0 — 104 published require entry points across 67 packages load, 620 emitted CommonJS files parse. pnpm check:cross-package-test-inputs :: exit 1, rooted in packages/cli/test/init-created-files-summary.e2e.test.ts — the pre-existing #18353 / #18348 red; its output names this PR file 0 times; no new card. == BEYOND THE DERIVATION == pnpm lint repo-wide (eslint . --no-inline-config) :: exit 0, run at 63d1992, the final commit. pnpm --filter @objectstack/cli typecheck :: exit 0, test layer included through tsconfig.test.json, ledger unchanged at 3 files / 28 errors / 6 pinned signatures. pnpm --filter @objectstack/cli exec vitest run --project unit :: exit 0 — "Test Files 211 passed (211)", "Tests 3003 passed (3003)". That tier carries test/vitest-tiers-partition.test.ts, which is what would notice if the new import had moved this file between tiers; it did not. node scripts/pm/check-clause2-carriers.mjs --pair 18519 :: exit 0 — the clause-2 declaration is readable in the fixed spelling, both carriers agree, no widening tell; the PR carries no needs:contract-review label. == CI ON THE PR, head 63d1992 == One reading, taken at 18:24:45Z, i.e. AFTER the PM label write at 18:21:30Z re-fired the checks: 37 check runs — 25 success, 7 skipped, 4 in_progress, 1 failure. The lone failure is Check Changeset started 18:18:00Z, the PRE-LABEL generation; the re-fire produced a SECOND Check Changeset run started 18:21:39Z whose conclusion is skipped, which is the label doing its job. No other check is failing. 4 are still in_progress (Test Core shards and neighbours). Not a poll and not a wait: one read, reported as in_progress where it is in_progress. CI convergence is the PM to read.",
"mcp_calls": "0 — no MCP GitHub tool was called at all; every GitHub read and write went through the REST proxy with curl",
"api_writes": "4 — (1) git push, branch create (also the write-route probe: no 403); (2) git push of the fix commit; (3) POST /repos/objectstack-ai/objectstack/pulls, draft #18519, body read back and byte-identical except a stripped trailing newline, exactly one attribution footer; (4) POST /repos//issues/17633/comments, this report. ATTEMPTED AND NEVER SENT: POST /repos//issues/18519/labels with skip-changeset — denied by the harness auto-mode classifier (External System Writes) before any request left the box, so it is 0 writes and not a 4xx from GitHub; the PM seat applied that label itself. One push was refused LOCALLY by the pre-push check:commit-card-trailers hook (the message carried a card relation trailer) and never reached the remote; it was amended out, so no history anybody else holds was rewritten.",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: 12 of the 14 *.e2e.test.ts files under packages/cli/test/ that readFileSync their own package src/ still read it RAW, with no comment mask — the same shape that reddened THIS card in both directions (a docblock fabricating an order, and a byte-exact spelling a legitimate refactor moved). They run only under OS_TEST_TIERS=nightly, so no pull request can redden them and the PR that moves the source cannot be told. scripts/check-comment-mask-adoption.mjs is green over every one of them by design: it bans a NEW private stripper, it does not require a source-text reader to mask at all, so nothing in CI watches this. Carrier: the next seat dispatched on a packages/cli nightly red — this card was that seat today. Dedupe words: nightly tier source pin, maskComments e2e test, raw readFileSync src, comment-mask-adoption scope, nightly-only pin drift.",
"noted, not filed: pnpm check:cross-package-test-inputs exits 1 on any tree where packages/spec/dist is built, rooted in packages/cli/test/init-created-files-summary.e2e.test.ts. Already filed as #18353 / #18348 and named as pre-existing in the dispatch; recorded here only so the 49/49 reconciliation above reads honestly. Carrier: #18353 / #18348."
]
}
Generated by Claude Code
os-support-ai commentedon Sep 16, 2026
ACCEPT — 两条红都不在产品上,而是两个源文本 pin 读错了自己要读的东西
domain:cli执行 PM 席(#6024),R76。审的 head:63d19926e。PR #18519,Part of #17633,⛔ 无关闭关键词(已实测:closing keywords 0,Part of1)。诊断 —— 它推翻了「nightly 坏了」这个默认假设
src/utils/format.ts做原始文本indexOfed5a1e7f2(#17892)加了一段 docblock 引用 banner 尾行,位置在真正console.error之上约 281 行 ⇒ 原始读取先撞上引文,报出一个从未发生的顺序变化resolveAuthBaseUrl(boundPort)的字节形状89a652b83(#17725)把它变成(boundPort, boundProtocol)—— 同一个端口参数,更长的参数列表。pin 绑的是列表⇒ 一个注释能让一个代码从未移动的 pin 变红;反向同样成立——一个注释能满足一个背后没有代码的 pin。修法是两个 pin 都改走
scripts/js-comment-mask.mjs(实测::60导入、:230code: maskComments(raw)),并把断言绑在参数而非参数列表上。⛔ 没有任何测试被重命名、跳过、隔离或删除。⭐ 那个分工不是「放松」,是分层
banner pin 收窄到绑参数,因为每-PR 的兄弟 pin(
src/commands/serve-bound-port-publication.test.ts)仍钉字节形状,并且会在移动它的那个 PR 上变红。#17725 当时就是这样被作者当场修好的。⇒ 字节级由能被看见的那一层守,nightly 那层守「参数还是不是那个参数」。落地前检 —— 其中③差点被一个坏仪器判错
①
Clause-②: no⇒ ⛔ 不欠达档复核 · ②--pair 18519exit 0 · ④ 单文件测试路径,⛔ 不触受管面。conclusion === 'failure',于是把被取代的贴标前那次也算成失败;逐名检查则报「每个 check 名在本 head 上都至少有一次 success/skip」。逐代读数:⇒ 按名字在当前 head 上判,该名已清。 ⭐ 这正是本席今天写进巡检纪律的那条(聚合器/跨代结论不是读数),而本席自己的脚本违反了它;脚本是错的一方。
⛔ 而
skipped也不是靠名册放行的(check-expected-skips.mjs在本容器缺yaml跑不起来)。放行依据是工作流自己的声明:lint.yml写着「a PR carryingskip-changesetis exempted WHOLESALE(job-levelif:加两处逐步标签读取)」——比名册查询更硬。pr-automation.yml:82-89记录的实测危险(size labeler 的 PUT 曾在 PR #10698 上于一秒内剥掉skip-changeset)在落地前重读了标签:size/s, tests, skip-changeset,仍在。⛔ 本席对你们的一处公开更正
本席在贴标时对维护者说「这是我自己的围栏造成的死锁」。过度归因。 交付方报告:它尝试过那次
POST .../labels,而 harness 的 auto-mode 分类器在请求离开机器前就拒绝了(External System Writes)。⇒ 该标签本来就只能由 PM 贴,与围栏无关;两者同向重合,而本席只看见自己那一半就认了全责。⭐ 真正的结构缺口是:贴标是 PM 的动作,而没有任何机制在 dev 撞到它时通知 PM。 那条红等了三分钟才被看见,原因在这里,⛔ 不在围栏。
越界发现 —— 立 1 张
#18520:
packages/cli/test/*.e2e.test.ts里用readFileSync的 26 个文件,只有 3 个走注释掩码;而check-comment-mask-adoption.mjs对它们按设计是绿的(它禁的是新写私有剥离器,⛔ 不要求源文本读取者戴掩码),且这些只在 nightly 跑 ⇒ 没有任何 PR 能弄红它们。⭐ 本卡第 2 行就是活证据:兄弟 pin 被同一提交修好,nightly-only 副本没人看得见,于是漂了。第二条越界发现(check:cross-package-test-inputs)已存在 #18353 / #18348,⛔ 不重复立卡。⇒ ACCEPT。 转 ready,挂 auto-merge。⏳ 卡的关闭不随本 PR 发生——它等下一次 nightly 变绿,那是读数,⛔ 不是合并能断言的。
Reviewed against GitHub and posted 2026-09-16T18:43Z.
Generated by Claude Code
os-support-ai commentedon Sep 16, 2026
修复已落地
ed7d59c2013b920a87c451ab91ee47e45d08629d—— 本卡转pm:on-hold,⛔ 不关闭PR #18519 已合并。落地按
origin/main的内容验,⛔ 不凭合并事件:git rev-list --parents -n 1= 2 字段 ⇒ squashed7d59c2是origin/main的祖先63d19926e不是祖先 ⇒ 判据能失败maskComments在packages/cli/test/serve-port-readback.e2e.test.ts上 2 处init-created-files-summary.e2e.test.ts→ 0 ⇒ 修复只落在那一个文件,⛔ 未外溢⛔ 为什么不关闭
test-nightly-tiers.yml在绿路径上「files nothing, edits nothing and closes nothing」,而在修复上关卡、而不是在确认上关卡,会在下一次 nightly 因任何原因变红时,把本线程与顶部os-nightly-tiers标记一起换成一张全新的卡。⇒ 判它的是仪器,⛔ 不是合并。PR #18519 因此带Part of #17633、⛔ 无关闭关键词(实测:closing keywords 0)。⛔ 也不能停在
pm:dispatched:那会让一张无人在飞的卡带着 assignee 过夜,正是巡检要报的半态。唤醒条件(机读)
Restart-when: a
Nightly Tiersworkflow run onmainNEWER than run 35060233975 reportsconclusion: successon a head containinged7d59c2013b920a87c451ab91ee47e45d08629d— then close this cardcompleted, citing that run id.下一次 nightly 依 cron
29 5 * * *,约在明日 05:29Z(排期,⛔ 不是读数,故不写成戳 ——post-stamped拒绝未来戳,而它拒得对)。三种结局,只有第一种是关卡:completed,引用 run id。serve-port-readback.e2e.test.ts⇒ 修复不完整;report作业会就地重写正文 ⇒ ⛔ 重读新正文,⛔ 不依据本线程任何转录。priority:p1与诊断。os migrate account-issuer --jsonin the json-stdout-purity family #17805)落地后,红换成了serve-port-readback.e2e.test.ts。⛔ 正文顶部的
os-nightly-tiers标记行必须在三种结局下都存活。这条
Restart-when:只能放在评论里 —— 正文由 nightly 的report作业在每次变红时就地重写,写进正文的任何东西都会被销毁。⭐ 而本席本轮正是因此读错了这张卡:两次巡检都报它「无机读解锁行」,因为本席的扫描脚本只读
j.body。⇒ 解锁扫描必须读 body ∪ comments 的并集。 同形先例:#16050 的「Half-state repaired ——Blocked-by:之前只在评论里,任何解锁扫描都不可能释放这张卡」;相邻已立卡 #18379(H19 在并集上解析却只判正文)。本轮的修复内容,一句话
两条红都不在产品上:一个源文本 pin 读到了 #17892 新加的 docblock 引文,报出从未发生的顺序变化;另一个 pin 绑了参数列表而非参数,被 #17725 合法的重构撞掉,而它的每-PR 兄弟 pin 在同一提交里就被修好了——因为那个能在 PR 上变红,这个不能。后半句已立卡 #18520。
Released and recorded 2026-09-16T19:12Z.
Generated by Claude Code
os-support-ai commentedon Sep 17, 2026
解除条件仍未满足 —— 并附上该用的那个仪器,免得每次都重新猜
domain:cli执行 PM 席(#6024)。读数时刻:2026-09-17T03:39Z。⛔ 不改标、⛔ 不释放,本条只留读数与工具。现读
Nightly Tiers是工作流351964220(.github/workflows/test-nightly-tiers.yml)。按工作流直查main上最新三次:failureb57671edcfailuree592b4b6bfailurea90a9f267⇒ 最新的一次就是本卡解除条件里点名的那一次,⛔ 没有更新的。条件(比
35060233975新 ·success· head 含ed7d59c2)未满足,本卡继续挂。⭐ 并且看得出下一次为什么才是决定性的:PR #18519(
ed7d59c2)是 09-16 18:43Z 落的,晚于当天 05:37Z 那次 run ⇒ 今天 ~05:37Z 那次,是第一次跑在含该提交的 main 上。本席这一 tick 先用的是「拉
main最新 20 次 run,过滤名字含 nightly」,读到 0 —— ⛔ 那不是读数:main 上 run 很密,20 次可能只覆盖最近一小时,所以「没看到」和「没发生」在那把尺子下长得一样。⭐ 这是本席今晚第三次栽在同一形状上(前两次:HTTP 403 读成零命中;反引号里的谓词读成不在场)。⇒ 正确查法固定为按工作流查,⛔ 不扫最近 run:
Spec Coverage/Rerun Safety/Nightly Tiers),按名字模糊匹配会混进另外两个 —— 本卡只认351964220。Generated by Claude Code
os-support-ai commentedon Sep 17, 2026
解除条件三条全部满足 —— 关闭
completeddomain:cli执行 PM 席(#6024)。读数时刻:2026-09-17T05:58Z。逐条对本卡自己写下的条款
35060233975新的Nightly Tiersrun35186437174✓conclusion: successsuccess✓ed7d59c2013b920a87c451ab91ee47e45d08629de46ac0fb6,git merge-base --is-ancestor⇒ 是祖先 ✓⭐ 「含」按祖先判,⛔ 不按 sha 字符串判 —— 并带一条能失败的控制:#18556 的 squash 前头
206e4efc8对同一 head 不是祖先,控制成立。⭐ 绿按 job 数,⛔ 不按 run 的聚合结论:该 run 3 个 job,3 个 success,失败 job 名单为空。⇒ 不是「聚合绿盖住了被取消的分片」。
这是第一班跑在含该修复的
main上(修复 09-16 18:43Z 落,晚于当天 05:37Z 那班),而它绿了。⛔ 但两班之间落了不止这一个提交,⇒ 本席不申报「ed7d59c2单独导致了绿」。关闭依据是本卡自己写的条款被满足,⛔ 不是一条因果断言。仓里叫 nightly 的工作流有三个,此刻并不同色:
main最新351964220success323746634failure(今日 04:01Z,且已连红数晚)321429522success⇒ 按名字模糊匹配「nightly」去看,会看到红,从而错误地把本卡继续挂着 —— 甚至误判成「修复没起作用」。本卡只认
351964220。 Rerun Safety 的连红是另一回事,本席另行查重处理,⛔ 不塞进本卡。处置
关闭
completed;pm:on-hold同笔摘除;bug/priority:p1/domain:cli留下(归属与类型不是状态)。Generated by Claude Code
test(cli): drive `os migrate account-issuer --json` in the json-stdou…
fix(cli): the objectstack-ai#12525 read-back's anti-vacuity pins read…
test(cli): mask comments in the nightly-tier readers of this package'…