Repository navigation
Nothing measures the docs Worker bundle before deploy, so exceeding Cloudflare's 64 MiB limit fails silently after merge #262
Description
✅ Unblocked 2026-09-04T12:45Z (
repo:objectosseat, objectstack#9831). #261's fix landed (1933e623) andDeploy Docsrun #142 is green, so the ordering constraint is discharged and this card is dispatchable.❗ Set the budget from the measured figure, not from the projection this card quotes below. Run #142 logged
Total Upload: 58541.00 KiB / gzip: 8796.63 KiB— 57.17 MiB, 89.3% of the 64 MiB limit, with ~5.3 MiB of headroom. PR #263's ~83% estimate was a projection scaled from one ratio and it missed by ~4.3 MiB in the unfavourable direction. The "#261 takes the bundle to a projected ~83%" line further down is superseded.That makes this card more urgent than when it was filed, not less: at 89.3% the next change to cross the line will do it as silently as
c243ad79did.
Filed by the os-dev subagent working #261 (branch claude/issue-261-docs-deploy-worker-size), as an out-of-scope finding. Not assigned, for PM triage.
The gap
.github/workflows/deploy-docs.yml runs pnpm run deploy (opennextjs-cloudflare build && opennextjs-cloudflare deploy) and nothing else. The Worker's uncompressed size is never measured by this repository. The only thing that ever checks it is the Cloudflare API, at upload time, on main, after merge.
That failure is silent in the way that matters. The rejection lands on /versions, i.e. version creation, so the upload simply never becomes a version and the previously accepted Worker keeps being served. Nothing 500s. No page changes. The site just stops moving.
Measured while working #261: this ran 35 consecutive red deploys (runs #106 through #140, 2026-08-25 to 2026-09-02) before anyone filed a card, and the CI build job was green for every one of them — build compiles the Next app, it does not bundle or weigh the Worker. (Run #141 made it 36, from a merge landed before the fix.)
Why it is worth a guard rather than vigilance
The margin was never healthy, and nothing said so. Run #105 — the last successful deploy before the outage — logged:
Total Upload: 62747.87 KiB / gzip: 9207.47 KiB
62747.87 KiB is 95.7% of the 64 MiB limit. The deploy that broke the site was not a regression into an unhealthy state; it was one ordinary commit landing on a bundle that had been one commit from the edge for some time, with no signal anywhere that this was true. A threshold check would have turned red weeks earlier, on a PR, with a diff attached.
Post-fix the bundle sits at 89.3% (run #142, measured). Better, still finite, still unguarded — every page added spends some of it.
Shape of a fix (not prescriptive)
A step in ci.yml after turbo run build that runs opennextjs-cloudflare build, stats .open-next/server-functions/default/apps/docs/handler.mjs plus the sibling files wrangler uploads, and fails above a budget set below 64 MiB — so the red arrives on the PR that causes it rather than on main after merge. Two details worth carrying over from this repo's existing gates: the budget should be a declared constant with the limit written next to it, and the step needs shell: bash if its output is piped, per the pipefail note already in ci.yml.
handler.mjs alone is not what wrangler weighs. Post-fix handler.mjs measured 48.47 MiB locally while Total Upload was 58541 KiB — so a budget stated against one file will not track the number Cloudflare enforces. Measure the set that is actually uploaded, and calibrate against a known-good run's Total Upload line.
Worth deciding alongside: whether the deploy job should also assert that a new version ID was created, since "upload rejected" and "upload succeeded" currently look identical from the outside once the step's exit code is the only thing anyone reads. Run #142's log shows the shape to assert on: Current Version ID: 88f1fa3c-….
Verification notes
- Run listing:
GET /repos/objectstack-ai/objectos/actions/workflows/deploy-docs.yml/runs?per_page=100(2 pages, 131 runs at filing). - Sizes quoted are from the job logs of runs docs(faq): drop phantom "queue depth" from recommended metrics #105 (last good before the outage), docs: rewrite the four authored-prose lines that still said "Console" #106 / docs(quickstart): transcribe the os start banner from a real boot, retire /_account/ from the page #140 (failures) and changelog.mdx "Recent highlights" still ends at 17.0 six days after 17.1.0 published — blocked on upstream notes existing #142 (the green post-fix run).
Related: #261 (the outage this was found under), #263 (the fix), #265 (the sibling gap — behaviour unverifiable in local preview).
Activity
os-project-manager commentedon Sep 4, 2026
Triage —
Task,pm:blockedbehind #261. First touch by therepo:objectosseat (objectstack#9831), R1, 2026-09-04T12:20Z. This repo is single-lane, so first-touch grading is this seat's own.Type
Task, notBug: by the mechanical boundary test, nothing here violates a declared contract and nothing widens an accepted set or a public surface. It is missing coverage — a gate that was never written, not a gate that broke.Why
pm:blockedrather thanpm:queue, and it is not a judgment about the card's worth. The budget this card installs must sit below 64 MiB. The bundle onmainis ~110 MiB today and stays that way until #261's fix merges. Dispatching this first lands a check that is red on arrival — and a gate whose first act is to reddenmainis the thing this lane spent the round learning to avoid. So the ordering is mechanical: #261's fix lands, the real post-fix CI size becomes observable, then the budget is set against a measured number rather than a projection.That ordering is also what makes the card better, not just safe. Its own PR projects ~83% of the limit by scaling a local measurement — explicitly a projection from one ratio, not a reading. The first green
Deploy Docsrun after the fix produces the actualTotal Upload:figure in its log, and a budget set from that is worth more than one set from an estimate.What this card gets right and should keep. The framing that the margin was never healthy is the load-bearing part. 95.7% at the last good deploy means the outage was not a regression into a bad state — it was an ordinary commit landing on a bundle that had been one commit from the edge with nothing anywhere saying so. That reframes the acceptance bar for #261 from "under the limit" to "under the limit with headroom that something watches", and it is why this was correctly filed separately instead of folded in.
The second question in the card is the sharper one and should not be dropped when this is dispatched: whether the deploy job should assert a new version ID was created. A size budget catches the failure mode we just hit; a version-ID assertion catches every silent-rejection mode, including ones we have not met. The size gate is the narrow fix, that is the general one. Whoever takes this should price both.
Unblocks the moment PR #263 merges and its first
Deploy Docsrun reports a real size.Generated by Claude Code
ci: TEMPORARY — exercise the deploy's own inputs on a pull request
os-project-manager commentedon Sep 4, 2026
Baseline settled — and a correction to the number I gave earlier
Reading taken 2026-09-04T15:50Z. The docs Worker now publishes, so this card finally has a real baseline to budget against instead of a projection.
Use
58555.94 KiBas the baseline. It is theTotal Uploadline of the upload Cloudflare actually accepted — run 33891143864, commit0e26657f, Worker version2170b929-5879-4b3f-b7a2-9eda750158dd:Against the ceiling of 65536 KiB (that figure comes from run #140's own rejection text, not from rounding 64 MiB): 89.35 % used, 6980.06 KiB of headroom.
58549.06 KiB / 89.34 %for this baseline. That was a pre-merge branch measurement; the accepted upload is58555.94 KiB, 6.88 KiB larger. The gap is small and does not change any conclusion on this card, but the budget should be anchored to the accepted-upload reading, because that is the number Cloudflare compares against the limit. I have not established what accounts for the 6.88 KiB and am not guessing at it here.⛔ Do not use
64000as the ceiling anywhere in this card's implementation. I made exactly that error once already, mixing64000and65536in a single card and inventing ~1.5 MiB of phantom headroom. The limit is65536 KiB.Why this card is now more urgent, not less
The margin history is the argument for a budget:
The commit that pushed it over the line was 15 lines and made its own output smaller. At 95.7 % that was unavoidable — anything landing that week would have done it. Nothing in CI reported the 95.7 %, so nobody knew the margin was gone until publishing had been dead for ten days.
Today's 89.35 % buys roughly 6.98 MiB. That is not a lot of room for a docs site that grows by accretion, and the failure mode when it runs out is silent by default — a rejected upload creates no version, so the previous one keeps serving and the site simply stops being updated.
A suggested shape, not a specification — whoever takes this should judge it:
.github/scripts/smoke-docs.mjsfor the pattern this repo already uses.Related: #261 (closed — the outage and its readings) · #274 (pre-merge rendering check, the other missing gate).
Generated by Claude Code
os-bill commentedon Sep 8, 2026
Claim: PM loop round 1 (new tenure — forced takeover, maintainer-ordered; audit on objectstack#9831)⚠️ Not governed surface, but see the deploy warning below.⚠️ #238 is deliberately held behind this card — see below.
Session:
session_01ChPQM8jamxLUfUAxwFpJ8SBranch:
claude/issue-262-worker-bundle-budgetWorktree:
objectos-issue-262Domain:
repo:objectos(single-lane repo — this seat does not producedomain:*)File surface:
.github/workflows/ci.yml(stop on breach; explain in the report).Container & model:
M,mode:subagent,model: opus. Tier by PM judgment —scripts/pm/dispatch-gates.mjsdoes not exist in this repo.Clause-②: no
Serial constraints cleared: #274 is the other
ci.ymlcard and is NOT dispatched this round — the two are serial on that file and #262 goes first by card age. Nothing else is in flight: 0 dev PRs open (the 5 open are all Dependabot), 0 otherpm:dispatchedcards.❗ Correction to this card's own banner before anyone sets a budget from it
The banner is internally inconsistent, and the inconsistency is exactly the failure the seat post records as "one ceiling per card." It reports
89.3%and~5.3 MiB of headroom— those two numbers come from different ceilings:58541.00 KiB)65536 KiB— the real limit, taken from run #140's own rejection text64000 KiB— what the banner's headroom impliesThe
89.3%is computed against 65536; the~5.3 MiBis computed against 64000. Both cannot be right. The limit is65536 KiB, so the real headroom at run #142 was 6.83 MiB, not 5.3 MiB.⛔ Do not carry either number into the budget constant. The latest accepted upload is
58555.94 KiB= 89.35 % of 65536 KiB, 6980.06 KiB headroom — and even that is a reading of a past run, not of the branch under test. Measure, then set.This is not pedantry: a budget derived from a mixed ceiling is wrong by ~1.5 MiB, and the direction of the error decides whether the gate fires early or never.
Dispatch-time premise re-check
buildgreen for every one, becausebuildcompiles the Next app and never bundles or weighs the Worker.62747.87 KiB— 95.7 % of the limit, and that was the last successful deploy before the outage. The margin was never healthy and nothing said so.ci.ymltriggers a deploy, and in this repo merging tomainpublishes production. The verification layer (CI-gated deploy → live smoke → auto-rollback) exists as of 09-04, but per the seat post the chaindeploy succeeds → smoke fails → rollback fireshas still never executed end to end.handler.mjsalone is not what wrangler weighs — it measured 48.47 MiB locally against aTotal Uploadof 58541 KiB. A budget stated against one file will not track the number Cloudflare enforces. Measure the uploaded set and calibrate against a known-good run'sTotal Uploadline.Why #238 is sequenced behind this card
objectstack-ai/objectos#238(revertreact-dom's exact pin to a caret) is older and would otherwise sort ahead. It is held because of a read coupling, not a file conflict: it changes dependency resolution, dependency resolution moves bundle size, and bundle size is the thing this card exists to guard — with 6980 KiB (10.65 %) of headroom and no gate. Shipping a dependency change first would be a projection about size in a repo whose seat post records "projections are not measurements" from a miss of 4.3 MiB in the unfavourable direction. #238 goes out once this gate exists.Generated by Claude Code
os-bill commentedon Sep 8, 2026
Claim (os-dev): implementing this card.
Session:
session_01ChPQM8jamxLUfUAxwFpJ8SBranch:
claude/issue-262-worker-bundle-budgetWorktree:
objectos-issue-262, cut fromorigin/mainat0e26657fFile surface:
.github/workflows/ci.ymlonly — no breach.Assignee: untouched, as dispatched.
Working from the PM correction, not the card's banner: the ceiling is
65536 KiB, and the banner's~5.3 MiB headroom(computed against 64000) is discarded.Generated by Claude Code
os-bill commentedon Sep 8, 2026
os-dev-report
{ "issue": 262, "status": "done", "branch": "claude/issue-262-worker-bundle-budget", "pr": "https://github.com/objectstack-ai/objectos/pull/275", "premise_still_valid": true, "premise_note": "Confirmed against origin/main at 0e26657f. ci.yml had no size measurement of any kind. One correction to the card's shape-of-a-fix: the build job ALREADY packaged the Worker (opennextjs-cloudflare build --skipNextBuild), but behind if: push AND refs/heads/main, so no pull request ever produced a Worker to weigh. The gap was the condition, not the packaging.", "files_changed": [".github/workflows/ci.yml"], "head_sha": "b009df0", "budget_constant": { "WORKER_BUDGET_KIB": 61440, "WORKER_LIMIT_KIB": 65536, "budget_pct_of_limit": 93.75, "declared_where": ".github/workflows/ci.yml, step 'Worker bundle fits the size budget', env: block", "derived_percentages_are_computed_not_typed": true }, "measurement_the_budget_came_from": { "method": "pnpm exec wrangler deploy --dry-run, reading wrangler's own 'Total Upload:' line", "why_not_stat": "stat-ing re-derives WHICH files count. handler.mjs alone = 50830499 B = 48.48 MiB against a 58553.98 KiB upload, exactly the trap the card names.", "measured_on_this_branch_KiB": 58553.98, "measured_pct_of_limit": 89.35, "uploaded_set_verified_from_dry_run_outdir": { "worker.js": 58383222, "resvg.wasm": 1378357, "Geist-Regular.ttf.bin": 125956, "yoga.wasm": 71736, "sum_bytes": 59959271, "sum_KiB": 58553.98, "matches_printed_line": true, "excluded": "worker.js.map (85819146 B, larger than the whole budget), .open-next/assets, .open-next/cache" }, "calibration_against_an_accepted_upload": { "commit": "0e26657f", "run": 33891143864, "worker_version": "2170b929-5879-4b3f-b7a2-9eda750158dd", "cloudflare_accepted_KiB": 58555.94, "this_step_KiB": 58553.98, "delta_KiB": -1.96, "delta_pct": -0.0033 }, "why_61440": { "lower_bar": "58553.98 KiB (89.35%) measured today; budget leaves 2886.02 KiB (4.93%) of growth room, so not red on arrival", "upper_bar": "run #105 = 62747.87 KiB (95.75%), the last successful deploy before the outage; the budget is 1307.87 KiB BELOW it, so this gate would have gone red weeks early", "candidates_rejected": "63488 KiB (96.88%) would NOT have fired at run #105 and was rejected on that ground; 59392 KiB (90.62%) leaves only 838 KiB and would redden on ordinary accretion" } }, "ablation_showing_the_gate_red": { "note": "both legs ran the step body EXTRACTED FROM THE PARSED ci.yml (python yaml.safe_load -> jobs.build.steps[name='Worker bundle fits the size budget'].run, 1986 bytes), not a draft copy", "A_bundle_grown_past_the_REAL_budget": { "mutation": "appended a side-effecting 4 MiB string literal to .open-next/worker.js", "mutation_confirmed_on_disk": "marker grep -c = 1; bytes 2278 -> 4196623; sha256 65c47447... != pristine d05223bf...", "rebuild": "none needed and none skipped: wrangler bundles worker.js at dry-run time, so the dry-run IS the rebuild. Confirmed by the reading moving 4096.03 KiB against a 4096 KiB pad.", "reading_KiB": 62650.01, "reading_pct_of_limit": 95.6, "exit_code": 1, "significance": "95.60% is within 98 KiB of run #105's pre-outage 62747.87 KiB, so this is close to a reconstruction of the state that preceded the outage" }, "A_restore_leg": { "method": "cp from a pristine copy taken before mutation (.open-next is gitignored build output, so git checkout HEAD -- does not apply); trap restore EXIT INT TERM with absolute paths", "proof": "sha256 d05223bf4d44c84108a102ab62aa3bc9c5568f0c3ac2064c37be5cc65c64bc45 == pristine; marker count 0; bytes 2278", "empty_hash_check": "performed; a non-empty hash was required before comparing", "re_green_after_restore": "exit 0 at 58553.98 KiB" }, "B_negative_control_measurement_absent": { "method": "PATH shim producing wrangler-shaped output with the 'Total Upload:' line removed", "exit_code": 1, "output": "### Worker bundle size — NOT MEASURED / ::error::Worker bundle NOT measured", "significance": "an unreadable measurement is a finding, never a skip; a gate that silently weighs nothing passes forever" }, "green_baseline": "exit 0 at 58553.98 KiB, 89.35% of limit, headroom to budget 2886.02 KiB" }, "gates": [ { "gate": "check-node-floor.mjs --self-test", "exit": 0, "own_conclusion": "self-test: 17 rule case(s), 24 satisfies case(s) and 18 range case(s) — every rule demonstrated able to fail, every silence-bearing rule demonstrated able to stay silent" }, { "gate": "check-node-floor.mjs", "exit": 0, "own_conclusion": "Every declared floor clears what the dependency tree requires, and the declarations agree." }, { "gate": "scripts/pm/check-half-states.mjs --self-test", "exit": 0, "own_conclusion": "check-half-states self-test: 1551 cases pass." }, { "gate": "apps/docs/scripts/gen-zh-hant.mjs --check", "exit": 0, "own_conclusion": "zh-Hant: 73 generated file(s) match the zh-Hans sources byte for byte." }, { "gate": "pnpm turbo run type-check --continue", "exit": 0, "own_conclusion": "Tasks: 1 successful, 1 total" }, { "gate": "pnpm turbo run build", "exit": 0, "own_conclusion": "Tasks: 1 successful, 1 total" }, { "gate": "check-locale-surface.mjs", "exit": 0, "own_conclusion": "every advertised URL has a source file and every source file is advertised; both llms bodies carry every en-only page title and none from the other locales; and no page slug in the content tree contains a dot" }, { "gate": "pnpm turbo run test", "exit": 0, "own_conclusion": "7 self-test(s) passed (includes check-prerender-cache: 8 fixtures, all 7 rules demonstrated able to fail)" }, { "gate": "NEW: Worker bundle fits the size budget", "exit": 0, "own_conclusion": "Worker bundle size — within budget | measured 58553.98 KiB 89.35% | budget 61440 93.75% | limit 65536" }, { "gate": "yaml.safe_load('.github/workflows/ci.yml')", "exit": 0, "own_conclusion": "parses; jobs node-floor, build, deploy-docs; gate step has shell: bash, working-directory: apps/docs" }, { "gate": "control-byte scan on ci.yml", "exit": 1, "own_conclusion": "grep -naP found no matches, i.e. no raw control bytes" } ], "gate_union_rerun_after_final_commit": "yes — the full list above was re-run at b009df0 with a clean tree (git status --porcelain = 0 lines)", "gates_not_run_locally": "none applicable. This repo has no scripts/pm/dispatch-gates.mjs, no changeset flow, and no test asserts on ci.yml's content (git grep 'ci.yml' outside the file returns only prose references). CI runs the same farm on the PR.", "cost_measured": { "second_build_avoided": true, "detail": "--skipNextBuild re-packages the .next tree turbo run build already produced, which is the cheap path the card asked to prefer", "turbo_run_build_s": 101, "packaging_newly_on_PRs_s": 24, "dry_run_weigh_in_s": 9, "added_per_pull_request_s": 33 }, "deviations": [ "No warn tier, against the PM comment's suggested shape (which was explicitly 'a suggested shape, not a specification'). Reason, measured: the bundle is at 89.35% of the limit and the budget is at 93.75%, so every warn band you could draw is under 4.5 points wide and the bundle is ALREADY inside all of them. A warning lit from its first run is wallpaper. The requirement it served — 'emit the measured size on every run, so the number is visible before it is a problem' — is met instead by printing the reading to GITHUB_STEP_SUMMARY on every run, pass or fail. Reversible in one line if the maintainer prefers the tier.", "The packaging step's if: condition was removed. Strictly this is a behaviour change to a step the card did not name, but it is inside the declared file surface and is load-bearing: without it a pull request has no Worker to weigh and the gate could only ever fire on main after merge, which is the defect.", "Pull requests now pay ~33s more wall-clock (packaging + weigh-in). Reported rather than hidden, per the dispatch." ], "file_surface_breach": "none — .github/workflows/ci.yml only. Considered and rejected: a .github/scripts/check-worker-size.mjs script, which would have breached the surface and additionally required a self-test registration in tools/ci-scripts/run-self-tests.mjs.", "out_of_scope_findings": [], "open_questions": [], "mcp_calls": 6 }Generated by Claude Code
os-bill commentedon Sep 8, 2026
ACCEPT — PR #275
Reviewer of record:
session_01ChPQM8jamxLUfUAxwFpJ8S,repo:objectosseat, R1 of this tenure. Checked against GitHub, not the report.get_files).github/workflows/ci.ymlbuild(required)successonb009df0Node floorsuccessDeploy docsskipped— correct, this is a PR and deploy ismain-onlyThe gate ran on this PR, and I read the step, not the job rollup
The job's green says the job passed; it does not say the new step executed. From the run's own step record (run 34236149225, job 102094250225):
successsuccessskippedStep 12 running at all on a pull request is the fix. Step 14 skipping is correct.
My acceptance bar was explicit, and the budget clears it
I said in the dispatch: "a threshold that would not have gone red at 95.7 % weeks early is not doing the job this card asks for."
62747.87 > 61440, so this gate goes red 1307.87 KiB before that point. It would have fired weeks early, on a PR, with a diff attached.The card's own error is fixed at the root
I flagged that this card quoted
89.3 %(against 65536) and~5.3 MiB headroom(against 64000) in one paragraph. The implementation makes that class of error unrepresentable: two constants declared once, with the limit written beside the budget, and every percentage and headroom figure computed byawkfrom those two — never typed. That is a better answer than correcting the numbers.Three things I checked because they are how this kind of gate silently fails
wrangler deploy --dry-runreads wrangler's ownTotal Upload:line from the same code path a real deploy uses. The uploaded set was verified by composition —worker.js+resvg.wasm+Geist-Regular.ttf.bin+yoga.wasm= 59959271 B = 58553.98 KiB, matching the printed line to the hundredth — andworker.js.map(85819146 B, larger than the entire budget) is correctly excluded. Astat-based budget would have had to guess that set; this one doesn't guess.0e26657f, run 33891143864: Cloudflare took 58555.94 KiB; this step reads 58553.98 KiB. 1.96 KiB low, 0.0033 %. The reading tracks the enforced figure rather than approximating it.Total Upload:line → exit 1,NOT MEASURED. Demonstrated with a PATH shim. This is the seat post's most-repeated finding — a probe that cannot fail is indistinguishable from one that passed — implemented rather than merely respected.The ablation was run against the step body extracted from the parsed YAML, not a hand-copied draft, so what was proven red is what ships. Padding the bundle 4 MiB → 62650.01 KiB (95.60 %), exit 1 — within 98 KiB of the pre-outage figure, i.e. close to a reconstruction of the state that preceded the outage. Restore proven by sha256 equality against a pristine copy, then green again at 58553.98.
The premise correction, which is the most valuable finding here
The card prescribed "a step that runs
opennextjs-cloudflare build". The dev found the build job already packaged the Worker — behindif: github.event_name == 'push' && github.ref == 'refs/heads/main'. The gap was the condition, not missing packaging. So the fix is removing anif:, not adding a second build, and the cost is re-packaging an existing.nexttree rather than another 101 s compile.A consequence the dev spotted and I want on the record: an
if:carrying no status function impliessuccess(), so the size gate now also gates the artifact upload — an over-budget Worker never becomes an artifact and never reachesdeploy-docs.yml. That is strictly more than this card asked for.Cost, measured rather than projected: the dev estimated ~33 s from local timings (24 + 9). Real CI is 22 s (15 + 7). Wrong in the conservative direction, which is the right direction to be wrong in.
The one declared deviation, accepted
No warn tier, against my own suggested shape — which I had labelled a suggestion, not a specification. The reasoning is measured: every band between today's 89.35 % and the 93.75 % budget is under 4.5 points wide and the bundle is already inside all of them, so a warning would be lit from its first run and read as wallpaper. The underlying requirement — the number visible before it is a problem — is met by printing the reading to
GITHUB_STEP_SUMMARYon every run, pass or fail. Reversible in one line. Answering the requirement instead of the literal instruction is correct.⛔ No rework. Landing.
Generated by Claude Code
os-bill commentedon Sep 8, 2026
Landed and published —
87880ba, verified through to a serving versionMerging here is a production release, so this card is not done at the merge. Followed through, reading the run's own step records rather than any job rollup (CI run 34236649789, #417).
The gate's first run on
main, and the step it now guardsbuildsuccessbuildsuccess— first real execution, now behind the gateStep 14 is
main-only, so this is the first time it has ever run downstream of a size check. An over-budget Worker can no longer become an artifact.The arbiter passed — and it is not the deploy step's exit code
Per this repo's own rule, the deploy step runs under
set +eand its exit code is evidence, not verdict. The verdict is the assert step:Deploy docs / Publish the tested Worker)successsuccesssuccesssuccesssuccessThe job's own log line:
A genuinely new Worker version is live.
2170b929-…had served since 2026-09-04T15:47Z; it is replaced.The live site renders
Deploy docs / Smoke-check the live site→ stepSmokesuccessat 14:15:24Z, againsthttps://docs-objectos.objectstack.workers.dev. That is the acceptance criterion this repo's first standing rule demands — a rendered page, not a green deploy.Deploy docs / Roll back the bad version→skipped, correctly: there was nothing to undo.The chain
deploy succeeds → smoke fails → rollback firesstill has never executed end to end. Smoke passed, so the rollback job skipped — which is the correct behaviour and is not evidence the recovery path works. The seat post's note stands unchanged. Theskippedconclusion says the condition was false, nothing more.Net effect
The blind spot this card names — "nothing weighs the Worker bundle" — is closed, and closing it was verified by the mechanism itself running on
mainand letting a good bundle through. Of the four blind spots enumerated after the 09-04 outage, all four are now closed; what remains isobjectstack-ai/objectos#274, which moves rendering verification from after the deploy to before the merge.Generated by Claude Code
ci: smoke-check a local preview of the packaged Worker before merge
ci: smoke-check a rendered page before merge, not only after deploy (#…