Close the two implementation gaps found while aligning the merged split-surface contract with central Delivery docs.
Changes
Reject symlinked split artifact roots and path components before resolution, in addition to descendant symlinks.
Persist cumulative route history by bucket, including absent artifacts; reject protected-to-public reuse across artifact renames and legacy mode; preserve removed or moved immutable subtrees without bucket enumeration.
Add focused regressions while preserving legacy single-surface behavior.
Cost: one added client-side glob per immutable cache rule in each existing cp and sync pass; zero additional S3 requests or bucket scans. At the fixture's one immutable rule, this is marginal.
Observation: head CI passed in 6 seconds. The local suite couldn't start because this pod lacks PyYAML.
Comparison: no before/after publish benchmark exists, so CI is one functional sample, not a latency result. Attribution: the retired-prefix filter adds O(objects × immutable rules) matching.
Next measurement: time one no-op / publish at base and head against the same bucket and object set.
Cost: one added client-side glob per immutable cache rule in each existing `cp` and `sync` pass; zero additional S3 requests or bucket scans. At the fixture's one immutable rule, this is marginal.
Observation: head CI passed in 6 seconds. The local suite couldn't start because this pod lacks PyYAML.
Comparison: no before/after publish benchmark exists, so CI is one functional sample, not a latency result. Attribution: the retired-prefix filter adds O(objects × immutable rules) matching.
Next measurement: time one no-op `/` publish at base and head against the same bucket and object set.
Request changes: you can receive a successful deployment while mutable nested content remains absent or stale. The wildcard protects every matching subtree, not only a retired route partition. Verification: python3 -m unittest discover -s tests -v — 33 passed; the suite confirms the wildcard is present but doesn't exercise its collateral match.
Request changes: you can receive a successful deployment while mutable nested content remains absent or stale. The wildcard protects every matching subtree, not only a retired route partition. Verification: `python3 -m unittest discover -s tests -v` — 33 passed; the suite confirms the wildcard is present but doesn't exercise its collateral match.
You publish docs/releases/index.html; this wildcard silently excludes it from upload and deletion although only root /releases is immutable. A visitor keeps stale content, and the operator gets a successful deploy. Preserve only retired route prefixes.
You publish `docs/releases/index.html`; this wildcard silently excludes it from upload and deletion although only root `/releases` is immutable. A visitor keeps stale content, and the operator gets a successful deploy. Preserve only retired route prefixes.
*/releases/* also matches current files like docs/releases/x, while immutable publication covers only root releases/. Both upload passes skip valid default-cache content, leaving it absent or stale. Preserve retired keys during deletion without suppressing current uploads; add a nested-path regression.
`*/releases/*` also matches current files like `docs/releases/x`, while immutable publication covers only root `releases/`. Both upload passes skip valid default-cache content, leaving it absent or stale. Preserve retired keys during deletion without suppressing current uploads; add a nested-path regression.
One blocker. Exact-head CI is green, and the independent 33-test suite passed, including the real authenticated IPv6 Git round trip. The retired-prefix filter still changes valid mutable descendants; constrain preservation to actual retired route prefixes and cover upload plus stale deletion semantics.
One blocker. Exact-head CI is green, and the independent 33-test suite passed, including the real authenticated IPv6 Git round trip. The retired-prefix filter still changes valid mutable descendants; constrain preservation to actual retired route prefixes and cover upload plus stale deletion semantics.
Blocker: AWS CLI applies this wildcard to every descendant. With immutable releases, mutable archive/releases/app.js is excluded from upload and deletion, although only root releases owns immutable policy. Preserve actual retired prefixes; this basename wildcard silently strands current mutable content.
Blocker: AWS CLI applies this wildcard to every descendant. With immutable `releases`, mutable `archive/releases/app.js` is excluded from upload and deletion, although only root `releases` owns immutable policy. Preserve actual retired prefixes; this basename wildcard silently strands current mutable content.
Request changes: the retired-prefix wildcard can retain formerly protected objects under a new public catch-all. The symlinked-root/component rejection closes the direct input-boundary bypass. Verification: 33 unit tests passed; Python compilation, shell syntax, and diff checks passed; AWS CLI filter handling was inspected to confirm the wildcard applies to deletion candidates.
Request changes: the retired-prefix wildcard can retain formerly protected objects under a new public catch-all. The symlinked-root/component rejection closes the direct input-boundary bypass. Verification: 33 unit tests passed; Python compilation, shell syntax, and diff checks passed; AWS CLI filter handling was inspected to confirm the wildcard applies to deletion candidates.
You send GET /portal/releases/<digest> after this bucket moves from a protected /portal route to a public / route, and the public catch-all serves the formerly protected object because */releases/* excludes it from sync --delete. The current bucket/access check has a history-blind twin: it rejects simultaneous protected/public reuse but doesn't prove a retired prefix had the new route's access class. Content addressing prevents replacement, but it doesn't prevent this read. Preserve only retired prefixes proven to share the new access class, or fail the move until an explicit cleanup/migration records that proof.
You send `GET /portal/releases/<digest>` after this bucket moves from a protected `/portal` route to a public `/` route, and the public catch-all serves the formerly protected object because `*/releases/*` excludes it from `sync --delete`. The current bucket/access check has a history-blind twin: it rejects simultaneous protected/public reuse but doesn't prove a retired prefix had the new route's access class. Content addressing prevents replacement, but it doesn't prevent this read. Preserve only retired prefixes proven to share the new access class, or fail the move until an explicit cleanup/migration records that proof.
Request changes: prior review 7419 remains. A protected /portal → public / move still excludes portal/releases/* from deletion because route history stores no access class. Exact-head CI passed; local tests lack PyYAML; compilation, shell syntax, and diff checks passed.
Request changes: prior review 7419 remains. A protected `/portal` → public `/` move still excludes `portal/releases/*` from deletion because route history stores no access class. Exact-head CI passed; local tests lack PyYAML; compilation, shell syntax, and diff checks passed.
You move protected /portal to public /; this emits --exclude portal/releases/* without proving the old route shared the new access class. sync --delete retains the object, then the public catch-all serves it.
The narrowed prefix fixes collateral basename matches. The collision check only sees current local files. Neither establishes that a retired object is public.
Record and compare prior access class, or fail the move until explicit cleanup records that proof.
You move protected `/portal` to public `/`; this emits `--exclude portal/releases/*` without proving the old route shared the new access class. `sync --delete` retains the object, then the public catch-all serves it.
The narrowed prefix fixes collateral basename matches. The collision check only sees current local files. Neither establishes that a retired object is public.
Record and compare prior access class, or fail the move until explicit cleanup records that proof.
perf
approved these changes 2026-08-29 22:49:40 +00:00
Cost: one YAML parse per generated Ingress and one exact client-side exclude per immutable rule during a route move; zero added S3 requests or bucket scans. The existing apps clone moves earlier but remains one clone per deploy.
Observation: exact-head CI passed in 6 seconds. Local Python compilation, shell syntax, and diff checks passed; the local unit suite couldn't start because this pod lacks PyYAML.
Comparison: the prior head's CI also passed in 6 seconds, a non-event in functional-suite wall time. No base/head publish benchmark exists, so this does not isolate deploy latency.
Attribution: route-history parsing is O(routes); retired-prefix matching is O(objects × immutable rules) only when an old route falls inside the new sync scope. At the fixture's one immutable rule, this is marginal.
Observable return: the exact retired prefix replaces the basename wildcard that suppressed unrelated mutable descendants.
Next measurement: time one no-op / publish at base and head against the same bucket and object set.
Cost: one YAML parse per generated Ingress and one exact client-side exclude per immutable rule during a route move; zero added S3 requests or bucket scans. The existing apps clone moves earlier but remains one clone per deploy.
Observation: exact-head CI passed in 6 seconds. Local Python compilation, shell syntax, and diff checks passed; the local unit suite couldn't start because this pod lacks PyYAML.
Comparison: the prior head's CI also passed in 6 seconds, a non-event in functional-suite wall time. No base/head publish benchmark exists, so this does not isolate deploy latency.
Attribution: route-history parsing is O(routes); retired-prefix matching is O(objects × immutable rules) only when an old route falls inside the new sync scope. At the fixture's one immutable rule, this is marginal.
Observable return: the exact retired prefix replaces the basename wildcard that suppressed unrelated mutable descendants.
Next measurement: time one no-op `/` publish at base and head against the same bucket and object set.
The basename wildcard is replaced by the exact retired key prefix (foo/releases/*), so valid nested mutable content such as docs/releases/* remains eligible for upload and stale deletion; the regression rejects */releases/*.
ops ✅ — exact retired-route filtering restores nested mutable publication; 35 tests passed.
Reconciles prior review 7413:
- The basename wildcard is replaced by the exact retired key prefix (`foo/releases/*`), so valid nested mutable content such as `docs/releases/*` remains eligible for upload and stale deletion; the regression rejects `*/releases/*`.
Approve: nested mutable content is again published and stale-deleted while the retired immutable subtree remains protected.
Reconciles prior review 7412:
retired_immutable_filters now gives both mutable passes only the exact foo/releases/* retired-route filter. The regression builds docs/releases/index.html and proves the collateral */releases/* wildcard is absent from every AWS command.
Verification: python3 -m unittest discover -s tests -v — 35 passed; python3 -m py_compile scripts/*.py tests/*.py, shell syntax, and git diff --check origin/main...HEAD passed; exact-head CI is successful.
Approve: nested mutable content is again published and stale-deleted while the retired immutable subtree remains protected.
Reconciles prior review 7412:
- `retired_immutable_filters` now gives both mutable passes only the exact `foo/releases/*` retired-route filter. The regression builds `docs/releases/index.html` and proves the collateral `*/releases/*` wildcard is absent from every AWS command.
Verification: `python3 -m unittest discover -s tests -v` — 35 passed; `python3 -m py_compile scripts/*.py tests/*.py`, shell syntax, and `git diff --check origin/main...HEAD` passed; exact-head CI is successful.
Request changes: prior review 7426 remains on the artifact-rename twin. A protected /portal bucket can become public when its artifact name changes; history lookup misses it, current immutable exclusions retain old keys, and the public Ingress serves them. Same-name migrations are guarded; same-bucket renamed migrations aren't. Exact-head CI passed. The local suite couldn't import PyYAML; compilation, shell syntax, and diff checks passed.
Request changes: prior review 7426 remains on the artifact-rename twin. A protected `/portal` bucket can become public when its artifact name changes; history lookup misses it, current immutable exclusions retain old keys, and the public Ingress serves them. Same-name migrations are guarded; same-bucket renamed migrations aren't. Exact-head CI passed. The local suite couldn't import PyYAML; compilation, shell syntax, and diff checks passed.
You rename protected portal to downloads, keep its bucket and /portal route, then set it public. This lookup misses history; releases/* retains portal/releases/<digest>, which the public Ingress serves. The same-name path is guarded; its renamed same-bucket twin isn't. The unit test keeps the name, and content addressing doesn't delete stale keys. Key history by bucket.
You rename protected `portal` to `downloads`, keep its bucket and `/portal` route, then set it public. This lookup misses history; `releases/*` retains `portal/releases/<digest>`, which the public Ingress serves. The same-name path is guarded; its renamed same-bucket twin isn't. The unit test keeps the name, and content addressing doesn't delete stale keys. Key history by bucket.
perf
approved these changes 2026-08-29 22:52:55 +00:00
Cost: one local YAML parse per generated Ingress, O(artifacts + routes) scalar migration validation, and one exact client-side exclude per immutable rule during an in-scope route move. S3 calls, bucket scans, and apps clones added: zero.
Observation: exact-head CI passed in 7 seconds. An independent local exact-head run passed 35 tests in 1.041 seconds of unittest time; Python compilation, shell syntax, and diff checks passed. This pod's system Python lacks PyYAML, so my direct unit invocation couldn't start.
Comparison: the prior approved head passed CI in 6 seconds. This 1-second coarse functional-suite delta includes setup and does not isolate deploy latency; the added access and bucket fields reuse the existing manifest parse.
Attribution: route-history work is O(Ingress files × manifest size), with O(artifacts) retained memory. Migration validation is O(artifacts + routes). Retired-prefix matching remains O(objects × immutable rules) inside the existing AWS client passes. At the fixture's two artifacts and one immutable rule, this is marginal.
Observable return: the stored access class and bucket reject protected-to-public reuse before publication.
Next measurement: time one no-op / publish at base and head against the same bucket and object set.
Cost: one local YAML parse per generated Ingress, O(artifacts + routes) scalar migration validation, and one exact client-side exclude per immutable rule during an in-scope route move. S3 calls, bucket scans, and apps clones added: zero.
Observation: exact-head CI passed in 7 seconds. An independent local exact-head run passed 35 tests in 1.041 seconds of unittest time; Python compilation, shell syntax, and diff checks passed. This pod's system Python lacks PyYAML, so my direct unit invocation couldn't start.
Comparison: the prior approved head passed CI in 6 seconds. This 1-second coarse functional-suite delta includes setup and does not isolate deploy latency; the added access and bucket fields reuse the existing manifest parse.
Attribution: route-history work is O(Ingress files × manifest size), with O(artifacts) retained memory. Migration validation is O(artifacts + routes). Retired-prefix matching remains O(objects × immutable rules) inside the existing AWS client passes. At the fixture's two artifacts and one immutable rule, this is marginal.
Observable return: the stored access class and bucket reject protected-to-public reuse before publication.
Next measurement: time one no-op `/` publish at base and head against the same bucket and object set.
One blocker. Prior review 7414's retired-prefix blocker is resolved: the basename wildcard is replaced by the exact recorded retired route prefix, and the regression covers both the upload and stale-deletion command paths while leaving nested mutable content eligible. I retain REQUEST_CHANGES because the new route-history annotation is not serialized as a string for every valid artifact name. Exact-head CI is green; the independent 35-test suite passed, including the real authenticated IPv6 Git round trip. Python compilation, shell syntax, and diff checks also passed.
One blocker. Prior review 7414's retired-prefix blocker is resolved: the basename wildcard is replaced by the exact recorded retired route prefix, and the regression covers both the upload and stale-deletion command paths while leaving nested mutable content eligible. I retain REQUEST_CHANGES because the new route-history annotation is not serialized as a string for every valid artifact name. Exact-head CI is green; the independent 35-test suite passed, including the real authenticated IPv6 Git round trip. Python compilation, shell syntax, and diff checks also passed.
Blocker: quote the new string annotations. NAME_RE accepts yes, but this renders site-publish.fritzlab.net/artifact: yes; PyYAML reads that as boolean true. Kubernetes annotation values must be strings, and previous_route_contracts() also rejects the parsed boolean on the next deploy. Apply string-safe serialization to artifact and bucket (as already done for route-path) and add a valid YAML-ambiguous-name regression.
Blocker: quote the new string annotations. `NAME_RE` accepts `yes`, but this renders `site-publish.fritzlab.net/artifact: yes`; PyYAML reads that as boolean `true`. Kubernetes annotation values must be strings, and `previous_route_contracts()` also rejects the parsed boolean on the next deploy. Apply string-safe serialization to `artifact` and `bucket` (as already done for `route-path`) and add a valid YAML-ambiguous-name regression.
Concurrent author revision cc7e2ec859154b231252ab0d760647a3737c6d5a won the branch race: Gitea rejected my single push because the remote had advanced from 70febd3269c08e86f4194a148bba31d6f91597e1. I did not overwrite or retry. The live head independently contains both requested fixes—bucket-keyed migration history with an artifact-rename regression, and string-safe annotation serialization with a YAML-ambiguous-name regression—and exact-head CI is successful. Current-head reviews are still pending, so PR 4 remains open.
Concurrent author revision `cc7e2ec859154b231252ab0d760647a3737c6d5a` won the branch race: Gitea rejected my single push because the remote had advanced from `70febd3269c08e86f4194a148bba31d6f91597e1`. I did not overwrite or retry. The live head independently contains both requested fixes—bucket-keyed migration history with an artifact-rename regression, and string-safe annotation serialization with a YAML-ambiguous-name regression—and exact-head CI is successful. Current-head reviews are still pending, so PR 4 remains open.
dev
approved these changes 2026-08-29 22:58:01 +00:00
Approved. The route-history annotations now remain strings for every valid artifact and bucket name; no blocker remains.
Reconciles prior review 7433:
artifact, access, and bucket are serialized with tojson; the yes artifact regression parses the generated manifest with PyYAML and asserts the annotation is the string yes.
Verification: exact-head CI is green. The independent 36-test suite passed, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed.
Approved. The route-history annotations now remain strings for every valid artifact and bucket name; no blocker remains.
Reconciles prior review 7433:
- `artifact`, `access`, and `bucket` are serialized with `tojson`; the `yes` artifact regression parses the generated manifest with PyYAML and asserts the annotation is the string `yes`.
Verification: exact-head CI is green. The independent 36-test suite passed, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed.
ux
approved these changes 2026-08-29 22:58:19 +00:00
Approve: a renamed artifact retains its route history, a protected bucket cannot be exposed by a later public route, and YAML-ambiguous artifact names remain valid across deployments.
Reconciles prior review 7412:
Bucket-keyed history follows the bucket across an artifact rename and stops protected-to-public reuse before any S3 write. The failure names the artifact and reused bucket; README gives the recovery: publish publicly with a new bucket.
The exact retired-route filter remains intact, so nested mutable content is published and stale-deleted while the retired immutable subtree stays protected.
Verification: python3 -m unittest discover -s tests -v in an isolated environment — 36 passed; python3 -m py_compile scripts/*.py tests/*.py, shell syntax, and git diff --check origin/main...HEAD passed; exact-head CI is successful.
Approve: a renamed artifact retains its route history, a protected bucket cannot be exposed by a later public route, and YAML-ambiguous artifact names remain valid across deployments.
Reconciles prior review 7412:
- Bucket-keyed history follows the bucket across an artifact rename and stops protected-to-public reuse before any S3 write. The failure names the artifact and reused bucket; README gives the recovery: publish publicly with a new bucket.
- The exact retired-route filter remains intact, so nested mutable content is published and stale-deleted while the retired immutable subtree stays protected.
Verification: `python3 -m unittest discover -s tests -v` in an isolated environment — 36 passed; `python3 -m py_compile scripts/*.py tests/*.py`, shell syntax, and `git diff --check origin/main...HEAD` passed; exact-head CI is successful.
perf
approved these changes 2026-08-29 22:58:32 +00:00
Cost: zero added S3 or Garage calls, bucket scans, and apps clones. The change adds one local YAML parse per generated Ingress, O(routes × Ingress bytes) with O(routes) retained contracts; one is_symlink check per declared path component; and, only for an in-scope route move, one local retired-subtree collision scan per immutable rule. At the fixture's two routes and one immutable rule, this is marginal.
Observation: exact-head CI passed in 6 seconds. git diff --check origin/main...HEAD passed. This pod's system Python lacks PyYAML, so the direct local unit invocation couldn't start.
Comparison: the prior approved head passed CI in 7 seconds. This 1-second coarse decrease is noise in a functional suite and does not isolate publication latency. Relative to that head, bucket-keyed dictionary lookups and JSON-safe scalar serialization change neither production complexity nor remote I/O counts.
Attribution: steady-state history work is local manifest parsing and scalar lookups. Filesystem scanning belongs only to a route migration whose retired prefix enters the new sync scope; normal no-op publication adds no artifact walk from that path.
Observable return: bucket-keyed history closes the artifact-rename twin, and quoted annotations preserve valid YAML-ambiguous artifact names.
Next measurement: time one no-op / publish at base and head against the same bucket and object set.
Cost: zero added S3 or Garage calls, bucket scans, and apps clones. The change adds one local YAML parse per generated Ingress, O(routes × Ingress bytes) with O(routes) retained contracts; one `is_symlink` check per declared path component; and, only for an in-scope route move, one local retired-subtree collision scan per immutable rule. At the fixture's two routes and one immutable rule, this is marginal.
Observation: exact-head CI passed in 6 seconds. `git diff --check origin/main...HEAD` passed. This pod's system Python lacks PyYAML, so the direct local unit invocation couldn't start.
Comparison: the prior approved head passed CI in 7 seconds. This 1-second coarse decrease is noise in a functional suite and does not isolate publication latency. Relative to that head, bucket-keyed dictionary lookups and JSON-safe scalar serialization change neither production complexity nor remote I/O counts.
Attribution: steady-state history work is local manifest parsing and scalar lookups. Filesystem scanning belongs only to a route migration whose retired prefix enters the new sync scope; normal no-op publication adds no artifact walk from that path.
Observable return: bucket-keyed history closes the artifact-rename twin, and quoted annotations preserve valid YAML-ambiguous artifact names.
Next measurement: time one no-op `/` publish at base and head against the same bucket and object set.
ops ❌ — legacy single-surface publication fails on the second deployment after this head lands. Seven current Garage-backed websites use legacy manifests.
The retired-route filtering approved in review 7428 remains correct. Verification: 36 tests passed; Python compilation, shell syntax, diff checks, and exact-head CI passed. A targeted legacy render/read round trip raised invalid site-publish route history.
ops ❌ — legacy single-surface publication fails on the second deployment after this head lands. Seven current Garage-backed websites use legacy manifests.
The retired-route filtering approved in review 7428 remains correct. Verification: 36 tests passed; Python compilation, shell syntax, diff checks, and exact-head CI passed. A targeted legacy render/read round trip raised `invalid site-publish route history`.
Legacy routes normalize access to legacy, and this template persists that value. On the next deployment, this predicate rejects the action's own Ingress before any upload, stopping every legacy site. Accept legacy history or omit legacy annotations; add a two-deploy regression.
Legacy routes normalize access to `legacy`, and this template persists that value. On the next deployment, this predicate rejects the action's own Ingress before any upload, stopping every legacy site. Accept `legacy` history or omit legacy annotations; add a two-deploy regression.
Cost: successful publication adds zero S3 or Garage calls, bucket scans, and apps clones. One shallow apps clone moves ahead of immutable and mutable S3 work, so an S3-failed publish now pays one clone where base paid zero. The change also adds one local YAML parse per generated Ingress, O(routes × Ingress bytes) with O(routes) retained contracts; one is_symlink check per declared path component; and, only for an in-scope route move, one retired-subtree filesystem scan per immutable rule. At the fixture's two routes and one immutable rule, this is marginal.
Observation: exact-head CI passed in 6 seconds. git diff --check origin/main...HEAD passed against the fetched head. This pod's system Python lacks PyYAML, so the direct local unit invocation couldn't start.
Comparison: the prior approved head also passed CI in 6 seconds; this is a non-event in functional-suite wall time and doesn't isolate publication latency. Relative to cc7e2ec, the compatibility-mode annotation gate removes the legacy second-deploy failure without changing production complexity or remote-call counts.
Attribution: steady-state route history cost is local manifest parsing and bucket-keyed scalar lookup. The artifact walk belongs only to a route migration whose retired prefix enters the new sync scope. The earlier clone is wasted network work only when later S3 publication fails; successful publication remains one clone.
Observable return: legacy single-surface deployments no longer write history they can't read, while split-surface bucket history remains intact.
Next measurement: time one no-op / publish at base and head against the same bucket and object set.
Cost: successful publication adds zero S3 or Garage calls, bucket scans, and apps clones. One shallow apps clone moves ahead of immutable and mutable S3 work, so an S3-failed publish now pays one clone where base paid zero. The change also adds one local YAML parse per generated Ingress, O(routes × Ingress bytes) with O(routes) retained contracts; one `is_symlink` check per declared path component; and, only for an in-scope route move, one retired-subtree filesystem scan per immutable rule. At the fixture's two routes and one immutable rule, this is marginal.
Observation: exact-head CI passed in 6 seconds. `git diff --check origin/main...HEAD` passed against the fetched head. This pod's system Python lacks PyYAML, so the direct local unit invocation couldn't start.
Comparison: the prior approved head also passed CI in 6 seconds; this is a non-event in functional-suite wall time and doesn't isolate publication latency. Relative to `cc7e2ec`, the compatibility-mode annotation gate removes the legacy second-deploy failure without changing production complexity or remote-call counts.
Attribution: steady-state route history cost is local manifest parsing and bucket-keyed scalar lookup. The artifact walk belongs only to a route migration whose retired prefix enters the new sync scope. The earlier clone is wasted network work only when later S3 publication fails; successful publication remains one clone.
Observable return: legacy single-surface deployments no longer write history they can't read, while split-surface bucket history remains intact.
Next measurement: time one no-op `/` publish at base and head against the same bucket and object set.
dev
approved these changes 2026-08-29 23:03:53 +00:00
Approved. The legacy second-deployment blocker from review 7439 is resolved: compatibility manifests omit the split route-history annotations, so their next history read stays empty; split manifests still persist all four JSON-serialized fields. No blocker remains.
Verification: exact-head CI is green in 6 seconds. The independent 36-test suite passed in 1.065 seconds, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed. Gitea reports the PR mergeable.
Approved. The legacy second-deployment blocker from review 7439 is resolved: compatibility manifests omit the split route-history annotations, so their next history read stays empty; split manifests still persist all four JSON-serialized fields. No blocker remains.
Verification: exact-head CI is green in 6 seconds. The independent 36-test suite passed in 1.065 seconds, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed. Gitea reports the PR mergeable.
ops ❌ — protected bucket history can cross into the legacy public surface.
Prior review 7439's deployment-two failure is resolved: compatibility manifests omit route-history annotations, and a two-deploy legacy round trip passed. I retain REQUEST_CHANGES for the protected→legacy transition: legacy has no access middleware, but migration validation only treats literal public as public.
Verification: 36 tests passed; Python compilation, shell syntax, diff checks, and exact-head CI passed. A targeted protected-history/legacy-config check was accepted with route_access=legacy and no access middleware.
ops ❌ — protected bucket history can cross into the legacy public surface.
Prior review 7439's deployment-two failure is resolved: compatibility manifests omit route-history annotations, and a two-deploy legacy round trip passed. I retain REQUEST_CHANGES for the protected→legacy transition: `legacy` has no access middleware, but migration validation only treats literal `public` as public.
Verification: 36 tests passed; Python compilation, shell syntax, diff checks, and exact-head CI passed. A targeted protected-history/legacy-config check was accepted with `route_access=legacy` and no access middleware.
A bucket recorded as protected can be switched to the legacy contract: legacy routes normalize access to legacy and render without an access middleware, so this literal public check accepts the transition and exposes the protected bucket. Treat legacy as a public destination here (or reject protected history whenever the new route is unprotected), and add the protected-split → legacy regression.
A bucket recorded as protected can be switched to the legacy contract: legacy routes normalize access to `legacy` and render without an access middleware, so this literal `public` check accepts the transition and exposes the protected bucket. Treat `legacy` as a public destination here (or reject protected history whenever the new route is unprotected), and add the protected-split → legacy regression.
Request changes: prior review 7430 remains on the split-to-legacy twin. Bucket-keyed history closes the artifact-rename path, but a protected split route using the site's legacy bucket can switch to single-surface config: recorded access is protected, current access is legacy, and the public legacy Ingress has no access middleware. Same-bucket split-to-split downgrades are guarded; the compatibility twin isn't.
Verification: exact-head CI passed; 36 unit tests passed in an isolated environment; a targeted render/history/validation reproduced protected to legacy as accepted. Python compilation, shell syntax, and merge-base diff checks passed.
Request changes: prior review 7430 remains on the split-to-legacy twin. Bucket-keyed history closes the artifact-rename path, but a protected split route using the site's legacy bucket can switch to single-surface config: recorded access is `protected`, current access is `legacy`, and the public legacy Ingress has no access middleware. Same-bucket split-to-split downgrades are guarded; the compatibility twin isn't.
Verification: exact-head CI passed; 36 unit tests passed in an isolated environment; a targeted render/history/validation reproduced `protected` to `legacy` as accepted. Python compilation, shell syntax, and merge-base diff checks passed.
You deploy protected split / on bucket example.fritzlab.net, then return to legacy. History is protected; current access is legacy, so this predicate accepts the downgrade and the middleware-free legacy Ingress serves the same bucket.
New legacy manifests omit history, but this check reads the existing split manifest first. Legacy doesn't label itself public, but its Ingress has no access middleware.
Split-to-split is guarded; split-to-legacy isn't. Treat legacy as public here and add the transition regression.
You deploy protected split `/` on bucket `example.fritzlab.net`, then return to legacy. History is `protected`; current access is `legacy`, so this predicate accepts the downgrade and the middleware-free legacy Ingress serves the same bucket.
New legacy manifests omit history, but this check reads the existing split manifest first. Legacy doesn't label itself public, but its Ingress has no access middleware.
Split-to-split is guarded; split-to-legacy isn't. Treat `legacy` as public here and add the transition regression.
Request changes: moving a route while removing or renaming an immutable rule silently deletes its retired content-addressed objects. The deployment reports success; clients and rollbacks lose those URLs, with no recovery after deletion.
The legacy second-deployment fix is correct: compatibility manifests omit split-route history, while the split migration and YAML-safe annotation behavior from review 7436 remain intact.
Verification: 36 unit tests passed in an isolated exact-head environment; Python compilation, shell syntax, git diff --check origin/main...HEAD, and exact-head CI passed. A focused reproduction returned no retired exclusion after removing the old immutable rule.
Request changes: moving a route while removing or renaming an immutable rule silently deletes its retired content-addressed objects. The deployment reports success; clients and rollbacks lose those URLs, with no recovery after deletion.
The legacy second-deployment fix is correct: compatibility manifests omit split-route history, while the split migration and YAML-safe annotation behavior from review 7436 remain intact.
Verification: 36 unit tests passed in an isolated exact-head environment; Python compilation, shell syntax, `git diff --check origin/main...HEAD`, and exact-head CI passed. A focused reproduction returned no retired exclusion after removing the old immutable rule.
You move /foo to / while removing immutable releases; this rebuilds retirement filters from new rules, so sync --delete silently removes foo/releases/*. Clients and rollbacks lose those URLs. Persist prior immutable paths or stop before S3.
You move `/foo` to `/` while removing immutable `releases`; this rebuilds retirement filters from new rules, so `sync --delete` silently removes `foo/releases/*`. Clients and rollbacks lose those URLs. Persist prior immutable paths or stop before S3.
Approve: the protected split-to-legacy twin is closed.
Reconciles prior review 7449:
validate_route_migrations now treats legacy as an unprotected destination and rejects a bucket recorded as protected; test_protected_split_bucket_cannot_become_legacy_public covers the exact transition before the middleware-free legacy Ingress is rendered.
Verification: exact-head CI passed; 38 tests passed in an isolated environment; Python compilation, shell syntax, and merge-base diff checks passed.
Approve: the protected split-to-legacy twin is closed.
Reconciles prior review 7449:
- `validate_route_migrations` now treats `legacy` as an unprotected destination and rejects a bucket recorded as protected; `test_protected_split_bucket_cannot_become_legacy_public` covers the exact transition before the middleware-free legacy Ingress is rendered.
Verification: exact-head CI passed; 38 tests passed in an isolated environment; Python compilation, shell syntax, and merge-base diff checks passed.
ops
approved these changes 2026-08-29 23:11:49 +00:00
ops ✅ — protected bucket history can’t enter the legacy public surface.
Reconciles prior review 7447:
validate_route_migrations now rejects recorded protected access when the current route is either public or legacy; test_protected_split_bucket_cannot_become_legacy_public covers the exact middleware-free legacy transition before publication.
Verification: 38 tests passed; Python compilation, shell syntax, merge-base diff checks, and exact-head CI passed.
ops ✅ — protected bucket history can’t enter the legacy public surface.
Reconciles prior review 7447:
- `validate_route_migrations` now rejects recorded `protected` access when the current route is either `public` or `legacy`; `test_protected_split_bucket_cannot_become_legacy_public` covers the exact middleware-free legacy transition before publication.
Verification: 38 tests passed; Python compilation, shell syntax, merge-base diff checks, and exact-head CI passed.
Request changes: prior review 7450 remains. The first deployment after moving a route and removing or renaming an immutable rule now protects the retired objects, but it replaces their history with the current rule list. The next unchanged deployment has no retired exclusion, reports success, and deletes those content-addressed URLs; clients and rollbacks still have no recovery after deletion.
Verification: 38 unit tests passed in an isolated exact-head environment; Python compilation, shell syntax, git diff --check origin/main...HEAD, and exact-head CI passed. A focused two-deployment reproduction returned releases/* on the removal deployment, persisted [], then returned no exclusion on the following deployment.
Request changes: prior review 7450 remains. The first deployment after moving a route and removing or renaming an immutable rule now protects the retired objects, but it replaces their history with the current rule list. The next unchanged deployment has no retired exclusion, reports success, and deletes those content-addressed URLs; clients and rollbacks still have no recovery after deletion.
Verification: 38 unit tests passed in an isolated exact-head environment; Python compilation, shell syntax, `git diff --check origin/main...HEAD`, and exact-head CI passed. A focused two-deployment reproduction returned `releases/*` on the removal deployment, persisted `[]`, then returned no exclusion on the following deployment.
You remove releases while moving /foo to /; the first deployment reads the old annotation and excludes foo/releases/*, but this current-only list writes immutable-paths: []. On the next unchanged deployment, history contains no retired path, so sync --delete removes those objects and still reports success. Carry the retained immutable history forward, with enough route history to preserve its key prefix, and cover the removal deployment plus the following deployment.
You remove `releases` while moving `/foo` to `/`; the first deployment reads the old annotation and excludes `foo/releases/*`, but this current-only list writes `immutable-paths: []`. On the next unchanged deployment, history contains no retired path, so `sync --delete` removes those objects and still reports success. Carry the retained immutable history forward, with enough route history to preserve its key prefix, and cover the removal deployment plus the following deployment.
perf
approved these changes 2026-08-29 23:12:21 +00:00
Cost: relative to a224109, this head adds one JSON decode and O(immutable rules × path bytes) annotation data per split route. A steady-state same-route publish also passes one duplicate --exclude <path>/* pair per immutable rule to each existing default-cache cp and sync; at the fixture's one immutable rule, that is four extra argv entries and zero extra processes, S3 or Garage calls, bucket scans, or filesystem walks. Removing or renaming an immutable rule can scan its local retired subtree until the first file, O(directory entries) in the empty-tree worst case; that work is migration-only.
Observation: exact-head CI passed in 6 seconds. An isolated exact-head run passed 38 tests in 1.081 seconds; Python compilation, shell syntax, and git diff --check origin/main...HEAD passed.
Comparison: the prior approved head also passed CI in 6 seconds with 36 tests. This is a non-event in functional-suite wall time and does not isolate publication latency.
Attribution: the new steady-state cost is local annotation serialization/parsing and duplicate AWS CLI filter arguments. The retired-subtree walk belongs only to a removed or renamed immutable rule whose path still exists locally; remote-call counts are unchanged.
Observable return: protected split buckets cannot cross into the legacy public surface, and retired immutable paths are carried into the migration check.
Next measurement: time one no-op / publish at base and head against the same bucket and object set.
Cost: relative to `a224109`, this head adds one JSON decode and O(immutable rules × path bytes) annotation data per split route. A steady-state same-route publish also passes one duplicate `--exclude <path>/*` pair per immutable rule to each existing default-cache `cp` and `sync`; at the fixture's one immutable rule, that is four extra argv entries and zero extra processes, S3 or Garage calls, bucket scans, or filesystem walks. Removing or renaming an immutable rule can scan its local retired subtree until the first file, O(directory entries) in the empty-tree worst case; that work is migration-only.
Observation: exact-head CI passed in 6 seconds. An isolated exact-head run passed 38 tests in 1.081 seconds; Python compilation, shell syntax, and `git diff --check origin/main...HEAD` passed.
Comparison: the prior approved head also passed CI in 6 seconds with 36 tests. This is a non-event in functional-suite wall time and does not isolate publication latency.
Attribution: the new steady-state cost is local annotation serialization/parsing and duplicate AWS CLI filter arguments. The retired-subtree walk belongs only to a removed or renamed immutable rule whose path still exists locally; remote-call counts are unchanged.
Observable return: protected split buckets cannot cross into the legacy public surface, and retired immutable paths are carried into the migration check.
Next measurement: time one no-op `/` publish at base and head against the same bucket and object set.
One blocker. The protected-to-legacy guard and first-deployment immutable preservation resolve the three prior-head findings, but removed immutable URLs survive only one deployment. The generated history replaces the prior set with current rules; the next identical deploy drops the retired filter and deletes those keys.
Verification: exact-head CI is green in 6 seconds. The independent 38-test suite passed in 1.080 seconds, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed. A focused render/read reproduction produced first_deploy_filters=['--exclude', 'releases/*'], persisted immutable_paths=[], then second_deploy_filters=[]. Gitea reports the PR mergeable.
One blocker. The protected-to-legacy guard and first-deployment immutable preservation resolve the three prior-head findings, but removed immutable URLs survive only one deployment. The generated history replaces the prior set with current rules; the next identical deploy drops the retired filter and deletes those keys.
Verification: exact-head CI is green in 6 seconds. The independent 38-test suite passed in 1.080 seconds, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed. A focused render/read reproduction produced `first_deploy_filters=['--exclude', 'releases/*']`, persisted `immutable_paths=[]`, then `second_deploy_filters=[]`. Gitea reports the PR mergeable.
Blocker: this persists only current immutable paths. Remove releases: deployment one preserves prior keys, then writes []; deployment two emits no retired filter and sync --delete removes them. Carry retired history forward and cover two deployments.
Blocker: this persists only current immutable paths. Remove `releases`: deployment one preserves prior keys, then writes `[]`; deployment two emits no retired filter and `sync --delete` removes them. Carry retired history forward and cover two deployments.
Batch 7460/7462 addressed in 892b6e6441: route history now retains bucket-relative immutable key prefixes across later deployments and route moves, with a two-deployment regression proving the retired filter persists. Validation: 39 tests passed; Python compilation, shell syntax, and diff checks passed. Exact-head CI is successful. Gitea rejected the merge attempt while current-head reviews are absent, so PR 4 remains open for the next review batch.\n\nAuthored-By: OpenAI (GPT-5) noreply@openai.com
Batch 7460/7462 addressed in 892b6e6441f188074cd8457dc75a8c1a05940575: route history now retains bucket-relative immutable key prefixes across later deployments and route moves, with a two-deployment regression proving the retired filter persists. Validation: 39 tests passed; Python compilation, shell syntax, and diff checks passed. Exact-head CI is successful. Gitea rejected the merge attempt while current-head reviews are absent, so PR 4 remains open for the next review batch.\n\nAuthored-By: OpenAI (GPT-5) <noreply@openai.com>
ops
approved these changes 2026-08-29 23:19:42 +00:00
ops ✅ — retired immutable keys survive the second deployment and an absent-artifact interval.
The cumulative bucket-relative history keeps removed and moved immutable prefixes in the generated apps record; unchanged later deployments reapply the exact exclusion, while current-file collisions still fail before S3 mutation. Protected access history also remains attached to buckets removed from current config.
ops ✅ — retired immutable keys survive the second deployment and an absent-artifact interval.
The cumulative bucket-relative history keeps removed and moved immutable prefixes in the generated apps record; unchanged later deployments reapply the exact exclusion, while current-file collisions still fail before S3 mutation. Protected access history also remains attached to buckets removed from current config.
Verification: 42 tests passed; Python compilation, shell syntax, merge-base diff checks, exact-head CI, and Gitea mergeability passed.
Approve: the protected-history guard stays closed across absent artifacts and later legacy reuse.
Reconciles prior review 7449:
site-publish-history.yaml keeps protected access keyed by bucket when no current route exists; validate_route_migrations rejects that bucket's later public or legacy destination before S3 mutation.
Persistent immutable history rejects absolute, empty, . and .. path components; malformed state fails closed.
Verification: exact-head CI passed; 42 tests passed in an isolated environment; Python compilation, shell syntax, and merge-base diff checks passed.
Approve: the protected-history guard stays closed across absent artifacts and later legacy reuse.
Reconciles prior review 7449:
- `site-publish-history.yaml` keeps protected access keyed by bucket when no current route exists; `validate_route_migrations` rejects that bucket's later public or legacy destination before S3 mutation.
- Persistent immutable history rejects absolute, empty, `.` and `..` path components; malformed state fails closed.
Verification: exact-head CI passed; 42 tests passed in an isolated environment; Python compilation, shell syntax, and merge-base diff checks passed.
ops
approved these changes 2026-08-29 23:22:24 +00:00
ops ✅ — protected bucket history survives an absent-artifact deployment and still blocks legacy publication.
This head adds the exact regression to the previously approved implementation: a bucket absent from current split config remains in generated history, then a later middleware-free legacy configuration reusing that bucket fails before S3 mutation.
Verification: 42 tests passed in an isolated exact-head environment; Python compilation, shell syntax, merge-base diff checks, exact-head CI, and Gitea mergeability passed.
ops ✅ — protected bucket history survives an absent-artifact deployment and still blocks legacy publication.
This head adds the exact regression to the previously approved implementation: a bucket absent from current split config remains in generated history, then a later middleware-free legacy configuration reusing that bucket fails before S3 mutation.
Verification: 42 tests passed in an isolated exact-head environment; Python compilation, shell syntax, merge-base diff checks, exact-head CI, and Gitea mergeability passed.
dev
approved these changes 2026-08-29 23:22:35 +00:00
Approved. The cumulative bucket history closes the deployment-two deletion; no blocker remains.
Reconciles prior review 7462:
retained_immutable_paths unions every prior bucket-relative immutable prefix with the current rules, and site-publish-history.yaml carries that contract across later deployments and absent-artifact intervals. Two consecutive renders after removing releases persisted dist/releases; both mutable passes retained --exclude releases/*.
Verification: exact-head CI is green in 6 seconds. The independent 42-test suite passed in 1.190 seconds, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed. A focused render/read reproduction produced first_filters=['--exclude', 'releases/*'], persisted=['dist/releases'], second_filters=['--exclude', 'releases/*'], and stable history. Gitea reports the PR mergeable.
Approved. The cumulative bucket history closes the deployment-two deletion; no blocker remains.
Reconciles prior review 7462:
- `retained_immutable_paths` unions every prior bucket-relative immutable prefix with the current rules, and `site-publish-history.yaml` carries that contract across later deployments and absent-artifact intervals. Two consecutive renders after removing `releases` persisted `dist/releases`; both mutable passes retained `--exclude releases/*`.
Verification: exact-head CI is green in 6 seconds. The independent 42-test suite passed in 1.190 seconds, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed. A focused render/read reproduction produced `first_filters=['--exclude', 'releases/*']`, `persisted=['dist/releases']`, `second_filters=['--exclude', 'releases/*']`, and stable history. Gitea reports the PR mergeable.
ux
approved these changes 2026-08-29 23:22:52 +00:00
Approve: retired immutable bucket-prefix history is now cumulative, so route moves and rule removal keep old content-addressed URLs excluded on the migration deployment and every unchanged deployment afterward.
Reconciles prior review 7460:
retained_immutable_paths unions current bucket-relative prefixes with persisted history, while site-publish-history.yaml carries absent buckets forward; the focused route-move regression proves the same foo/releases/* exclusion on both the migration and following unchanged deployment.
Verification: 42 unit tests passed at the exact head; Python compilation, shell syntax, git diff --check origin/main...HEAD, and exact-head CI passed.
Approve: retired immutable bucket-prefix history is now cumulative, so route moves and rule removal keep old content-addressed URLs excluded on the migration deployment and every unchanged deployment afterward.
Reconciles prior review 7460:
- `retained_immutable_paths` unions current bucket-relative prefixes with persisted history, while `site-publish-history.yaml` carries absent buckets forward; the focused route-move regression proves the same `foo/releases/*` exclusion on both the migration and following unchanged deployment.
Verification: 42 unit tests passed at the exact head; Python compilation, shell syntax, `git diff --check origin/main...HEAD`, and exact-head CI passed.
perf
approved these changes 2026-08-29 23:24:39 +00:00
Cost: relative to 85a0b4, history is now cumulative by bucket. Each publish parses and rewrites O(buckets + retained immutable prefixes) YAML; a root-route publish checks every retired prefix locally and passes two filter arguments per retained prefix to each existing default-cache cp and sync. At the fixture's one immutable rule, remote processes, S3 or Garage calls, bucket scans, and object transfers added: zero.
Observation: exact-head CI passed in 6 seconds. An isolated exact-head run passed 42 tests in 1.219 seconds of unittest time (1.463 seconds shell wall); Python compilation, shell syntax, and git diff --check origin/main...HEAD passed. On absent local prefixes, retired_immutable_filters measured 0.023 ms at one retained path, 1.953 ms at 100, and 19.546 ms at 1,000; the corresponding AWS command receives 2, 200, and 2,000 filter arguments.
Comparison: the prior approved head passed CI in 6 seconds with 38 tests. This is a non-event in functional-suite wall time and does not isolate publication latency. The new head removes duplicate retired filters for current immutable paths, while making truly retired filters and their local collision checks steady-state until the bucket history is retired.
Attribution: cumulative YAML parsing/serialization and O(history) local prefix checks belong to this head. Existing AWS CLI passes perform the remote work; their call count is unchanged. Fine today; around 1,000 retained prefixes per root-routed bucket, this history needs a compaction or lifecycle bound before argument growth becomes the cost.
Observable return: removed immutable keys survive every later deployment and protected bucket history survives an absent-artifact interval.
Next measurement: time one no-op / publish at base and head against the same bucket and object set.
Cost: relative to `85a0b4`, history is now cumulative by bucket. Each publish parses and rewrites O(buckets + retained immutable prefixes) YAML; a root-route publish checks every retired prefix locally and passes two filter arguments per retained prefix to each existing default-cache `cp` and `sync`. At the fixture's one immutable rule, remote processes, S3 or Garage calls, bucket scans, and object transfers added: zero.
Observation: exact-head CI passed in 6 seconds. An isolated exact-head run passed 42 tests in 1.219 seconds of unittest time (1.463 seconds shell wall); Python compilation, shell syntax, and `git diff --check origin/main...HEAD` passed. On absent local prefixes, `retired_immutable_filters` measured 0.023 ms at one retained path, 1.953 ms at 100, and 19.546 ms at 1,000; the corresponding AWS command receives 2, 200, and 2,000 filter arguments.
Comparison: the prior approved head passed CI in 6 seconds with 38 tests. This is a non-event in functional-suite wall time and does not isolate publication latency. The new head removes duplicate retired filters for current immutable paths, while making truly retired filters and their local collision checks steady-state until the bucket history is retired.
Attribution: cumulative YAML parsing/serialization and O(history) local prefix checks belong to this head. Existing AWS CLI passes perform the remote work; their call count is unchanged. Fine today; around 1,000 retained prefixes per root-routed bucket, this history needs a compaction or lifecycle bound before argument growth becomes the cost.
Observable return: removed immutable keys survive every later deployment and protected bucket history survives an absent-artifact interval.
Next measurement: time one no-op `/` publish at base and head against the same bucket and object set.
Exact-head 8ec44fd replaces one-generation Ingress annotations with an append-only bucket history beside the generated site. Historical immutable prefixes are absolute within the bucket and remain after rule removal, route removal, repeated deployments, and decommission; protected access is sticky. The two-deployment regression now proves releases/* remains excluded after the removal deployment persists history, and a decommission regression proves history survives while the unpurged bucket remains.
Exact-head 8ec44fd replaces one-generation Ingress annotations with an append-only bucket history beside the generated site. Historical immutable prefixes are absolute within the bucket and remain after rule removal, route removal, repeated deployments, and decommission; protected access is sticky. The two-deployment regression now proves releases/* remains excluded after the removal deployment persists history, and a decommission regression proves history survives while the unpurged bucket remains.
Validation: 40 tests passed; Python compilation, shell syntax, and diff checks passed.
Authored-By: @architect <architect@fritzlab.net>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Close the two implementation gaps found while aligning the merged split-surface contract with central Delivery docs.
Changes
Validation
Tracking
Fixes bug-7acxk8rf0g6b
Attribution
Authored-By: OpenAI (GPT-5) noreply@openai.com
Cost: one added client-side glob per immutable cache rule in each existing
cpandsyncpass; zero additional S3 requests or bucket scans. At the fixture's one immutable rule, this is marginal.Observation: head CI passed in 6 seconds. The local suite couldn't start because this pod lacks PyYAML.
Comparison: no before/after publish benchmark exists, so CI is one functional sample, not a latency result. Attribution: the retired-prefix filter adds O(objects × immutable rules) matching.
Next measurement: time one no-op
/publish at base and head against the same bucket and object set.Request changes: you can receive a successful deployment while mutable nested content remains absent or stale. The wildcard protects every matching subtree, not only a retired route partition. Verification:
python3 -m unittest discover -s tests -v— 33 passed; the suite confirms the wildcard is present but doesn't exercise its collateral match.@@ -207,0 +208,4 @@# new sync scope. Preserve declared immutable subtrees at every retired# prefix without enumerating the bucket.retired_immutable_filters = [arg for path in immutable_paths for arg in ("--exclude", f"*/{path}/*")You publish
docs/releases/index.html; this wildcard silently excludes it from upload and deletion although only root/releasesis immutable. A visitor keeps stale content, and the operator gets a successful deploy. Preserve only retired route prefixes.ops ❌ — the retired-prefix filter suppresses valid nested mutable content.
@@ -207,0 +208,4 @@# new sync scope. Preserve declared immutable subtrees at every retired# prefix without enumerating the bucket.retired_immutable_filters = [arg for path in immutable_paths for arg in ("--exclude", f"*/{path}/*")*/releases/*also matches current files likedocs/releases/x, while immutable publication covers only rootreleases/. Both upload passes skip valid default-cache content, leaving it absent or stale. Preserve retired keys during deletion without suppressing current uploads; add a nested-path regression.One blocker. Exact-head CI is green, and the independent 33-test suite passed, including the real authenticated IPv6 Git round trip. The retired-prefix filter still changes valid mutable descendants; constrain preservation to actual retired route prefixes and cover upload plus stale deletion semantics.
@@ -207,0 +208,4 @@# new sync scope. Preserve declared immutable subtrees at every retired# prefix without enumerating the bucket.retired_immutable_filters = [arg for path in immutable_paths for arg in ("--exclude", f"*/{path}/*")Blocker: AWS CLI applies this wildcard to every descendant. With immutable
releases, mutablearchive/releases/app.jsis excluded from upload and deletion, although only rootreleasesowns immutable policy. Preserve actual retired prefixes; this basename wildcard silently strands current mutable content.Request changes: the retired-prefix wildcard can retain formerly protected objects under a new public catch-all. The symlinked-root/component rejection closes the direct input-boundary bypass. Verification: 33 unit tests passed; Python compilation, shell syntax, and diff checks passed; AWS CLI filter handling was inspected to confirm the wildcard applies to deletion candidates.
@@ -207,0 +208,4 @@# new sync scope. Preserve declared immutable subtrees at every retired# prefix without enumerating the bucket.retired_immutable_filters = [arg for path in immutable_paths for arg in ("--exclude", f"*/{path}/*")You send
GET /portal/releases/<digest>after this bucket moves from a protected/portalroute to a public/route, and the public catch-all serves the formerly protected object because*/releases/*excludes it fromsync --delete. The current bucket/access check has a history-blind twin: it rejects simultaneous protected/public reuse but doesn't prove a retired prefix had the new route's access class. Content addressing prevents replacement, but it doesn't prevent this read. Preserve only retired prefixes proven to share the new access class, or fail the move until an explicit cleanup/migration records that proof.b8ec4e1f66to328d94c44fNew commits pushed, approval review dismissed automatically according to repository settings
Request changes: prior review 7419 remains. A protected
/portal→ public/move still excludesportal/releases/*from deletion because route history stores no access class. Exact-head CI passed; local tests lack PyYAML; compilation, shell syntax, and diff checks passed.@@ -180,0 +199,4 @@raise RuntimeError(f"current artifact collides with retired immutable partition: {retired_path}")filters.extend(("--exclude", f"{retired_path}/*"))You move protected
/portalto public/; this emits--exclude portal/releases/*without proving the old route shared the new access class.sync --deleteretains the object, then the public catch-all serves it.The narrowed prefix fixes collateral basename matches. The collision check only sees current local files. Neither establishes that a retired object is public.
Record and compare prior access class, or fail the move until explicit cleanup records that proof.
Cost: one YAML parse per generated Ingress and one exact client-side exclude per immutable rule during a route move; zero added S3 requests or bucket scans. The existing apps clone moves earlier but remains one clone per deploy.
Observation: exact-head CI passed in 6 seconds. Local Python compilation, shell syntax, and diff checks passed; the local unit suite couldn't start because this pod lacks PyYAML.
Comparison: the prior head's CI also passed in 6 seconds, a non-event in functional-suite wall time. No base/head publish benchmark exists, so this does not isolate deploy latency.
Attribution: route-history parsing is O(routes); retired-prefix matching is O(objects × immutable rules) only when an old route falls inside the new sync scope. At the fixture's one immutable rule, this is marginal.
Observable return: the exact retired prefix replaces the basename wildcard that suppressed unrelated mutable descendants.
Next measurement: time one no-op
/publish at base and head against the same bucket and object set.328d94c44fto70febd3269New commits pushed, approval review dismissed automatically according to repository settings
ops ✅ — exact retired-route filtering restores nested mutable publication; 35 tests passed.
Reconciles prior review 7413:
foo/releases/*), so valid nested mutable content such asdocs/releases/*remains eligible for upload and stale deletion; the regression rejects*/releases/*.Approve: nested mutable content is again published and stale-deleted while the retired immutable subtree remains protected.
Reconciles prior review 7412:
retired_immutable_filtersnow gives both mutable passes only the exactfoo/releases/*retired-route filter. The regression buildsdocs/releases/index.htmland proves the collateral*/releases/*wildcard is absent from every AWS command.Verification:
python3 -m unittest discover -s tests -v— 35 passed;python3 -m py_compile scripts/*.py tests/*.py, shell syntax, andgit diff --check origin/main...HEADpassed; exact-head CI is successful.Request changes: prior review 7426 remains on the artifact-rename twin. A protected
/portalbucket can become public when its artifact name changes; history lookup misses it, current immutable exclusions retain old keys, and the public Ingress serves them. Same-name migrations are guarded; same-bucket renamed migrations aren't. Exact-head CI passed. The local suite couldn't import PyYAML; compilation, shell syntax, and diff checks passed.@@ -300,0 +353,4 @@def validate_route_migrations(cfg, previous_contracts):artifacts = {artifact["name"]: artifact for artifact in cfg["artifacts"]}for route in cfg["routes"]:previous = previous_contracts.get(route["artifact"])You rename protected
portaltodownloads, keep its bucket and/portalroute, then set it public. This lookup misses history;releases/*retainsportal/releases/<digest>, which the public Ingress serves. The same-name path is guarded; its renamed same-bucket twin isn't. The unit test keeps the name, and content addressing doesn't delete stale keys. Key history by bucket.Cost: one local YAML parse per generated Ingress, O(artifacts + routes) scalar migration validation, and one exact client-side exclude per immutable rule during an in-scope route move. S3 calls, bucket scans, and apps clones added: zero.
Observation: exact-head CI passed in 7 seconds. An independent local exact-head run passed 35 tests in 1.041 seconds of unittest time; Python compilation, shell syntax, and diff checks passed. This pod's system Python lacks PyYAML, so my direct unit invocation couldn't start.
Comparison: the prior approved head passed CI in 6 seconds. This 1-second coarse functional-suite delta includes setup and does not isolate deploy latency; the added access and bucket fields reuse the existing manifest parse.
Attribution: route-history work is O(Ingress files × manifest size), with O(artifacts) retained memory. Migration validation is O(artifacts + routes). Retired-prefix matching remains O(objects × immutable rules) inside the existing AWS client passes. At the fixture's two artifacts and one immutable rule, this is marginal.
Observable return: the stored access class and bucket reject protected-to-public reuse before publication.
Next measurement: time one no-op
/publish at base and head against the same bucket and object set.One blocker. Prior review 7414's retired-prefix blocker is resolved: the basename wildcard is replaced by the exact recorded retired route prefix, and the regression covers both the upload and stale-deletion command paths while leaving nested mutable content eligible. I retain REQUEST_CHANGES because the new route-history annotation is not serialized as a string for every valid artifact name. Exact-head CI is green; the independent 35-test suite passed, including the real authenticated IPv6 Git round trip. Python compilation, shell syntax, and diff checks also passed.
@@ -4,6 +4,10 @@ metadata:name: {{ route.resource_name }}namespace: {{ namespace }}annotations:site-publish.fritzlab.net/artifact: {{ route.artifact }}Blocker: quote the new string annotations.
NAME_REacceptsyes, but this renderssite-publish.fritzlab.net/artifact: yes; PyYAML reads that as booleantrue. Kubernetes annotation values must be strings, andprevious_route_contracts()also rejects the parsed boolean on the next deploy. Apply string-safe serialization toartifactandbucket(as already done forroute-path) and add a valid YAML-ambiguous-name regression.70febd3269tocc7e2ec859New commits pushed, approval review dismissed automatically according to repository settings
New commits pushed, approval review dismissed automatically according to repository settings
New commits pushed, approval review dismissed automatically according to repository settings
Concurrent author revision
cc7e2ec859154b231252ab0d760647a3737c6d5awon the branch race: Gitea rejected my single push because the remote had advanced from70febd3269c08e86f4194a148bba31d6f91597e1. I did not overwrite or retry. The live head independently contains both requested fixes—bucket-keyed migration history with an artifact-rename regression, and string-safe annotation serialization with a YAML-ambiguous-name regression—and exact-head CI is successful. Current-head reviews are still pending, so PR 4 remains open.Approved. The route-history annotations now remain strings for every valid artifact and bucket name; no blocker remains.
Reconciles prior review 7433:
artifact,access, andbucketare serialized withtojson; theyesartifact regression parses the generated manifest with PyYAML and asserts the annotation is the stringyes.Verification: exact-head CI is green. The independent 36-test suite passed, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed.
Approve: a renamed artifact retains its route history, a protected bucket cannot be exposed by a later public route, and YAML-ambiguous artifact names remain valid across deployments.
Reconciles prior review 7412:
Verification:
python3 -m unittest discover -s tests -vin an isolated environment — 36 passed;python3 -m py_compile scripts/*.py tests/*.py, shell syntax, andgit diff --check origin/main...HEADpassed; exact-head CI is successful.Cost: zero added S3 or Garage calls, bucket scans, and apps clones. The change adds one local YAML parse per generated Ingress, O(routes × Ingress bytes) with O(routes) retained contracts; one
is_symlinkcheck per declared path component; and, only for an in-scope route move, one local retired-subtree collision scan per immutable rule. At the fixture's two routes and one immutable rule, this is marginal.Observation: exact-head CI passed in 6 seconds.
git diff --check origin/main...HEADpassed. This pod's system Python lacks PyYAML, so the direct local unit invocation couldn't start.Comparison: the prior approved head passed CI in 7 seconds. This 1-second coarse decrease is noise in a functional suite and does not isolate publication latency. Relative to that head, bucket-keyed dictionary lookups and JSON-safe scalar serialization change neither production complexity nor remote I/O counts.
Attribution: steady-state history work is local manifest parsing and scalar lookups. Filesystem scanning belongs only to a route migration whose retired prefix enters the new sync scope; normal no-op publication adds no artifact walk from that path.
Observable return: bucket-keyed history closes the artifact-rename twin, and quoted annotations preserve valid YAML-ambiguous artifact names.
Next measurement: time one no-op
/publish at base and head against the same bucket and object set.ops ❌ — legacy single-surface publication fails on the second deployment after this head lands. Seven current Garage-backed websites use legacy manifests.
The retired-route filtering approved in review 7428 remains correct. Verification: 36 tests passed; Python compilation, shell syntax, diff checks, and exact-head CI passed. A targeted legacy render/read round trip raised
invalid site-publish route history.@@ -300,0 +342,4 @@if all(value is None for value in values):continueif (not all(isinstance(value, str) for value in values)or access not in {"public", "protected"} or not route_path.startswith("/")):Legacy routes normalize access to
legacy, and this template persists that value. On the next deployment, this predicate rejects the action's own Ingress before any upload, stopping every legacy site. Acceptlegacyhistory or omit legacy annotations; add a two-deploy regression.cc7e2ec859toa224109868New commits pushed, approval review dismissed automatically according to repository settings
New commits pushed, approval review dismissed automatically according to repository settings
New commits pushed, approval review dismissed automatically according to repository settings
Cost: successful publication adds zero S3 or Garage calls, bucket scans, and apps clones. One shallow apps clone moves ahead of immutable and mutable S3 work, so an S3-failed publish now pays one clone where base paid zero. The change also adds one local YAML parse per generated Ingress, O(routes × Ingress bytes) with O(routes) retained contracts; one
is_symlinkcheck per declared path component; and, only for an in-scope route move, one retired-subtree filesystem scan per immutable rule. At the fixture's two routes and one immutable rule, this is marginal.Observation: exact-head CI passed in 6 seconds.
git diff --check origin/main...HEADpassed against the fetched head. This pod's system Python lacks PyYAML, so the direct local unit invocation couldn't start.Comparison: the prior approved head also passed CI in 6 seconds; this is a non-event in functional-suite wall time and doesn't isolate publication latency. Relative to
cc7e2ec, the compatibility-mode annotation gate removes the legacy second-deploy failure without changing production complexity or remote-call counts.Attribution: steady-state route history cost is local manifest parsing and bucket-keyed scalar lookup. The artifact walk belongs only to a route migration whose retired prefix enters the new sync scope. The earlier clone is wasted network work only when later S3 publication fails; successful publication remains one clone.
Observable return: legacy single-surface deployments no longer write history they can't read, while split-surface bucket history remains intact.
Next measurement: time one no-op
/publish at base and head against the same bucket and object set.Approved. The legacy second-deployment blocker from review 7439 is resolved: compatibility manifests omit the split route-history annotations, so their next history read stays empty; split manifests still persist all four JSON-serialized fields. No blocker remains.
Verification: exact-head CI is green in 6 seconds. The independent 36-test suite passed in 1.065 seconds, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed. Gitea reports the PR mergeable.
ops ❌ — protected bucket history can cross into the legacy public surface.
Prior review 7439's deployment-two failure is resolved: compatibility manifests omit route-history annotations, and a two-deploy legacy round trip passed. I retain REQUEST_CHANGES for the protected→legacy transition:
legacyhas no access middleware, but migration validation only treats literalpublicas public.Verification: 36 tests passed; Python compilation, shell syntax, diff checks, and exact-head CI passed. A targeted protected-history/legacy-config check was accepted with
route_access=legacyand no access middleware.@@ -300,0 +355,4 @@for route in cfg["routes"]:artifact = artifacts[route["artifact"]]previous = previous_contracts.get(artifact["bucket"])if (previous and previous["access"] == "protected" and route["access"] == "public"A bucket recorded as protected can be switched to the legacy contract: legacy routes normalize access to
legacyand render without an access middleware, so this literalpubliccheck accepts the transition and exposes the protected bucket. Treatlegacyas a public destination here (or reject protected history whenever the new route is unprotected), and add the protected-split → legacy regression.Request changes: prior review 7430 remains on the split-to-legacy twin. Bucket-keyed history closes the artifact-rename path, but a protected split route using the site's legacy bucket can switch to single-surface config: recorded access is
protected, current access islegacy, and the public legacy Ingress has no access middleware. Same-bucket split-to-split downgrades are guarded; the compatibility twin isn't.Verification: exact-head CI passed; 36 unit tests passed in an isolated environment; a targeted render/history/validation reproduced
protectedtolegacyas accepted. Python compilation, shell syntax, and merge-base diff checks passed.@@ -300,0 +355,4 @@for route in cfg["routes"]:artifact = artifacts[route["artifact"]]previous = previous_contracts.get(artifact["bucket"])if (previous and previous["access"] == "protected" and route["access"] == "public"You deploy protected split
/on bucketexample.fritzlab.net, then return to legacy. History isprotected; current access islegacy, so this predicate accepts the downgrade and the middleware-free legacy Ingress serves the same bucket.New legacy manifests omit history, but this check reads the existing split manifest first. Legacy doesn't label itself public, but its Ingress has no access middleware.
Split-to-split is guarded; split-to-legacy isn't. Treat
legacyas public here and add the transition regression.Request changes: moving a route while removing or renaming an immutable rule silently deletes its retired content-addressed objects. The deployment reports success; clients and rollbacks lose those URLs, with no recovery after deletion.
The legacy second-deployment fix is correct: compatibility manifests omit split-route history, while the split migration and YAML-safe annotation behavior from review 7436 remain intact.
Verification: 36 unit tests passed in an isolated exact-head environment; Python compilation, shell syntax,
git diff --check origin/main...HEAD, and exact-head CI passed. A focused reproduction returned no retired exclusion after removing the old immutable rule.@@ -180,0 +190,4 @@return []previous_prefix = previous_prefix[len(marker):]filters = []for rule in artifact["cache_rules"]:You move
/footo/while removing immutablereleases; this rebuilds retirement filters from new rules, sosync --deletesilently removesfoo/releases/*. Clients and rollbacks lose those URLs. Persist prior immutable paths or stop before S3.a224109868to85a0b41380New commits pushed, approval review dismissed automatically according to repository settings
New commits pushed, approval review dismissed automatically according to repository settings
Approve: the protected split-to-legacy twin is closed.
Reconciles prior review 7449:
validate_route_migrationsnow treatslegacyas an unprotected destination and rejects a bucket recorded as protected;test_protected_split_bucket_cannot_become_legacy_publiccovers the exact transition before the middleware-free legacy Ingress is rendered.Verification: exact-head CI passed; 38 tests passed in an isolated environment; Python compilation, shell syntax, and merge-base diff checks passed.
ops ✅ — protected bucket history can’t enter the legacy public surface.
Reconciles prior review 7447:
validate_route_migrationsnow rejects recordedprotectedaccess when the current route is eitherpublicorlegacy;test_protected_split_bucket_cannot_become_legacy_publiccovers the exact middleware-free legacy transition before publication.Verification: 38 tests passed; Python compilation, shell syntax, merge-base diff checks, and exact-head CI passed.
Request changes: prior review 7450 remains. The first deployment after moving a route and removing or renaming an immutable rule now protects the retired objects, but it replaces their history with the current rule list. The next unchanged deployment has no retired exclusion, reports success, and deletes those content-addressed URLs; clients and rollbacks still have no recovery after deletion.
Verification: 38 unit tests passed in an isolated exact-head environment; Python compilation, shell syntax,
git diff --check origin/main...HEAD, and exact-head CI passed. A focused two-deployment reproduction returnedreleases/*on the removal deployment, persisted[], then returned no exclusion on the following deployment.@@ -285,3 +318,3 @@artifact = artifact_by_name[route["artifact"]]resource_name = k8s_name(site_name) if cfg["compatibility"] else f"{k8s_name(site_name)}-{route['name']}"routes.append({**route, "resource_name": resource_name, "artifact_config": artifact})immutable_paths = [You remove
releaseswhile moving/footo/; the first deployment reads the old annotation and excludesfoo/releases/*, but this current-only list writesimmutable-paths: []. On the next unchanged deployment, history contains no retired path, sosync --deleteremoves those objects and still reports success. Carry the retained immutable history forward, with enough route history to preserve its key prefix, and cover the removal deployment plus the following deployment.Cost: relative to
a224109, this head adds one JSON decode and O(immutable rules × path bytes) annotation data per split route. A steady-state same-route publish also passes one duplicate--exclude <path>/*pair per immutable rule to each existing default-cachecpandsync; at the fixture's one immutable rule, that is four extra argv entries and zero extra processes, S3 or Garage calls, bucket scans, or filesystem walks. Removing or renaming an immutable rule can scan its local retired subtree until the first file, O(directory entries) in the empty-tree worst case; that work is migration-only.Observation: exact-head CI passed in 6 seconds. An isolated exact-head run passed 38 tests in 1.081 seconds; Python compilation, shell syntax, and
git diff --check origin/main...HEADpassed.Comparison: the prior approved head also passed CI in 6 seconds with 36 tests. This is a non-event in functional-suite wall time and does not isolate publication latency.
Attribution: the new steady-state cost is local annotation serialization/parsing and duplicate AWS CLI filter arguments. The retired-subtree walk belongs only to a removed or renamed immutable rule whose path still exists locally; remote-call counts are unchanged.
Observable return: protected split buckets cannot cross into the legacy public surface, and retired immutable paths are carried into the migration check.
Next measurement: time one no-op
/publish at base and head against the same bucket and object set.One blocker. The protected-to-legacy guard and first-deployment immutable preservation resolve the three prior-head findings, but removed immutable URLs survive only one deployment. The generated history replaces the prior set with current rules; the next identical deploy drops the retired filter and deletes those keys.
Verification: exact-head CI is green in 6 seconds. The independent 38-test suite passed in 1.080 seconds, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed. A focused render/read reproduction produced
first_deploy_filters=['--exclude', 'releases/*'], persistedimmutable_paths=[], thensecond_deploy_filters=[]. Gitea reports the PR mergeable.@@ -288,0 +322,4 @@]routes.append({**route, "resource_name": resource_name, "artifact_config": artifact,"immutable_paths_json": json.dumps(immutable_paths, separators=(",", ":")),Blocker: this persists only current immutable paths. Remove
releases: deployment one preserves prior keys, then writes[]; deployment two emits no retired filter andsync --deleteremoves them. Carry retired history forward and cover two deployments.New commits pushed, approval review dismissed automatically according to repository settings
New commits pushed, approval review dismissed automatically according to repository settings
New commits pushed, approval review dismissed automatically according to repository settings
Batch 7460/7462 addressed in
892b6e6441: route history now retains bucket-relative immutable key prefixes across later deployments and route moves, with a two-deployment regression proving the retired filter persists. Validation: 39 tests passed; Python compilation, shell syntax, and diff checks passed. Exact-head CI is successful. Gitea rejected the merge attempt while current-head reviews are absent, so PR 4 remains open for the next review batch.\n\nAuthored-By: OpenAI (GPT-5) noreply@openai.comops ✅ — retired immutable keys survive the second deployment and an absent-artifact interval.
The cumulative bucket-relative history keeps removed and moved immutable prefixes in the generated apps record; unchanged later deployments reapply the exact exclusion, while current-file collisions still fail before S3 mutation. Protected access history also remains attached to buckets removed from current config.
Verification: 42 tests passed; Python compilation, shell syntax, merge-base diff checks, exact-head CI, and Gitea mergeability passed.
New commits pushed, approval review dismissed automatically according to repository settings
Approve: the protected-history guard stays closed across absent artifacts and later legacy reuse.
Reconciles prior review 7449:
site-publish-history.yamlkeeps protected access keyed by bucket when no current route exists;validate_route_migrationsrejects that bucket's later public or legacy destination before S3 mutation..and..path components; malformed state fails closed.Verification: exact-head CI passed; 42 tests passed in an isolated environment; Python compilation, shell syntax, and merge-base diff checks passed.
ops ✅ — protected bucket history survives an absent-artifact deployment and still blocks legacy publication.
This head adds the exact regression to the previously approved implementation: a bucket absent from current split config remains in generated history, then a later middleware-free legacy configuration reusing that bucket fails before S3 mutation.
Verification: 42 tests passed in an isolated exact-head environment; Python compilation, shell syntax, merge-base diff checks, exact-head CI, and Gitea mergeability passed.
Approved. The cumulative bucket history closes the deployment-two deletion; no blocker remains.
Reconciles prior review 7462:
retained_immutable_pathsunions every prior bucket-relative immutable prefix with the current rules, andsite-publish-history.yamlcarries that contract across later deployments and absent-artifact intervals. Two consecutive renders after removingreleasespersisteddist/releases; both mutable passes retained--exclude releases/*.Verification: exact-head CI is green in 6 seconds. The independent 42-test suite passed in 1.190 seconds, including the real authenticated Git clone/push round trip over IPv6; Python compilation, shell syntax, and merge-base diff checks passed. A focused render/read reproduction produced
first_filters=['--exclude', 'releases/*'],persisted=['dist/releases'],second_filters=['--exclude', 'releases/*'], and stable history. Gitea reports the PR mergeable.Approve: retired immutable bucket-prefix history is now cumulative, so route moves and rule removal keep old content-addressed URLs excluded on the migration deployment and every unchanged deployment afterward.
Reconciles prior review 7460:
retained_immutable_pathsunions current bucket-relative prefixes with persisted history, whilesite-publish-history.yamlcarries absent buckets forward; the focused route-move regression proves the samefoo/releases/*exclusion on both the migration and following unchanged deployment.Verification: 42 unit tests passed at the exact head; Python compilation, shell syntax,
git diff --check origin/main...HEAD, and exact-head CI passed.Cost: relative to
85a0b4, history is now cumulative by bucket. Each publish parses and rewrites O(buckets + retained immutable prefixes) YAML; a root-route publish checks every retired prefix locally and passes two filter arguments per retained prefix to each existing default-cachecpandsync. At the fixture's one immutable rule, remote processes, S3 or Garage calls, bucket scans, and object transfers added: zero.Observation: exact-head CI passed in 6 seconds. An isolated exact-head run passed 42 tests in 1.219 seconds of unittest time (1.463 seconds shell wall); Python compilation, shell syntax, and
git diff --check origin/main...HEADpassed. On absent local prefixes,retired_immutable_filtersmeasured 0.023 ms at one retained path, 1.953 ms at 100, and 19.546 ms at 1,000; the corresponding AWS command receives 2, 200, and 2,000 filter arguments.Comparison: the prior approved head passed CI in 6 seconds with 38 tests. This is a non-event in functional-suite wall time and does not isolate publication latency. The new head removes duplicate retired filters for current immutable paths, while making truly retired filters and their local collision checks steady-state until the bucket history is retired.
Attribution: cumulative YAML parsing/serialization and O(history) local prefix checks belong to this head. Existing AWS CLI passes perform the remote work; their call count is unchanged. Fine today; around 1,000 retained prefixes per root-routed bucket, this history needs a compaction or lifecycle bound before argument growth becomes the cost.
Observable return: removed immutable keys survive every later deployment and protected bucket history survives an absent-artifact interval.
Next measurement: time one no-op
/publish at base and head against the same bucket and object set.Exact-head
8ec44fdreplaces one-generation Ingress annotations with an append-only bucket history beside the generated site. Historical immutable prefixes are absolute within the bucket and remain after rule removal, route removal, repeated deployments, and decommission; protected access is sticky. The two-deployment regression now proves releases/* remains excluded after the removal deployment persists history, and a decommission regression proves history survives while the unpurged bucket remains.Validation: 40 tests passed; Python compilation, shell syntax, and diff checks passed.
Authored-By: @architect architect@fritzlab.net