Skip to content

Console static plugin serves hashed /assets/* with no cache-control or validator: every self-hosted console load re-downloads the whole SPA #22237

Description

@hotlong

Filing gate: ②, a defect the cloud seat measured while working cloud#2692. The dev of that card recommended filing it here as the long-term home.

Dedupe: an open-issue search for console static cache-control immutable returned nothing.

Reading

At origin/main, packages/cli/src/utils/console.ts:605-607, the console static plugin (createConsoleStaticPlugin) answers every non-HTML file under the console dist like this:

const content = fs.readFileSync(filePath);
return new Response(content, { headers: { 'content-type': mimeType(filePath) } });

It sends no cache-control, no etag and no last-modified. With no validator and no lifetime, a browser cannot reuse a chunk, so every console load re-downloads the whole SPA.

Measured on hosted ObjectOS before cloud's Worker-level fix:

  • staging os-6hf1jq.objectos.app: 247 chunks, 2.8 MB on the wire, every one with transferSize > 0;
  • domContentLoaded at 67 s on the maintainer's link.

Hosted cloud now papers over this with an edge cache in its environment Worker (objectstack-ai/cloud#2692, PR objectstack-ai/cloud#2694). Self-hosted ObjectOS has no such layer and pays the full re-download on every visit.

What a fix looks like

  • For files whose names carry a Vite content hash: cache-control: public, max-age=31536000, immutable.
  • Not for the HTML shell and SPA fallback routes. readIndexHtml() rewrites per request (<base href>, OS_PAGE_REACT), so those stay revalidated.
  • ⚠️ Not for files that are copied with stable names. objectui's scripts/vite-maplibre-worker.ts puts maplibre-gl-worker.mjs and maplibre-gl-shared.mjs under assets/ with no hash, so a blanket assets/* rule would pin a changing file for a year. Gate on a hash-shaped name the way cloud#2694 does, or give those files a short lifetime plus a validator.

Acceptance

  • Test pin: a hashed asset response carries the immutable header. The shell and the two stable-named files do not.
  • Ablation: with the header removed, the pin fails.

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    Contributor

    Triage: routed domain:cli · area:devpath · pm:queue; priority:p3 stands. Direction: immutable only for hash-named assets

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T09:05Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in packages/cli/src/utils/console.ts (createConsoleStaticPlugin, about :605) ⇒ domain:cli; rationale: that lane's.

    • Grade p3 stands: hosted cloud already has an edge cache (objectstack-ai/cloud PR feat(metadata-protocol): enforce package namespace prefix for Studio-authored packages #2694). This is self-hosted load time.
    • The rule: immutable only for Vite hash-shaped names, gated as cloud#2694 gates. The HTML shell and SPA fallback stay revalidated. The two stable-named maplibre files get a short lifetime plus a validator. Sibling: objectstack-ai/cloud#2697 fixes the same trap on the control plane's _headers.
    • Pins: as the card says, with the ablation.
  2. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1
    Session: session_01BmsuLyUeuG5CNpZFMH1jzS
    Account: os-elon-musk (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-22237-console-asset-cache
    Worktree: objectstack-issue-22237
    Domain: domain:cli
    Seat: domain:cli#1
    File surface: packages/cli/src/utils/console.ts (createConsoleStaticPlugin's static-file arm, about :676), a new test beside it under packages/cli/src/utils/, and .changeset/22237-*.md (@objectstack/cli patch). Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: default (opus). dispatch-gates --tier prints "no path-derived mandate"; the hash-shape gate and the stable-named exception are design judgment, so this is not a mechanical card.
    Clause-②: no
    Responsibility: packages/cli/src/utils/console.ts (createConsoleStaticPlugin) produces the missing lifetime and validator | the hosted control plane's edge cache covers hosted deployments only; for self-hosted, none | every self-hosted console load reaches it; it is used today (self-hosted deployments)
    Thread-read: 6056523960
    Serial constraints cleared: no open PR touches packages/cli/src/utils/console.ts (REST file lists of all 14 open PRs, read in this round). Lane queue sibling #22225 also needs console.ts (its 404 page) and serves behind this card. Fold-or-serial answer: SERIAL, because gate ① fails (two defects with two fixes: a cache lifetime, and a missing-Console page). Lane siblings in flight: #22310 and #22190, disjoint.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    {
    "issue": 22237,
    "status": "done",
    "branch": "claude/issue-22237-console-asset-cache",
    "pr": "#22395",
    "session": "session_01BmsuLyUeuG5CNpZFMH1jzS",
    "premise_still_valid": true,
    "summary": "createConsoleStaticPlugin's static-file arm now answers a content-hashed build output (directly under assets/, NAME-HASH.EXT with Vite's 8-char base64url hash containing an uppercase letter, digit or _) with cache-control 'public, max-age=31536000, immutable'; every other non-HTML file gets 'public, max-age=300' plus a strong sha256 content etag with If-None-Match -> 304; HTML (shell, direct index.html, SPA fallback) is unchanged. The hash shape was measured on the published @objectstack/console@17.7.0 tarball: 2457 files under assets/, the rule matches all 2455 hashed chunks and excludes exactly the 2 stable-named maplibre files (no other stable-named file exists in assets/). H1, H2, H3 and H4 all held; Zone 3's stat-derived validator was measured and NOT used: plain tar extraction of that tarball stamps every one of its 2471 files 1985-10-26 08:15 (npm install stamps install time instead), so a stat validator can 304 changed bytes. Card assignee untouched; the newest Claim (6072183286) names this branch.",
    "tests": "New pin file packages/cli/src/utils/console.asset-cache.test.ts: 26 passed (8 real hashed names immutable; missing hashed asset 404 with no lifetime; 2 maplibre + favicon.svg + manifest.json + 4 edge lookalikes short-lived with etag, never immutable; 304 on own/weak/list/* tag, 200 on another tag; same-size same-1985-mtime new bytes get a new tag; 3 HTML routes no lifetime). Ablations, each committed first, mutation via scripts/ablation-replace.mjs wrap mode (anchor hit 1 -> 0, blob 66279f318c21 changed, restore proven blob == HEAD and git diff HEAD empty, plus a trap restore in the leg script), run under the verify lock: A1 the card's ablation (drop the immutable header) -> 8 failed / 18 passed, exactly the 8 hashed pins; A2 blanket assets/* immutable -> 10 failed / 16 passed; A3 drop the hash character condition -> 1 failed (assets/pdf-renderer.js); A4 etag from byte length -> 1 failed (same-size same-mtime replacement). The subject is imported as ./console.js relative to src (no package exports, no dist), so no rebuild or ablation-dist-preflight leg applies. cli unit layer: 269/271 files, 3965 passed / 29 skipped; the 2 remaining pin files refused only for an unbuilt packages/cli and passed 29/29 after the build. cli typecheck exit 0. Full pnpm lint exit 0. Gate union at 174339b (final commit): 65/65 exit 0; dispatch-gates --ran: 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN. Integration layer declared to CI (diff reaches no spawn entry or kernel boot).",
    "gates": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
    "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 :: ✓ check-adr-0087-registration --self-test: 441 assertions over real temp git repos (real scan()/assertInputs() path)",
    "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ This diff introduces no major bump.",
    "node scripts/check-changeset-no-major.mjs --self-test :: exit 0 :: ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + th...",
    "node scripts/check-ci-filter-parity.mjs :: exit 0 :: OK: all 20 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every $TURBO_ROOT$ input of a build Build Core runs) and all 11...",
    "node scripts/check-closing-keyword-parity.mjs :: exit 0 :: check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 10380 tracked file(...",
    "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 :: ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
    "node scripts/check-comment-mask-adoption.mjs :: exit 0 :: OK check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/, all 14 recorded and every recorded row still reached (13 unconverted,...",
    "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 :: PASS check-comment-mask-adoption --self-test (0 failure(s))",
    "node scripts/check-comment-mask-corpus.mjs :: exit 0 :: ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 8496 files, 0 disagree, 0 unparseable, 151.5s (comparator self-test: 26 cases pass).",
    "node scripts/check-dts-emitted.mjs --self-test :: exit 0 :: check-dts-emitted self-test: all assertions passed.",
    "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
    "node scripts/check-empty-changeset.mjs --self-test :: exit 0 :: ✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)",
    "node scripts/check-issue-citations.mjs :: exit 0 :: ✅ check-issue-citations: no issue citations added against 117d34d (1 file(s) read).",
    "node scripts/check-keyed-text-bounds.mjs :: exit 0 :: ✓ check:keyed-text-bounds: 111 .object.ts files under packages/
    + apps/
    * + examples/** (walk is repo-wide; 0 outside), 116 object declarations, 241 declared index ...",
    "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 :: PASS check-keyed-text-bounds --self-test (0 failure(s))",
    "node scripts/check-platform-object-tenancy-census.mjs :: exit 0 :: ✓ platform-object tenancy census matches the tree: 83 platform-namespace objects, 48 in the machinery's reach, 35 outside it, every exclusion explained by a declaratio...",
    "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 :: ✓ check-platform-object-tenancy-census self-test: all checks pass (83 objects, 35 outside the machinery)",
    "node scripts/check-plugin-teardown-shape.mjs :: exit 0 :: ✓ check:plugin-teardown-shape: 69 Plugin implementation(s) across 7920 source(s) under packages/; every teardown-shaped method (stop / shutdown / close / dispose) si...",
    "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 :: ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay green, every roster n...",
    "node scripts/check-registry-log-declared.mjs :: exit 0 :: OK: 73 vitest-running package(s) walked, 11 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/silent).",
    "node scripts/check-registry-log-declared.mjs --self-test :: exit 0 :: self-test OK: 16 cases + level-vocabulary read + population declaration + real-tree selection floor.",
    "node scripts/check-rest-log-spy-declared.mjs :: exit 0 :: OK: 30 of 270 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
    "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 :: ✓ the real tree yields a NON-EMPTY observer population that is a strict subset of its test files",
    "node scripts/check-system-context-census.mjs :: exit 0 :: check-system-context-census: OK — 118 elevation read sites in 20 packages across 55 files, living in 101 symbol(s); the page cites 114 symbol(s) against 114 required, ...",
    "node scripts/check-system-context-census.mjs --self-test :: exit 0 :: check-system-context-census --self-test: all cases passed",
    "node scripts/check-undeclared-dep-imports.mjs :: exit 0 :: ✓ check:undeclared-dep-imports: 80 workspace packages under packages/
    + apps/** + examples/**, 2951 non-test src files, 2397 @objectstack/* specifiers (0 assembled, ...",
    "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 :: PASS check-undeclared-dep-imports --self-test (0 failure(s))",
    "node scripts/docs-audit/check-affected-docs.mjs :: exit 0 :: ✓ affected-docs self-test: 605 cases pass.",
    "node scripts/docs-audit/check-drift-comment.mjs :: exit 0 :: ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
    "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 :: ✓ self-test passed",
    "node scripts/release-pending-publish.mjs --self-test :: exit 0 :: ✓ release-pending-publish self-test: 92 cases across 22 batteries pass.",
    "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 :: ✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 2955 source file(s) all carry their unit in the key name (or in a sibling unit, or under a decl...",
    "pnpm check:changeset-gate-self-tests :: exit 0 :: ✓ check-changeset-no-major --self-test: 339 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in both directions + th...",
    "pnpm check:cross-package-test-inputs :: exit 0 :: OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split "test:repo" task); 13 walked root(s) judged, 3...",
    "pnpm check:dispatcher-error-vocabulary :: exit 0 :: door typing (#9098): packages/rest/src/error-response.ts checked for the typed author-side responder, the absence of a second sendError, and decided refusals bypassi...",
    "pnpm check:doc-authoring :: exit 0 :: ✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 89431 string(s) read in 1283 parsed source(s) (seen floor 40000...",
    "pnpm check:driver-memory-census :: exit 0 :: check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states "#6664 census: 2 ruled consumers". This gate po...",
    "pnpm check:dts-closure :: exit 0 :: check-dts-closure self-test: all assertions passed.",
    "pnpm check:dual-build-cjs-loads :: exit 0 :: · declared UNREACHABLE declaration: @objectstack/core#./logger — Same declaration set as @objectstack/core#. — the subpath is emitted from the same tsup pass and spl...",
    "pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: OK — 990 pinned, 129 in the DEBT ledger, 3 exempt.",
    "pnpm check:gitlink-declared :: exit 0 :: check-gitlink-declared: OK (10380 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declare).",
    "pnpm check:i18n :: exit 0 :: check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys).",
    "pnpm check:i18n-coverage :: exit 0 :: check-i18n-coverage: OK (13 config(s), 621 baselined untranslated string(s), none new).",
    "pnpm check:i18n-walk-parity :: exit 0 :: ✓ check-i18n-walk-parity: 11 declared group(s), 9 walked, 2 exempted — every declared group has an extractor face.",
    "pnpm check:issue-citations :: exit 0 :: ✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes kept apart, both board strate...",
    "pnpm check:lean-entry-closure :: exit 0 :: ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
    "pnpm check:logger-receiver-detach :: exit 0 :: OK every log channel keeps its receiver: 3234 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
    "pnpm check:nul-bytes :: exit 0 :: check-nul-bytes: OK (scanned 10371 text file(s) -- 10371 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).",
    "pnpm check:objectql-double-limit :: exit 0 :: OK ObjectQL double limit conformance holds: 470 double(s) graded, 273 apply the caller's bound or refuse it loudly.",
    "pnpm check:objectui-changeset :: exit 0 :: ✓ objectui-range --self-test: all checks passed",
    "pnpm check:org-identifier :: exit 0 :: check-org-identifier: OK (3256 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias).",
    "pnpm check:page-declaration-shape :: exit 0 :: not judged: 108 inline literal(s) in 1 file(s) (migration fixtures — anonymous, so nothing to discover), 176 string page-name reference(s) (book/doc nav).",
    "pnpm check:pm-changeset-deadline-census :: exit 0 :: ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, the controls that make a zero a reading, the verdict truth...",
    "pnpm check:published-files :: exit 0 :: ✓ check:published-files — 69 publishable package(s) of 80 workspace member(s) declare a files whitelist that covers every entry point plus CHANGELOG.md and admits no...",
    "pnpm check:query-options-erasure :: exit 0 :: ✓ query-options-erasure ratchet holds: 65 unswept non-test site(s) in 17 file(s), none new, and every file measured parsed. Every other non-test file under packages/ i...",
    "pnpm check:refd-timer-probe :: exit 0 :: OK check-refd-timer-probe: 8491 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhere else.",
    "pnpm check:slot-lookup :: exit 0 :: ✓ slot-lookup ratchet holds: 104 unswept site(s) in 25 file(s), none new, and every file in the population parsed. Every other file under packages/ is covered by pnpm...", "pnpm check:sourcemap-no-sources-content :: exit 0 :: check-sourcemap-no-sources-content self-test: all assertions passed.", "pnpm check:test-source-alias :: exit 0 :: check-test-source-alias OK — 73 packages with tests scanned; 60 registered as still resolving a workspace dep through dist/`; 53 published subpath(s) resolved through...",
    "pnpm check:tier-file-adoption :: exit 0 :: OK: 80 workspace package(s) walked, 82 nightly-tier test file(s) on disk (82 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
    "pnpm check:type-check-coverage :: exit 0 :: check-type-check-coverage: OK — 79/80 workspace packages type-checked (plus the root), 1 in the DEBT ledger (26 frozen raw errors, https://github.com/objectstack-ai/ob...",
    "pnpm check:type-check-debt :: exit 0 :: check-type-check-coverage --re-measure: OK — 1 ledger entr(ies) re-measured in 16.8s, 26 raw tsc error(s) total, none above its recorded number.",
    "pnpm check:watch-hint-literal :: exit 0 :: ✓ check-watch-hint-literal: 72 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 48, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH_HINTS ...",
    "pnpm check:where-matcher :: exit 0 :: ✓ where-matcher conformance holds: 491 matcher(s) discovered, 491 answer the combinator battery correctly or refuse it loudly (321 refuse).",
    "pnpm lint (full, not narrowed) :: exit 0 :: eslint . --no-inline-config completed with no messages at 174339b (204s on the shared box)",
    "pnpm --filter @objectstack/cli typecheck :: exit 0 :: check:test-typecheck: OK -- @objectstack/cli's test layer compiles under packages/cli/tsconfig.test.json; tsc --noEmit (tsconfig.json) lists console.asset-cache.test.ts (--listFilesOnly count 1)",
    "pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 src/utils/console.asset-cache.test.ts :: VERDICT command-exit 0 :: Test Files 1 passed (1), Tests 26 passed (26)",
    "pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 :: VERDICT command-exit 1 :: Test Files 2 failed | 269 passed (271), Tests 3965 passed | 29 skipped -- the 2 were published-subpath-console.pin and published-subpath-hook-body.pin refusing 'packages/cli is not built' (prerequisite, not a measurement)",
    "pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/published-subpath-console.pin.test.ts test/published-subpath-hook-body.pin.test.ts (after building packages/cli) :: VERDICT command-exit 0 :: Test Files 2 passed (2), Tests 29 passed (29)",
    "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.txt :: exit 0 :: Run reconciliation -- 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN (a DERIVED zero -- all 65 recorded an exit code and none of them is 3)"
    ],
    "ci": "in_progress at report time: head 174339b, 31 check runs = 11 success, 3 skipped, 17 in_progress, 0 failure (one read, no polling)",
    "line_budget": "none set; +337 / -1 across 3 files",
    "files_changed": [
    ".changeset/22237-console-asset-cache.md (+11 / -0)",
    "packages/cli/src/utils/console.asset-cache.test.ts (+227 / -0, new)",
    "packages/cli/src/utils/console.ts (+99 / -1)"
    ],
    "deviations": [
    "Zone 3 route: the validator is a sha256 etag of the bytes, not etag/last-modified from stat. Measured reason: plain tar extraction of @objectstack/console@17.7.0 keeps the fixed 1985-10-26 08:15 pack mtime on all 2471 files (npm install stamps install time instead), so a stat validator can answer 304 for changed bytes. Pinned by the same-size, same-mtime replacement case (ablation A4).",
    "Short lifetime chosen as max-age=300. The ruling says 'short lifetime plus a validator' and names no number. PR Acceptance notes state the bounded upgrade window this leaves for the maplibre worker, and that no-cache would close it.",
    "First gate pass hit 5 PREREQUISITE NOT MET (exit 3) refusals before the workspace build: check-plugin-teardown-shape --self-test (shallow clone), check:dual-build-cjs-loads, check:i18n, check:i18n-coverage, check:i18n-walk-parity. Not counted as measurements. Cleared by a locked full 'turbo run build --filter=!@objectstack/docs' (72 tasks, 58 cached) and 'git fetch --depth=1 origin 621a487' (shared object store; it adds one shallow root). All 5 then exited 0, and the final union reran all 65.",
    "The full unit-layer run was not repeated after the build. Only its 2 prerequisite-refused files were re-run, and they passed 29/29. HEAD did not change between the two runs.",
    "Harness attribution reminder vs. the role file: commits carry the model-free 'Claude-Session' + 'Co-authored-by: Claude' trailer pair, and the PR body ends with the single session-URL footer. The harness's model-named trailer and its 'Generated with' line were not used, per the role file and AGENTS.md."
    ],
    "mcp_calls": "0 -- no MCP GitHub tool called",
    "api_writes": "3 REST writes, each one POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204) as the session, executed by the fleet-write relay as objectstack-fleet[bot]: (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls, draft PR #22395, run 37873651695, body read back byte-identical (8592 bytes); (2) label-write assign -> POST /repos//issues/22395/assignees ['os-elon-musk'], run 37873756215, read-back MATCHES, zero labels written (the PR ships a patch changeset, so skip-changeset does not apply); (3) this os-dev-report comment -> POST /repos//issues/22237/comments. Plus 2 git pushes (empty-branch probe, then 174339b), which are not REST writes.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: 承接者:无 · noted, not filed: the static arm answers EVERY existing .html file under the dist with index.html's rewritten content, not its own. The measured dist has only index.html, so nothing observable changes (observation; PR Acceptance notes).",
    "carrier: 承接者:无 · noted, not filed: /runtime/assets/:filename (createRuntimeAssetsPlugin) still sends 'public, max-age=3600' with no validator; contentEtag and ifNoneMatchHits are the helpers to share if it ever gains one (H4, note only; PR Acceptance notes)."
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    Contributor

    ACCEPT: PR #22395 (head 174339b9)

    domain:cli seat (#6024) · session_01BmsuLyUeuG5CNpZFMH1jzS · 2026-10-09T02:20Z. Reviewed against GitHub, not against the report.

    • PR shape: draft, base main; the body opens with Fixes #22237 (the only closing keyword in the body) and carries a line-initial Clause-②: no. Three files, +337 / -1: console.ts, a new console.asset-cache.test.ts, and .changeset/22237-console-asset-cache.md (@objectstack/cli patch). No content/docs/releases/, no governed path, nothing unrelated to the card.
    • Diff read: only createConsoleStaticPlugin's static-file arm changes.
    • Triage direction met: immutable only for hash-shaped names; the shell and fallback stay revalidated; the stable-named maplibre files get a short lifetime plus a validator.
    • Deviation accepted: the validator hashes the content instead of reading the file's stat. The reason is measured: plain tar extraction of the published console tarball gives every file the same fixed mtime, so a stat validator could answer 304 for changed bytes. The same-size, same-mtime replacement pin (ablation A4) holds it.
    • The rule comes from a measurement: over the published @objectstack/console@17.7.0 dist, 2,455 of 2,457 files under assets/ match, and the 2 exclusions are exactly the maplibre files. Four lookalikes pin the rule's edges.
    • Spot-checked: the test runs the real plugin on a real HonoHttpServer; @objectstack/plugin-hono-server is already a declared dependency of @objectstack/cli. The card's ablation (header removed) gives 8 failed, exactly the 8 hashed pins.
    • Gates: the dev's local union at 174339b9 (the PR's current head) was 65 of 65 exit 0, --ran reconciled with 0 unrun, and the full pnpm lint exited 0. CI: in progress at this read (17 success, 3 skipped, 11 running, 0 failed). Landing waits for every check to be green.
    • Out of scope, noted, not filed (both are PR Acceptance notes and are below the filing gate): every .html under the dist answers with the shell's content, and the measured dist has only index.html; /runtime/assets sends a lifetime with no validator.
    • Next: once every check on 174339b9 is green: ready, auto-merge, then the queue.

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    Contributor

    Landed: PR #22395 → a55fdc470b, a single-parent queue squash

    domain:cli seat (#6024) · session_01BmsuLyUeuG5CNpZFMH1jzS · 2026-10-09T03:28Z


    Generated by Claude Code

  6. added a commit that references this issue on Oct 9, 2026
    a55fdc4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:cliperformancepriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions