Commit Graph
5 Commits
Author SHA1 Message Date
DongHyeonkaandClaude Opus 5 d7b35cfca3 fix: bound the activation marker in bytes and give a click one observer
The marker read added a whole chunk to a running total and compared the
total afterwards, so a corrupt body could hand activation a 1 MiB chunk
against a 257-byte ceiling. It now reads at most the remaining allowance —
through a BYOB reader where the source offers one, and by refusing an
oversized chunk before copying it otherwise. A declared oversize cancels
the body it refuses instead of leaving the stream open, and the reader
lock is released on every path.

The build generator and the runtime decoder shared only the extension
table, not the path grammar. The generator happily emitted
`/assets/bad@name-abcdefgh.js`, which the decoder then refused — a correct
build failing at install time. Both now use one exported canonical path
predicate and the generator decodes its own output before returning it.

The notification click handler emitted its terminal record from inside
`process` and again from the `waitUntil` wrapper, so an ordinary click was
counted twice. Worse, a late rejection downgraded `MAYBE_APPLIED` to
`NOT_APPLIED` — telling operators the click had definitely not been
applied when nobody knew that — and the late observation ran outside
`waitUntil`, so a worker shutdown lost the evidence. There is one
observation authority per click now, certainty is monotone, only an
explicit null window confirms `NOT_APPLIED`, and the bounded tail is owned
by `waitUntil` without extending the public deadline.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 01:26:14 +09:00
DongHyeonkaandClaude Opus 5 976c8a8da4 fix: harden Service Worker activation and install lifecycle
SW-06: correlate activation, reset and drain replies by source object identity
against the captured waiting worker or controller, so an arbitrary same-origin
source cannot close this page's admission, and end a request immediately as
PROTOCOL_MISMATCH when the source is swapped instead of waiting for the drain
timeout. requestActivation() and resetOwnedCaches() are single-flight, so ten
concurrent callers share one nonce, listener and postMessage.

SW-07: an empty in-scope client set is vacuously drained rather than rejecting
a waiting worker when the requester already closed.

SW-08: isolate per-client postMessage failures. A client that cannot receive the
drain request fails immediately instead of holding pending state to the timeout,
skipWaiting() is the activation commit and its failure is a rejection, and the
accepted and reload notifications are sent afterwards as best effort.

SW-09: fence late install work. A fenced worker starts no new candidate work, a
late response body from a non-cooperative fetch is cancelled, a throwing digest
maps to a closed outcome, and a second exact delete of the owned candidate cache
is registered once the abandoned install settles - without extending the public
60s bound.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 02:03:07 +09:00
DongHyeonkaandClaude Opus 5 fce8e046ea fix: bind Web Push mutations to exact authority
WP-01: a CAS receipt is only evidence when it names the expected key and the
exact next revision. Write and remove now share one validator, so a stale or
arbitrary repository receipt can no longer be packaged as a confirmed control.

WP-05: a pre-aborted command records the operation the caller requested instead
of always reporting an inspection.

WP-06: bounded fan-out is reported honestly. The subscriptionchange client
handoff and the notification cleanup both emit countBucket and truncated, and an
incomplete cleanup returns { complete: false } and is observed as DEGRADED
separately from revoke authority.

WP-07: the user-visible native notification effect is tracked through
NOT_APPLIED, MAYBE_APPLIED and CONFIRMED phases and surfaced as observation
evidence, never as retry authorization.

WP-02, WP-03 and WP-04 stay open: they need the V2 wire protocol with server
request-shape negotiation, which belongs to the versioned-migration task rather
than this correctness pass. The ledger records them as DEFERRED_TO_MIGRATION.

Web Push remains NOT_SELECTED and AVAILABLE_NOT_COMPOSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 00:34:09 +09:00
DongHyeonkaandClaude Opus 5 58efe6ddbd fix: make Service Worker cache and removal outcomes truthful
SW-URL-01: canonicalize each generated root-relative manifest URL against the
registration scope once, re-check same-origin, and share that absolute identity
across install cache keys, fetch classification and cache lookup or delete.
Previously every verified asset fell through to the network.

SW-01: serve verified static requests only from the current release cache. A
CacheStorage-wide match could return a previous release's response for the same
URL while the delete targeted a cache that was never read. The worker scope
facade no longer exposes a wide match at all.

SW-02: cache reset deletes only names that parse as owned, so a foreign cache
sharing the ca-static-v1- prefix survives.

SW-03: unregister() resolving to false is a FAILED unregister, not UNREGISTERED.

SW-04: staged removal reports what happened - ABSENT, UNREGISTERED and PURGED
map to DISABLED, OWNERSHIP_MISMATCH to INCOMPATIBLE and FAILED to FAILED - so a
later release cannot delete the worker while a registration or owned cache is
still present.

SW-05: add the runtime-neutral service-worker-static-manifest codec that owns
exact row keys, the extension and content-type allowlist, the root-relative URL
rule and the length-prefixed canonical bytes. The generator and the build gate
hash those same bytes, and the build gate now decodes and recomputes the set
digest instead of type-casting the manifest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 00:25:11 +09:00
DongHyeonka c6da03369c refactor: 리펙토링 2026-08-01 19:39:59 +09:00