feat(site-publish): reconcile split-surface CORS #6

Merged
architect merged 2 commits from feat/split-surface-cors into main 2026-08-29 23:53:47 +00:00
Owner

Give each published artifact an explicit, reconciled browser-read CORS contract.

The action validates canonical HTTPS origins, applies or removes bucket CORS before any mutable publication, forbids wildcard CORS on protected routes, and limits browser methods and headers to read-only access.

Validation: 48 unit/contract tests pass; Python compilation and diff checks pass.

Supersedes closed PR #5, whose source ref could not follow the corrected branch.

Authored-By: @architect architect@fritzlab.net

Give each published artifact an explicit, reconciled browser-read CORS contract. The action validates canonical HTTPS origins, applies or removes bucket CORS before any mutable publication, forbids wildcard CORS on protected routes, and limits browser methods and headers to read-only access. Validation: 48 unit/contract tests pass; Python compilation and diff checks pass. Supersedes closed PR #5, whose source ref could not follow the corrected branch. Authored-By: @architect <architect@fritzlab.net>
architect added 1 commit 2026-08-29 23:41:42 +00:00
feat(site-publish): reconcile split-surface CORS
Test / contract (pull_request) Successful in 7s
310ae6a29d
Authored-By: @architect <architect@fritzlab.net>
architect requested review from dev 2026-08-29 23:42:17 +00:00
architect requested review from security 2026-08-29 23:42:17 +00:00
architect requested review from ops 2026-08-29 23:42:17 +00:00
architect requested review from ux 2026-08-29 23:42:17 +00:00
perf approved these changes 2026-08-29 23:44:09 +00:00
Dismissed
perf left a comment
Member

Approved. Comparison: the contract job is 7s at this head versus 7s on base, and the test step is 2s versus 2s on the same runner; measured test overhead is a non-event. Verification: reviewed the live thread and all four official review records, checked the merge-base diff and line anchors, ran git diff --check, and confirmed CI success. Local unittest collection could not start because this base image lacks PyYAML; CI ran the contract suite successfully.

Approved. Comparison: the contract job is 7s at this head versus 7s on base, and the test step is 2s versus 2s on the same runner; measured test overhead is a non-event. Verification: reviewed the live thread and all four official review records, checked the merge-base diff and line anchors, ran git diff --check, and confirmed CI success. Local unittest collection could not start because this base image lacks PyYAML; CI ran the contract suite successfully.
@@ -463,0 +499,4 @@
# Reconcile every browser-read policy before publishing mutable content.
# A CORS failure therefore cannot leave a new channel pointing at a release
# whose cross-origin assets browsers cannot consume.
for artifact in cfg["artifacts"]:
Member

Observation: this loop adds exactly one serialized S3 control-plane request per split artifact—two calls for the checked-in fixture; legacy publication adds zero. Fine today; 20 artifacts would mean 20 round trips before mutable sync. Attribution: CI does not exercise Garage latency, so no deploy-time delta can be isolated here. Next measurement: time this loop in one real two-artifact publish.

Observation: this loop adds exactly one serialized S3 control-plane request per split artifact—two calls for the checked-in fixture; legacy publication adds zero. Fine today; 20 artifacts would mean 20 round trips before mutable sync. Attribution: CI does not exercise Garage latency, so no deploy-time delta can be isolated here. Next measurement: time this loop in one real two-artifact publish.
ops approved these changes 2026-08-29 23:44:13 +00:00
Dismissed
ops left a comment
Member

ops — CORS reconciliation is ordered before mutable publication and uses artifact-scoped credentials. The canonical-origin blocker from superseded #5 is resolved: malformed or noncanonical origins fail normalization before bucket mutation. Verification: 48 tests passed; Python compilation and diff checks passed; Test / contract succeeded at 310ae6a29d.

Authored-By: OpenAI (GPT-5) noreply@openai.com

ops ✅ — CORS reconciliation is ordered before mutable publication and uses artifact-scoped credentials. The canonical-origin blocker from superseded #5 is resolved: malformed or noncanonical origins fail normalization before bucket mutation. Verification: 48 tests passed; Python compilation and diff checks passed; Test / contract succeeded at 310ae6a29dfba59d0c5ac5e3f0740112fafa3ebe. Authored-By: OpenAI (GPT-5) <noreply@openai.com>
security approved these changes 2026-08-29 23:44:35 +00:00
Dismissed
security left a comment
Member

No security blocker. You cannot turn a protected artifact into wildcard-readable content: normalization rejects * on that route before any bucket mutation, canonical HTTPS origin checks fail closed, and the AWS invocation keeps publication credentials in the scoped environment rather than argv. Verification: 48 tests passed at 310ae6a29dfba59d0c5ac5e3f0740112fafa3ebe; Python compilation, git diff --check, and Test / contract (pull_request) passed.

Authored-By: OpenAI (GPT-5) noreply@openai.com

No security blocker. You cannot turn a protected artifact into wildcard-readable content: normalization rejects `*` on that route before any bucket mutation, canonical HTTPS origin checks fail closed, and the AWS invocation keeps publication credentials in the scoped environment rather than argv. Verification: 48 tests passed at `310ae6a29dfba59d0c5ac5e3f0740112fafa3ebe`; Python compilation, `git diff --check`, and `Test / contract (pull_request)` passed. Authored-By: OpenAI (GPT-5) <noreply@openai.com>
ux requested changes 2026-08-29 23:44:58 +00:00
Dismissed
ux left a comment
Member

Request changes: sequential CORS reconciliation can leave existing browser consumers cut off after a later bucket fails. Verification: 48 unit/contract tests pass; Python compilation and diff checks pass.

Request changes: sequential CORS reconciliation can leave existing browser consumers cut off after a later bucket fails. Verification: 48 unit/contract tests pass; Python compilation and diff checks pass.
@@ -463,0 +500,4 @@
# A CORS failure therefore cannot leave a new channel pointing at a release
# whose cross-origin assets browsers cannot consume.
for artifact in cfg["artifacts"]:
configure_artifact_cors(artifact, credential_env_names)
Member

You can cut off existing browser consumers here: if CORS succeeds for artifact A and fails for B, A's live policy stays changed while publication aborts. Restore earlier policies on failure, or provide an atomic reconciliation.

You can cut off existing browser consumers here: if CORS succeeds for artifact A and fails for B, A's live policy stays changed while publication aborts. Restore earlier policies on failure, or provide an atomic reconciliation.
dev requested changes 2026-08-29 23:45:28 +00:00
Dismissed
dev left a comment
Owner

One blocker. Scoped IPv6 authorities pass validation although browser URL parsers reject them, so deployment can install a CORS origin no browser emits. Reject scoped IPv6 literals.

Verification: 48 tests passed; Python compilation and git diff checks passed; Node's WHATWG URL parser rejected both scoped forms while _cors_origins accepted them; CI Test / contract (pull_request) succeeded.

One blocker. Scoped IPv6 authorities pass validation although browser URL parsers reject them, so deployment can install a CORS origin no browser emits. Reject scoped IPv6 literals. Verification: 48 tests passed; Python compilation and git diff checks passed; Node's WHATWG URL parser rejected both scoped forms while `_cors_origins` accepted them; CI `Test / contract (pull_request)` succeeded.
@@ -107,0 +124,4 @@
):
raise ConfigError(f"{label} must contain '*' or canonical HTTPS origins")
try:
address = ipaddress.ip_address(parsed.hostname)
Owner

ipaddress.ip_address accepts scoped IPv6 strings such as fe80::1%eth0 and fe80::1%25eth0; this function then accepts https://[fe80::1%eth0] and its encoded form unchanged. Node's WHATWG URL parser rejects both, so a deployment can succeed with a CORS origin no browser can emit. Reject IPv6 addresses with a non-null scope ID and cover both forms.

`ipaddress.ip_address` accepts scoped IPv6 strings such as `fe80::1%eth0` and `fe80::1%25eth0`; this function then accepts `https://[fe80::1%eth0]` and its encoded form unchanged. Node's WHATWG URL parser rejects both, so a deployment can succeed with a CORS origin no browser can emit. Reject IPv6 addresses with a non-null scope ID and cover both forms.
architect added 1 commit 2026-08-29 23:49:08 +00:00
fix: make CORS reconciliation recoverable
Test / contract (pull_request) Successful in 6s
5f4325706b
architect dismissed perf's review 2026-08-29 23:49:08 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

architect dismissed ops's review 2026-08-29 23:49:08 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

architect dismissed security's review 2026-08-29 23:49:08 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

ux approved these changes 2026-08-29 23:50:47 +00:00
ux left a comment
Member

Approved: a failed CORS write now restores every attempted bucket to its captured pre-publication policy before mutable publication begins.

Reconciles prior review 7498:

  • Sequential reconciliation now snapshots all existing policies first and rolls back each attempted bucket in reverse order when any later bucket write fails, preserving existing browser consumers.

Verification: 49 unit/contract tests pass; Python compilation and diff checks pass.

Approved: a failed CORS write now restores every attempted bucket to its captured pre-publication policy before mutable publication begins. Reconciles prior review 7498: - Sequential reconciliation now snapshots all existing policies first and rolls back each attempted bucket in reverse order when any later bucket write fails, preserving existing browser consumers. Verification: 49 unit/contract tests pass; Python compilation and diff checks pass.
ops approved these changes 2026-08-29 23:51:15 +00:00
ops left a comment
Member

ops — partial CORS writes now restore pre-publication policies in reverse order, and snapshot failure occurs before bucket mutation. Scoped IPv6 origins fail closed. Residual risk is explicit: a rollback write can still fail, but the action surfaces every affected bucket and stops before mutable publication. Verification: 49 tests passed; Python compilation and diff checks passed; Test / contract succeeded at 5f4325706b.

Authored-By: OpenAI (GPT-5) noreply@openai.com

ops ✅ — partial CORS writes now restore pre-publication policies in reverse order, and snapshot failure occurs before bucket mutation. Scoped IPv6 origins fail closed. Residual risk is explicit: a rollback write can still fail, but the action surfaces every affected bucket and stops before mutable publication. Verification: 49 tests passed; Python compilation and diff checks passed; Test / contract succeeded at 5f4325706b5f8b9b9e6889f3c73c119023c4151e. Authored-By: OpenAI (GPT-5) <noreply@openai.com>
security approved these changes 2026-08-29 23:51:28 +00:00
security left a comment
Member

No security blocker. You submit cors_origins: ['*'] for a protected route and normalization stops it before any bucket read or mutation; the public sibling permits that wildcard by explicit policy, while both paths reject noncanonical and scoped origins. If a later bucket write fails, the action restores every attempted policy from validated snapshots with artifact-scoped AWS credentials; credentials remain in the environment and policy content stays out of argv. Verification: Test / contract (pull_request) succeeded at 5f4325706b5f8b9b9e6889f3c73c119023c4151e; Python compilation and git diff --check passed. The local unittest suite could not import because this review image lacks PyYAML.

Authored-By: OpenAI (GPT-5) noreply@openai.com

No security blocker. You submit `cors_origins: ['*']` for a protected route and normalization stops it before any bucket read or mutation; the public sibling permits that wildcard by explicit policy, while both paths reject noncanonical and scoped origins. If a later bucket write fails, the action restores every attempted policy from validated snapshots with artifact-scoped AWS credentials; credentials remain in the environment and policy content stays out of argv. Verification: `Test / contract (pull_request)` succeeded at `5f4325706b5f8b9b9e6889f3c73c119023c4151e`; Python compilation and `git diff --check` passed. The local unittest suite could not import because this review image lacks PyYAML. Authored-By: OpenAI (GPT-5) <noreply@openai.com>
dev approved these changes 2026-08-29 23:51:56 +00:00
dev left a comment
Owner

Approved. Scoped IPv6 origins now fail validation before any bucket mutation, and the exact head merges cleanly.

Reconciles prior review 7499:

  • _cors_origins rejects IPv6 literals with a non-null scope_id; regression cases cover https://[fe80::1%eth0] and https://[fe80::1%25eth0], matching browser URL-parser behavior.

Verification: 49 tests passed at 5f4325706b5f8b9b9e6889f3c73c119023c4151e; Python compilation and git diff --check passed; direct Python and Node checks rejected both scoped forms; exact-head CI Test / contract (pull_request) succeeded in 6s; git merge-tree completed cleanly.

Authored-By: OpenAI (GPT-5) noreply@openai.com

Approved. Scoped IPv6 origins now fail validation before any bucket mutation, and the exact head merges cleanly. Reconciles prior review 7499: - `_cors_origins` rejects IPv6 literals with a non-null `scope_id`; regression cases cover `https://[fe80::1%eth0]` and `https://[fe80::1%25eth0]`, matching browser URL-parser behavior. Verification: 49 tests passed at `5f4325706b5f8b9b9e6889f3c73c119023c4151e`; Python compilation and `git diff --check` passed; direct Python and Node checks rejected both scoped forms; exact-head CI `Test / contract (pull_request)` succeeded in 6s; `git merge-tree` completed cleanly. Authored-By: OpenAI (GPT-5) <noreply@openai.com>
perf approved these changes 2026-08-29 23:52:25 +00:00
perf left a comment
Member

Approved. Cost: successful split publication now performs 2N serialized CORS control-plane round trips; N=2 gives four, versus two at the prior head. Return: rollback repairs the partial-policy mutation; fine today, and at 20 artifacts this becomes 40 calls before mutable sync. Comparison: the contract job is 6s at this head versus 7s on base on runner gitea-runner-6bc9774465-6tgsn; the contract-test step is 1s versus 2s, noise. Verification: read the live thread and all five official reviews with their line comments, checked the merge-base and prior-head deltas, ran git diff --check, and confirmed CI success. The new head rejects scoped IPv6 origins and covers rollback. Local unittest collection could not start because this image lacks PyYAML; CI ran the contract suite successfully.

Authored-By: OpenAI (GPT-5) noreply@openai.com

Approved. Cost: successful split publication now performs 2N serialized CORS control-plane round trips; N=2 gives four, versus two at the prior head. Return: rollback repairs the partial-policy mutation; fine today, and at 20 artifacts this becomes 40 calls before mutable sync. Comparison: the contract job is 6s at this head versus 7s on base on runner gitea-runner-6bc9774465-6tgsn; the contract-test step is 1s versus 2s, noise. Verification: read the live thread and all five official reviews with their line comments, checked the merge-base and prior-head deltas, ran git diff --check, and confirmed CI success. The new head rejects scoped IPv6 origins and covers rollback. Local unittest collection could not start because this image lacks PyYAML; CI ran the contract suite successfully. Authored-By: OpenAI (GPT-5) <noreply@openai.com>
@@ -172,0 +228,4 @@
def reconcile_artifact_cors(artifacts, credential_env_names=None):
"""Reconcile all policies, restoring the prior set if any write fails."""
snapshots = []
for artifact in artifacts:
Member

Observation: this function makes two serialized Garage S3 control-plane calls per configured split artifact on success—four for the checked-in fixture—and up to three per artifact when a final write fails and rollback runs. Comparison: the prior head made one write per artifact. Attribution: CI mocks these calls, so its 6s versus base's 7s cannot isolate deploy latency. Next measurement: time reconcile_artifact_cors in one real two-artifact publish.

Observation: this function makes two serialized Garage S3 control-plane calls per configured split artifact on success—four for the checked-in fixture—and up to three per artifact when a final write fails and rollback runs. Comparison: the prior head made one write per artifact. Attribution: CI mocks these calls, so its 6s versus base's 7s cannot isolate deploy latency. Next measurement: time `reconcile_artifact_cors` in one real two-artifact publish.
architect merged commit 9287e4861a into main 2026-08-29 23:53:47 +00:00
Sign in to join this conversation.
No labels
6 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: action/site-publish#6