@sudobility/sider_lib 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gates.js +12 -11
- package/dist/gates.test.js +25 -2
- package/package.json +1 -1
- package/src/gates.test.ts +28 -2
- package/src/gates.ts +12 -11
package/dist/gates.js
CHANGED
|
@@ -44,18 +44,19 @@ export function evaluateGates(req, ctx) {
|
|
|
44
44
|
if (!b.ok)
|
|
45
45
|
return b;
|
|
46
46
|
}
|
|
47
|
-
// --- Gate C: mutations
|
|
48
|
-
// Bind to the HTTP method
|
|
49
|
-
//
|
|
50
|
-
//
|
|
47
|
+
// --- Gate C: mutations respect site policy; financial requires confirm ---
|
|
48
|
+
// Bind "is a mutation" to the HTTP method as well as the declared class (a
|
|
49
|
+
// recipe labeled "read" with a POST still mutates). Product decision
|
|
50
|
+
// 2026-07-22: only `financial` actions need user confirmation; ordinary
|
|
51
|
+
// writes (add-to-cart level) run automatically. `allowMutations` remains the
|
|
52
|
+
// per-site hard stop for ALL mutations.
|
|
51
53
|
const idempotent = req.method === "GET" || req.method === "HEAD" || req.method === "OPTIONS";
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
54
|
+
const mutating = req.safetyClass !== "read" || !idempotent;
|
|
55
|
+
if (mutating && ctx.allowMutations === false) {
|
|
56
|
+
return { ok: false, gate: "safety", reason: `mutations disabled for ${ctx.siteOrigin}` };
|
|
57
|
+
}
|
|
58
|
+
if (req.safetyClass === "financial" && !ctx.confirmed) {
|
|
59
|
+
return { ok: false, gate: "safety", reason: `financial request requires confirmation` };
|
|
59
60
|
}
|
|
60
61
|
return { ok: true };
|
|
61
62
|
}
|
package/dist/gates.test.js
CHANGED
|
@@ -64,8 +64,31 @@ test("Gate B: refuses when the sentinel is absent from its declared location", (
|
|
|
64
64
|
if (!out.ok)
|
|
65
65
|
expect(out.gate).toBe("injection_location");
|
|
66
66
|
});
|
|
67
|
-
test("Gate C: a POST
|
|
68
|
-
|
|
67
|
+
test("Gate C: a POST labeled read runs unconfirmed (financial-only policy)", () => {
|
|
68
|
+
expect(evaluateGates(req({ method: "POST", safetyClass: "read" }), ctx())).toEqual({ ok: true });
|
|
69
|
+
});
|
|
70
|
+
test("Gate C: write POST runs unconfirmed (financial-only policy)", () => {
|
|
71
|
+
const out = evaluateGates(req({ method: "POST", safetyClass: "write" }), ctx());
|
|
72
|
+
expect(out).toEqual({ ok: true });
|
|
73
|
+
});
|
|
74
|
+
test("Gate C: financial request without confirmation is blocked", () => {
|
|
75
|
+
const out = evaluateGates(req({ method: "POST", safetyClass: "financial" }), ctx());
|
|
76
|
+
expect(out.ok).toBe(false);
|
|
77
|
+
if (!out.ok)
|
|
78
|
+
expect(out.gate).toBe("safety");
|
|
79
|
+
});
|
|
80
|
+
test("Gate C: financial request with confirmation passes", () => {
|
|
81
|
+
const out = evaluateGates(req({ method: "POST", safetyClass: "financial" }), ctx({ confirmed: true }));
|
|
82
|
+
expect(out).toEqual({ ok: true });
|
|
83
|
+
});
|
|
84
|
+
test("Gate C: GET declared financial still requires confirmation", () => {
|
|
85
|
+
const out = evaluateGates(req({ safetyClass: "financial" }), ctx());
|
|
86
|
+
expect(out.ok).toBe(false);
|
|
87
|
+
if (!out.ok)
|
|
88
|
+
expect(out.gate).toBe("safety");
|
|
89
|
+
});
|
|
90
|
+
test("Gate C: allowMutations=false still blocks an unconfirmed write", () => {
|
|
91
|
+
const out = evaluateGates(req({ method: "POST", safetyClass: "write" }), ctx({ allowMutations: false }));
|
|
69
92
|
expect(out.ok).toBe(false);
|
|
70
93
|
if (!out.ok)
|
|
71
94
|
expect(out.gate).toBe("safety");
|
package/package.json
CHANGED
package/src/gates.test.ts
CHANGED
|
@@ -74,8 +74,34 @@ test("Gate B: refuses when the sentinel is absent from its declared location", (
|
|
|
74
74
|
if (!out.ok) expect(out.gate).toBe("injection_location");
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
test("Gate C: a POST
|
|
78
|
-
|
|
77
|
+
test("Gate C: a POST labeled read runs unconfirmed (financial-only policy)", () => {
|
|
78
|
+
expect(evaluateGates(req({ method: "POST", safetyClass: "read" }), ctx())).toEqual({ ok: true });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("Gate C: write POST runs unconfirmed (financial-only policy)", () => {
|
|
82
|
+
const out = evaluateGates(req({ method: "POST", safetyClass: "write" }), ctx());
|
|
83
|
+
expect(out).toEqual({ ok: true });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("Gate C: financial request without confirmation is blocked", () => {
|
|
87
|
+
const out = evaluateGates(req({ method: "POST", safetyClass: "financial" }), ctx());
|
|
88
|
+
expect(out.ok).toBe(false);
|
|
89
|
+
if (!out.ok) expect(out.gate).toBe("safety");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("Gate C: financial request with confirmation passes", () => {
|
|
93
|
+
const out = evaluateGates(req({ method: "POST", safetyClass: "financial" }), ctx({ confirmed: true }));
|
|
94
|
+
expect(out).toEqual({ ok: true });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("Gate C: GET declared financial still requires confirmation", () => {
|
|
98
|
+
const out = evaluateGates(req({ safetyClass: "financial" }), ctx());
|
|
99
|
+
expect(out.ok).toBe(false);
|
|
100
|
+
if (!out.ok) expect(out.gate).toBe("safety");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("Gate C: allowMutations=false still blocks an unconfirmed write", () => {
|
|
104
|
+
const out = evaluateGates(req({ method: "POST", safetyClass: "write" }), ctx({ allowMutations: false }));
|
|
79
105
|
expect(out.ok).toBe(false);
|
|
80
106
|
if (!out.ok) expect(out.gate).toBe("safety");
|
|
81
107
|
});
|
package/src/gates.ts
CHANGED
|
@@ -59,18 +59,19 @@ export function evaluateGates(req: CompiledRequest, ctx: GateContext): GateOutco
|
|
|
59
59
|
if (!b.ok) return b;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
// --- Gate C: mutations
|
|
63
|
-
// Bind to the HTTP method
|
|
64
|
-
//
|
|
65
|
-
//
|
|
62
|
+
// --- Gate C: mutations respect site policy; financial requires confirm ---
|
|
63
|
+
// Bind "is a mutation" to the HTTP method as well as the declared class (a
|
|
64
|
+
// recipe labeled "read" with a POST still mutates). Product decision
|
|
65
|
+
// 2026-07-22: only `financial` actions need user confirmation; ordinary
|
|
66
|
+
// writes (add-to-cart level) run automatically. `allowMutations` remains the
|
|
67
|
+
// per-site hard stop for ALL mutations.
|
|
66
68
|
const idempotent = req.method === "GET" || req.method === "HEAD" || req.method === "OPTIONS";
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
69
|
+
const mutating = req.safetyClass !== "read" || !idempotent;
|
|
70
|
+
if (mutating && ctx.allowMutations === false) {
|
|
71
|
+
return { ok: false, gate: "safety", reason: `mutations disabled for ${ctx.siteOrigin}` };
|
|
72
|
+
}
|
|
73
|
+
if (req.safetyClass === "financial" && !ctx.confirmed) {
|
|
74
|
+
return { ok: false, gate: "safety", reason: `financial request requires confirmation` };
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
return { ok: true };
|