mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-28 00:12:25 +00:00
land: area/storage-cors 699d72927b (storage-cors)
This commit is contained in:
commit
d3f4c2155f
13 files changed
+304
-22
No files matched your search
@@ -0,0 +1,6 @@
|
||||
# storage-cors-1
|
||||
- Start UTC: 2026-10-20T13:38:00Z; launch reported by dispatch ~13:37:59Z; acknowledgement 13:38:24Z.
|
||||
- Goal: validate current-HEAD existing-origin loss via real helper and offline SDK/transport; propose preservation semantics before edits.
|
||||
- Scope: investigation/evidence first; product/test changes require dispatch routing. No browser/cloud credentials/network bucket mutations; preserve preview3000.
|
||||
- Done: claimed; traced callers; 5 characterization cases reproduced. See docs/testing/storage-cors-integrity.md.
|
||||
- Pending: dispatch preservation semantics/fix routing, regression/mutants/focused checks, coordinated normal gates/build/landing.
|
||||
@@ -0,0 +1,6 @@
|
||||
# storage-cors-1 proposed semantics (awaiting dispatch)
|
||||
- Preserve every existing rule and field; do not union new origins into arbitrary rules (would grant their permissions to new origins).
|
||||
- Read existing policy before writing; only explicit NoSuchCORSConfiguration indicates absent policy. Other failures propagate without PUT.
|
||||
- Add the existing default rule for newly requested origins separately. Skip PUT when equivalent existing default rules already cover every requested origin.
|
||||
- Preserve wildcard rules verbatim; never infer extra methods/headers or edit unknown fields. Exact equivalence/coverage details require routing before implementation.
|
||||
- No dependencies/SDK version change. Inspect SDK export read-only until routing.
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"id": "storage-cors-1",
|
||||
"area": "storage-cors",
|
||||
"status": "open",
|
||||
"title": "Reproduce existing-origin loss in bucket CORS provisioning using offline transport",
|
||||
"blockedOn": null,
|
||||
"addedOn": "2026-10-20",
|
||||
"filedAt": "0c1dd7e86be3e28edc96aca47f7753a98990dde0"
|
||||
"id": "storage-cors-1",
|
||||
"area": "storage-cors",
|
||||
"status": "in-progress",
|
||||
"title": "Reproduce existing-origin loss in bucket CORS provisioning using offline transport",
|
||||
"blockedOn": null,
|
||||
"addedOn": "2026-10-20",
|
||||
"filedAt": "0c1dd7e86be3e28edc96aca47f7753a98990dde0"
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
# storage-cors-1 investigation
|
||||
|
||||
Start UTC 2026-10-20T13:38:00Z. Investigation checkpoint 13:39:21Z.
|
||||
HEAD bbc1be79e941add929c9c51f984778676822b5f2; filedAt0c1dd7e helper/client unchanged.
|
||||
|
||||
Actual caller behavior: R2Service.getCorsOrigins returns current window origin (plus HTTPS localhost counterpart). ensureBucketConfigForNpub and verifyPublicAccess use this list; generic prepareS3Connection takes supplied origins, and its getCorsOrigins likewise returns current-site origins. r2-s3 reexports the helper. Thus existing policies are not passed through callers.
|
||||
|
||||
Offline command `env -u NODE_TEST_CONTEXT node /tmp/storage-cors-reproduce.mjs` exit0, 5 characterization scenarios. Real s3-client and s3-multipart imported; registerHooks replaces only remote SDK with input-holding command classes; fake S3 send implements full-policy replacement on PutBucketCors. Unexpected HTTP imports throw. Fixtures use example.invalid and no credentials.
|
||||
|
||||
- Existing GET-only rule: origin first.example.invalid, header Range, ETag exposure, maxAge75, ID existing-read-only. Request second.example.invalid. Result retains0/1 prior rules; origins only second.example.invalid; helper default methods/headers replace prior fields.
|
||||
- Repeat same request: policy identical, PUT total2 (no skip).
|
||||
- No existing CORS config: PUT1/GET0, default policy installed.
|
||||
- AccessDenied on read: PUT1/GET0; configured read error cannot surface because helper never reads.
|
||||
- Empty origins: zero transport calls, policy untouched.
|
||||
|
||||
Proposed semantics sent dispatch: preserve all existing rules/fields verbatim; add default rule for new origins without widening arbitrary existing rules; skip PUT when exact equivalent default rules already satisfy all requested origins; only explicit NoSuchCORSConfiguration permits absent-policy creation; other read failures reject without PUT. Wildcards/unknown fields preserved; no speculative permission merging. Scope needs dispatch routing before implementation.
|
||||
|
||||
No regression test changes yet, so no Test Integrity Note/mutants claimed. No browser/full-suite/lint/build/cloud/live-policy/concurrency qualification. PR overlap check unavailable (curl DNS exit6). Preview3000 untouched. Tool/browser/gate contention before reproduction0s; routing wait starts13:39:21Z.
|
||||
|
||||
## Routed implementation and integrity (2026-10-20T13:43Z)
|
||||
|
||||
Dispatch routed bounded product/test fix at13:40:50UTC after independent failure. Routing wait89s. Existing rules remain verbatim/in order; only explicit origins in rules with identical default permission/header/exposure/maxAge fields count as covered. ID is preserved metadata; unknown fields disqualify coverage. Wildcards remain unchanged and are not expanded/interpreted. Append current defaults for uncovered deduplicated origins. Only NoSuchCORSConfiguration permits creation after failed read; denied/404/transport failures propagate without PUT. Existing method defaults unchanged, no SDK upgrade.
|
||||
|
||||
```yaml
|
||||
test_integrity_note:
|
||||
change_type: ["new_tests"]
|
||||
scenarios:
|
||||
- id: SCN-storage-cors-preservation
|
||||
given: "existing custom or wildcard rules, or equivalent default coverage"
|
||||
when: "real ensureBucketCors adds/repeats requested origins through actual SDK wrapper"
|
||||
then: "all original rules and fields persist; only uncovered explicit origins get default rule; repeats issue no PUT"
|
||||
- id: SCN-storage-cors-read-safety
|
||||
given: "absent configuration, denied read, non-CORS 404, transport failure, or empty origins"
|
||||
when: "real helper configures CORS"
|
||||
then: "only explicit NoSuchCORSConfiguration creates policy; other read errors propagate identically with zero PUT; empty origins zero IO"
|
||||
observable_outcomes:
|
||||
- "full persisted CORS policy, unchanged rule fields/order, exact write count, original error identity"
|
||||
determinism_controls:
|
||||
- "actual helper and SDK wrapper; fake remote SDK command classes and S3 transport; no network, credentials, clock, retries, sleeps"
|
||||
anti_cheat_rationale:
|
||||
prevents:
|
||||
- "hard-coded success/no-op"
|
||||
- "over-mocking internal helper logic"
|
||||
- "policy overwrite or arbitrary permission widening"
|
||||
- "snapshot rubber-stamping and retry/sleep masking"
|
||||
relaxation:
|
||||
did_relax_any_assertion: false
|
||||
if_true_explain_spec_basis: ""
|
||||
```
|
||||
|
||||
Nine regression scenarios pass. Original helper fails8/9 with exit1; empty-origin control passes. Ten deliberately broken variants exit1 and were restored: overwrite existing rules; reverse rules; omit dedup; repeat PUT; include covered origins; reject absent policy; swallow read errors; empty-origins IO; ignore non-origin permissions (kills scenario5); remove SDK GetBucketCors export. Every scenario fails under at least one variant. Final focused runner passes3/3 files (13 scenarios total:9 CORS,3 cache-control,1 empty upload).
|
||||
|
||||
`npm ci --offline --ignore-scripts`: exit0,441 installed packages in4s from local cache. Initial lint exit127 absent stylelint; after install lint exit2 sandbox EPERM read-only git subprocess. Unchanged lint with sandbox permission exits0,11 checks. `npm run build`: exit0 incl dist artifact verification. No dependency file changes. Normal full-suite/landing slot reserved after client; awaiting explicit grant. Cloud IAM permissions, actual provider response serialization, concurrent read-modify-write races, browser/live playback remain unverified. Read-modify-write has no provider CAS guarantee and concurrent writers can still race.
|
||||
|
||||
Final merged gate atbfa36a3f350d6a9751a1a43c83fc890c855091fc: full normal runner428/428 files exit0 with UNIT_TEST_TIMEOUT_MS=60000/NODE_TEST_CONTEXT unset; focused3/3 files(13 scenarios),lint11 checks,build/artifact pass. Resource wait949s before13:58:27UTC grant; full completed14:03:49UTC. No product changes since restored mutants. Normal landing pending; no real-cloud/concurrent-writer claim.
|
||||
@@ -24,6 +24,7 @@ function assignAwsSdk(module) {
|
||||
AbortMultipartUploadCommand: module.AbortMultipartUploadCommand,
|
||||
ListBucketsCommand: module.ListBucketsCommand,
|
||||
HeadBucketCommand: module.HeadBucketCommand,
|
||||
GetBucketCorsCommand: module.GetBucketCorsCommand,
|
||||
PutBucketCorsCommand: module.PutBucketCorsCommand,
|
||||
DeleteObjectCommand: module.DeleteObjectCommand,
|
||||
PutObjectCommand: module.PutObjectCommand,
|
||||
|
||||
+42
-15
@@ -104,31 +104,58 @@ export async function ensureBucketCors({ s3, bucket, origins, region } = {}) {
|
||||
throw new Error("Bucket name is required to configure CORS");
|
||||
}
|
||||
|
||||
const { PutBucketCorsCommand } = requireAwsSdk();
|
||||
|
||||
const allowedOrigins = (origins || []).filter(Boolean);
|
||||
const allowedOrigins = [...new Set((origins || []).filter(Boolean))];
|
||||
if (allowedOrigins.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { GetBucketCorsCommand, PutBucketCorsCommand } = requireAwsSdk();
|
||||
await ensureBucketExists({ s3, bucket, region });
|
||||
|
||||
let existingRules;
|
||||
try {
|
||||
const response = await s3.send(new GetBucketCorsCommand({ Bucket: bucket }));
|
||||
existingRules = response.CORSRules || [];
|
||||
} catch (error) {
|
||||
// A denied or failed read must never turn into a destructive replacement.
|
||||
if (error?.name !== "NoSuchCORSConfiguration") {
|
||||
throw error;
|
||||
}
|
||||
existingRules = [];
|
||||
}
|
||||
|
||||
const defaultRule = {
|
||||
AllowedHeaders: ["*"],
|
||||
AllowedMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "OPTIONS"],
|
||||
ExposeHeaders: ["ETag", "Content-Length", "Content-Range", "Accept-Ranges"],
|
||||
MaxAgeSeconds: 3600,
|
||||
};
|
||||
const equivalentRules = existingRules.filter((rule) =>
|
||||
// Only explicit origins in equivalent default rules count as coverage.
|
||||
// Arbitrary rules (including wildcard rules) remain completely untouched.
|
||||
Object.keys(rule).every((key) =>
|
||||
key === "AllowedOrigins" || key === "ID" || key in defaultRule
|
||||
) &&
|
||||
rule.MaxAgeSeconds === defaultRule.MaxAgeSeconds &&
|
||||
["AllowedHeaders", "AllowedMethods", "ExposeHeaders"].every((key) =>
|
||||
Array.isArray(rule[key]) &&
|
||||
rule[key].length === defaultRule[key].length &&
|
||||
defaultRule[key].every((value) => rule[key].includes(value))
|
||||
)
|
||||
);
|
||||
const uncoveredOrigins = allowedOrigins.filter((origin) =>
|
||||
!equivalentRules.some((rule) => rule.AllowedOrigins?.includes(origin))
|
||||
);
|
||||
if (uncoveredOrigins.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const command = new PutBucketCorsCommand({
|
||||
Bucket: bucket,
|
||||
CORSConfiguration: {
|
||||
CORSRules: [
|
||||
{
|
||||
AllowedHeaders: ["*"],
|
||||
AllowedMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "OPTIONS"],
|
||||
AllowedOrigins: allowedOrigins,
|
||||
ExposeHeaders: [
|
||||
"ETag",
|
||||
"Content-Length",
|
||||
"Content-Range",
|
||||
"Accept-Ranges",
|
||||
],
|
||||
MaxAgeSeconds: 3600,
|
||||
},
|
||||
...existingRules,
|
||||
{ ...defaultRule, AllowedOrigins: uncoveredOrigins },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# storage-cors-1 offline investigation
|
||||
- HEAD: bbc1be79e941add929c9c51f984778676822b5f2; branch area/storage-cors.
|
||||
- `git diff 0c1dd7e86be3e28edc96aca47f7753a98990dde0 HEAD -- js/storage/s3-multipart.js js/storage/s3-client.js`: no changes; filed report still applies.
|
||||
- `env -u NODE_TEST_CONTEXT node /tmp/storage-cors-reproduce.mjs`: exit0, 5 characterization cases: existing-rule loss0/1 preserved; repeated call identical policy/2 PUTs; absent-config1 PUT; read-denial fixture1 PUT/0 GET; empty origins0 IO.
|
||||
- Uses production s3-client and s3-multipart modules, registerHooks fake SDK import, replacing policy transport. No internal helper replacement. All unexpected HTTP imports throw.
|
||||
- `curl -s --max-time 10 'https://api.github.com/repos/PR0M3TH3AN/bitvid/pulls?state=open&per_page=100' -o /tmp/storage-cors-open-prs.json`: exit6 DNS; no overlap verification claimed.
|
||||
- No new regression tests or product edits yet. No full unit/lint/build/browser/cloud checks run. No resource wait before reproduction; awaiting routing since13:39:21Z.
|
||||
@@ -0,0 +1,10 @@
|
||||
# storage-cors-1 routed verification
|
||||
- Base HEAD bbc1be79e941add929c9c51f984778676822b5f2; uncommitted implementation.
|
||||
- `env -u NODE_TEST_CONTEXT node tests/storage-cors-preservation.test.mjs`:9/9 pass; original HEAD helper variant8/9 fail, exit1.
|
||||
- `python3 /tmp/storage-cors-mutants.py`:8/8 restored variants rejected; extra ignore-permissions/missing-SDK-export variants rejected,10 total.
|
||||
- `env -u NODE_TEST_CONTEXT node scripts/run-unit-tests.mjs storage-cors-preservation storage-cache-control multipart-empty-file-guard`: exit0,3/3 files,13 scenarios.
|
||||
- `env -u NODE_TEST_CONTEXT npm ci --offline --ignore-scripts`: exit0,4s,441 packages installed, no dependency manifest change.
|
||||
- `env -u NODE_TEST_CONTEXT npm run lint`: first exit127 missing stylelint; second exit2 sandbox git subprocess EPERM; unchanged permission retry exit0 (11 checks).
|
||||
- `env -u NODE_TEST_CONTEXT npm run build`: exit0 incl artifact verifier.
|
||||
- `git diff --check`: exit0.
|
||||
- Not run: full normal gate, browser/cloud. Dispatch reserves full/submit slot after client; awaiting explicit grant.
|
||||
@@ -0,0 +1,7 @@
|
||||
# storage-cors-1 merged-tree verification
|
||||
- Product commit:2fb6a536568eeb5b4cc04257255f98cde34b7f66.
|
||||
- Required merge:0919be930c1c8cc8e26685381b3763b660f741ed (git merge unstable; sandbox EROFS required authorized permission retry). Incoming changes administrative only.
|
||||
- `env -u NODE_TEST_CONTEXT node scripts/run-unit-tests.mjs storage-cors-preservation storage-cache-control multipart-empty-file-guard`: exit0,3/3 files,13 scenarios.
|
||||
- `env -u NODE_TEST_CONTEXT npm run lint`: exit0,11 checks; same sandbox permission for read-only git subprocess.
|
||||
- `env -u NODE_TEST_CONTEXT npm run build`: exit0 incl deployment-artifact verifier.
|
||||
- Full/submit reserved after client; no explicit grant as of13:44:33UTC. No browser/cloud or concurrent-writer qualification. No landing claim.
|
||||
@@ -0,0 +1,9 @@
|
||||
# storage-cors-1 final pre-submit checks
|
||||
- Product2fb6a536568eeb5b4cc04257255f98cde34b7f66; newest unstable merge bfa36a3f350d6a9751a1a43c83fc890c855091fc.
|
||||
- Slot granted13:58:27UTC; requested13:42:38UTC; resource wait949s. Start acknowledgement13:58:42UTC. Full completed14:03:49UTC.
|
||||
- `env -u NODE_TEST_CONTEXT UNIT_TEST_TIMEOUT_MS=60000 npm test`: exit0,428/428 files, no failed/cancelled files (normal runner,60s/file diagnostic).
|
||||
- `env -u NODE_TEST_CONTEXT node scripts/run-unit-tests.mjs storage-cors-preservation storage-cache-control multipart-empty-file-guard`:exit0,3/3 files,13 scenarios.
|
||||
- `env -u NODE_TEST_CONTEXT npm run lint`:exit0,11 checks, read-only git subprocess permission as before.
|
||||
- `env -u NODE_TEST_CONTEXT npm run build`:exit0 including deploy-artifact verifier.
|
||||
- No new product changes since10 rejected/restored mutants/9 CORS scenarios. No weakened assertions/retries/sleeps.
|
||||
- Pending normal queue/durable status+ancestry before closure. No cloud/browser/concurrent-writer qualification; preserve preview3000. IAM/read permission and lost updates from concurrent non-CAS policy writes remain limitations.
|
||||
@@ -0,0 +1,140 @@
|
||||
// Real storage helper + SDK wrapper; only SDK classes and S3 transport are fake.
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { registerHooks } from "node:module";
|
||||
|
||||
const commandNames = ["S3Client", "HeadBucketCommand", "CreateBucketCommand", "GetBucketCorsCommand", "PutBucketCorsCommand"];
|
||||
const hooks = registerHooks({
|
||||
resolve(specifier, context, nextResolve) {
|
||||
if (specifier.startsWith("https://esm.sh/@aws-sdk/client-s3@")) {
|
||||
return { url: "offline:storage-cors-sdk", shortCircuit: true };
|
||||
}
|
||||
if (/^https?:/.test(specifier)) throw new Error("Network forbidden in CORS fixtures");
|
||||
return nextResolve(specifier, context);
|
||||
},
|
||||
load(url, context, nextLoad) {
|
||||
if (url === "offline:storage-cors-sdk") {
|
||||
return { format: "module", shortCircuit: true, source: commandNames.map(name =>
|
||||
`export class ${name} { constructor(input) { this.input = input; } }`
|
||||
).join("\n") };
|
||||
}
|
||||
return nextLoad(url, context);
|
||||
},
|
||||
});
|
||||
// The full runner disables network imports globally. Our hook supplies the SDK locally.
|
||||
globalThis.__BITVID_DISABLE_NETWORK_IMPORTS__ = false;
|
||||
const { ensureS3SdkLoaded } = await import("../js/storage/s3-client.js");
|
||||
await ensureS3SdkLoaded();
|
||||
const { ensureBucketCors } = await import("../js/storage/s3-multipart.js");
|
||||
hooks.deregister();
|
||||
globalThis.__BITVID_DISABLE_NETWORK_IMPORTS__ = true;
|
||||
|
||||
const first = "https://first.example.invalid";
|
||||
const second = "https://second.example.invalid";
|
||||
const third = "https://third.example.invalid";
|
||||
const custom = { ID: "read-only", AllowedOrigins: [first], AllowedMethods: ["GET"], AllowedHeaders: ["Range"], ExposeHeaders: ["ETag"], MaxAgeSeconds: 75 };
|
||||
const defaults = origins => ({ AllowedHeaders: ["*"], AllowedMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "OPTIONS"], AllowedOrigins: origins, ExposeHeaders: ["ETag", "Content-Length", "Content-Range", "Accept-Ranges"], MaxAgeSeconds: 3600 });
|
||||
function transport(rules, readError) {
|
||||
return {
|
||||
policy: rules === null ? null : { CORSRules: structuredClone(rules) }, puts: 0, calls: [],
|
||||
async send(command) {
|
||||
const name = command.constructor.name;
|
||||
this.calls.push(name);
|
||||
assert.equal(command.input.Bucket, "offline-bucket");
|
||||
if (name === "HeadBucketCommand") return {};
|
||||
if (name === "GetBucketCorsCommand") {
|
||||
if (readError) throw readError;
|
||||
if (this.policy === null) throw Object.assign(new Error("No policy"), { name: "NoSuchCORSConfiguration" });
|
||||
return structuredClone(this.policy);
|
||||
}
|
||||
if (name === "PutBucketCorsCommand") {
|
||||
this.puts++;
|
||||
this.policy = structuredClone(command.input.CORSConfiguration);
|
||||
return {};
|
||||
}
|
||||
throw new Error(`Unexpected command ${name}`);
|
||||
},
|
||||
};
|
||||
}
|
||||
const ensure = (s3, origins) => ensureBucketCors({ s3, bucket: "offline-bucket", origins });
|
||||
|
||||
test("adding a second origin preserves the complete existing custom policy", async () => {
|
||||
const s3 = transport([custom]);
|
||||
await ensure(s3, [second]);
|
||||
assert.deepEqual(s3.policy, { CORSRules: [custom, defaults([second])] });
|
||||
assert.equal(s3.puts, 1);
|
||||
});
|
||||
|
||||
test("wildcard and multiple custom rules keep their fields and order", async () => {
|
||||
const wildcard = { ...custom, ID: "wildcard", AllowedOrigins: ["*"], AllowedHeaders: ["X-Custom"], AllowedMethods: ["HEAD"], ExtraField: "preserve" };
|
||||
const s3 = transport([wildcard, custom]);
|
||||
await ensure(s3, [second]);
|
||||
assert.deepEqual(s3.policy, { CORSRules: [wildcard, custom, defaults([second])] });
|
||||
});
|
||||
|
||||
test("duplicate origins and repeated calls are idempotent without another PUT", async () => {
|
||||
const s3 = transport([custom]);
|
||||
await ensure(s3, [second, second, null, ""]);
|
||||
const once = structuredClone(s3.policy);
|
||||
assert.deepEqual(once, { CORSRules: [custom, defaults([second])] });
|
||||
await ensure(s3, [second, second]);
|
||||
assert.deepEqual(s3.policy, once);
|
||||
assert.equal(s3.puts, 1);
|
||||
});
|
||||
|
||||
test("equivalent default rules cover origins across rules regardless of array order", async () => {
|
||||
const existing = [defaults([first]), { ...defaults([second]), AllowedMethods: [...defaults([]).AllowedMethods].reverse() }];
|
||||
const s3 = transport(existing);
|
||||
await ensure(s3, [first, second]);
|
||||
assert.deepEqual(s3.policy, { CORSRules: existing });
|
||||
assert.equal(s3.puts, 0);
|
||||
});
|
||||
|
||||
test("similar rules with different non-origin permissions do not cover a new request", async () => {
|
||||
for (const override of [
|
||||
{ AllowedMethods: ["GET"] },
|
||||
{ AllowedHeaders: ["Range"] },
|
||||
{ ExposeHeaders: ["ETag"] },
|
||||
{ MaxAgeSeconds: 75 },
|
||||
{ ExtraField: "preserve" },
|
||||
]) {
|
||||
const existing = { ...defaults([second]), ...override };
|
||||
const s3 = transport([existing]);
|
||||
await ensure(s3, [second]);
|
||||
assert.deepEqual(s3.policy, { CORSRules: [existing, defaults([second])] });
|
||||
assert.equal(s3.puts, 1);
|
||||
}
|
||||
});
|
||||
|
||||
test("only uncovered origins receive a separate rule", async () => {
|
||||
const existing = [defaults([first]), custom];
|
||||
const s3 = transport(existing);
|
||||
await ensure(s3, [first, second, third]);
|
||||
assert.deepEqual(s3.policy, { CORSRules: [...existing, defaults([second, third])] });
|
||||
});
|
||||
|
||||
test("an explicit absent policy creates defaults and then skips repeated writes", async () => {
|
||||
const s3 = transport(null);
|
||||
await ensure(s3, [first]);
|
||||
assert.deepEqual(s3.policy, { CORSRules: [defaults([first])] });
|
||||
await ensure(s3, [first]);
|
||||
assert.equal(s3.puts, 1);
|
||||
});
|
||||
|
||||
test("all other policy read failures reject unchanged without PUT", async () => {
|
||||
for (const error of [Object.assign(new Error("Denied"), { name: "AccessDenied" }), Object.assign(new Error("Not found"), { name: "NoSuchBucket", $metadata: { httpStatusCode: 404 } }), new Error("Transport failed")]) {
|
||||
const s3 = transport([custom], error);
|
||||
await assert.rejects(ensure(s3, [second]), actual => actual === error);
|
||||
assert.deepEqual(s3.policy, { CORSRules: [custom] });
|
||||
assert.equal(s3.puts, 0);
|
||||
}
|
||||
});
|
||||
|
||||
test("empty or missing origins perform no IO and preserve policy", async () => {
|
||||
for (const origins of [[], undefined, [null, "", false]]) {
|
||||
const s3 = transport([custom]);
|
||||
await ensure(s3, origins);
|
||||
assert.deepEqual(s3.calls, []);
|
||||
assert.deepEqual(s3.policy, { CORSRules: [custom] });
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
# storage-cors-1
|
||||
- [x] Read rules/mailbox/memory/known issues/test integrity; claim current applicable item.
|
||||
- [x] Trace callers and reproduce offline loss at bbc1be79.
|
||||
- [x] Send demonstrated loss and proposed preservation semantics to dispatch.
|
||||
- [ ] Obtain fix routing; implement bounded regression and product change only after routing.
|
||||
- [ ] Run mutant/focused checks, coordinated full gates/build, commit/merge/normal queue and durable ancestry check.
|
||||
- Blocked: awaiting dispatch routing. PR overlap check unavailable: curl exit6 DNS resolution.
|
||||
@@ -0,0 +1,7 @@
|
||||
# storage-cors-1
|
||||
- [x] Offline loss reproduced and dispatch independently confirmed; fix routed.
|
||||
- [x] Preserve policy implementation2fb6a536; merged unstable0919be93.
|
||||
- [x]9 regression scenarios,10 restored mutants,focused3/3 files/13 scenarios; lint11/11 and build pass on merged tree.
|
||||
- [ ] Dispatch explicit full-suite/submit grant (client ahead).
|
||||
- [ ] Full normal gate; submit/durable landing+unstable ancestry; close backlog; dispatch final evidence.
|
||||
- Remaining scope: actual cloud/API permissions/serialization and concurrent policy writers unverified; no browser needed.
|
||||
Reference in new issue
Block a user