@smartmemory/stratum 0.3.3 → 0.4.0
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/cli/guard.js +63 -6
- package/dist/cli/guard.js.map +1 -1
- package/dist/cli/learn.js +226 -0
- package/dist/cli/learn.js.map +1 -0
- package/dist/cli/query_gate.js +2 -1
- package/dist/cli/query_gate.js.map +1 -1
- package/dist/cli/stratum.js +5 -2
- package/dist/cli/stratum.js.map +1 -1
- package/dist/connectors/background.js +58 -10
- package/dist/connectors/background.js.map +1 -1
- package/dist/connectors/base.js +16 -0
- package/dist/connectors/base.js.map +1 -1
- package/dist/connectors/cancellation.js +119 -0
- package/dist/connectors/cancellation.js.map +1 -0
- package/dist/connectors/claude-bg-worker.js +9 -1
- package/dist/connectors/claude-bg-worker.js.map +1 -1
- package/dist/connectors/claude.js +179 -86
- package/dist/connectors/claude.js.map +1 -1
- package/dist/connectors/codex.js +289 -61
- package/dist/connectors/codex.js.map +1 -1
- package/dist/connectors/runner.js +44 -3
- package/dist/connectors/runner.js.map +1 -1
- package/dist/contracts/events.json +34 -1
- package/dist/contracts/guard-signers.allowed +36 -0
- package/dist/contracts/mcp-surface.json +1097 -104
- package/dist/engine/checkpoint.js +8 -1
- package/dist/engine/checkpoint.js.map +1 -1
- package/dist/engine/engine.js +508 -42
- package/dist/engine/engine.js.map +1 -1
- package/dist/engine/evaluate.js +61 -0
- package/dist/engine/evaluate.js.map +1 -0
- package/dist/engine/ledger.js +12 -0
- package/dist/engine/ledger.js.map +1 -1
- package/dist/engine/receipts.js +91 -0
- package/dist/engine/receipts.js.map +1 -0
- package/dist/engine/state.js +51 -1
- package/dist/engine/state.js.map +1 -1
- package/dist/guard/authorization.js +71 -0
- package/dist/guard/authorization.js.map +1 -0
- package/dist/guard/descriptors.js +248 -0
- package/dist/guard/descriptors.js.map +1 -0
- package/dist/guard/errors.js +12 -0
- package/dist/guard/errors.js.map +1 -1
- package/dist/guard/evidence.js +24 -3
- package/dist/guard/evidence.js.map +1 -1
- package/dist/guard/sshsig.js +264 -0
- package/dist/guard/sshsig.js.map +1 -0
- package/dist/guard/store.js +13 -0
- package/dist/guard/store.js.map +1 -1
- package/dist/guard/transition.js +484 -33
- package/dist/guard/transition.js.map +1 -1
- package/dist/guard/trust.js +78 -0
- package/dist/guard/trust.js.map +1 -0
- package/dist/ir/schema.js +8 -1
- package/dist/ir/schema.js.map +1 -1
- package/dist/ir/validate.js +3 -1
- package/dist/ir/validate.js.map +1 -1
- package/dist/judge/judged.js +1 -1
- package/dist/judge/pricing.js +1 -0
- package/dist/judge/pricing.js.map +1 -1
- package/dist/learn/apply.js +600 -0
- package/dist/learn/apply.js.map +1 -0
- package/dist/learn/candidate.js +186 -0
- package/dist/learn/candidate.js.map +1 -0
- package/dist/learn/classify.js +181 -0
- package/dist/learn/classify.js.map +1 -0
- package/dist/learn/harvest.js +138 -0
- package/dist/learn/harvest.js.map +1 -0
- package/dist/learn/smartmemory_egress.js +330 -0
- package/dist/learn/smartmemory_egress.js.map +1 -0
- package/dist/mcp/server.js +155 -24
- package/dist/mcp/server.js.map +1 -1
- package/dist/policy/bundle.js +195 -0
- package/dist/policy/bundle.js.map +1 -0
- package/dist/policy/events.js +51 -0
- package/dist/policy/events.js.map +1 -0
- package/dist/policy/smartmemory_client.js +332 -0
- package/dist/policy/smartmemory_client.js.map +1 -0
- package/dist/policy/types.js +2 -0
- package/dist/policy/types.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Default `EvaluateRunner`: runs the declared command through `/bin/sh -c`,
|
|
4
|
+
* hands the bound input to it as JSON on stdin, and reads a JSON verdict from
|
|
5
|
+
* stdout. It only classifies the *transport* outcome — exit / timeout / parse.
|
|
6
|
+
* The engine owns the trust schema, so this never fabricates a verdict.
|
|
7
|
+
*/
|
|
8
|
+
export function createEvaluateRunner() {
|
|
9
|
+
return ({ command, input, timeoutMs }, context) => new Promise((resolve) => {
|
|
10
|
+
// `detached` makes the shell its own process-group leader, so a timeout
|
|
11
|
+
// can SIGKILL the whole tree (`-pid`) — killing only the shell would
|
|
12
|
+
// orphan grandchildren it backgrounded, leaking work and stdio handles.
|
|
13
|
+
const child = spawn("/bin/sh", ["-c", command], {
|
|
14
|
+
...(context.workspaceRoot !== undefined ? { cwd: context.workspaceRoot } : {}),
|
|
15
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
16
|
+
detached: true,
|
|
17
|
+
});
|
|
18
|
+
let stdout = "";
|
|
19
|
+
let stderr = "";
|
|
20
|
+
let settled = false;
|
|
21
|
+
const finish = (result) => {
|
|
22
|
+
if (settled)
|
|
23
|
+
return;
|
|
24
|
+
settled = true;
|
|
25
|
+
clearTimeout(timer);
|
|
26
|
+
resolve(result);
|
|
27
|
+
};
|
|
28
|
+
const timer = setTimeout(() => {
|
|
29
|
+
// Kill the process group, not just the shell, so backgrounded children die too.
|
|
30
|
+
try {
|
|
31
|
+
if (child.pid !== undefined)
|
|
32
|
+
process.kill(-child.pid, "SIGKILL");
|
|
33
|
+
}
|
|
34
|
+
catch { /* already exited */ }
|
|
35
|
+
finish({ ok: false, kind: "timeout", reason: `command exceeded ${timeoutMs}ms` });
|
|
36
|
+
}, timeoutMs);
|
|
37
|
+
child.stdout.on("data", (chunk) => { stdout += String(chunk); });
|
|
38
|
+
child.stderr.on("data", (chunk) => { stderr += String(chunk); });
|
|
39
|
+
child.on("error", (error) => finish({ ok: false, kind: "exit", reason: error.message }));
|
|
40
|
+
child.on("close", (code) => {
|
|
41
|
+
if (code !== 0) {
|
|
42
|
+
finish({ ok: false, kind: "exit", reason: `exit code ${String(code)}${stderr ? `: ${stderr.trim()}` : ""}` });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
finish({ ok: true, result: JSON.parse(stdout) });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
finish({ ok: false, kind: "parse", reason: error instanceof Error ? error.message : String(error) });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
// A command that never reads stdin (and has already exited) turns this
|
|
53
|
+
// write into an EPIPE on the pipe; swallow it — the close/error handlers
|
|
54
|
+
// above carry the real outcome. Left unhandled it crashes the process.
|
|
55
|
+
child.stdin.on("error", () => { });
|
|
56
|
+
if (input !== undefined)
|
|
57
|
+
child.stdin.write(JSON.stringify(input));
|
|
58
|
+
child.stdin.end();
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../../src/engine/evaluate.ts"],"names":[],"mappings":"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"}
|
package/dist/engine/ledger.js
CHANGED
|
@@ -33,4 +33,16 @@ export function validUsage(value) {
|
|
|
33
33
|
return false;
|
|
34
34
|
return Object.entries(value).every(([key, amount]) => BUDGET_KEYS.includes(key) && typeof amount === "number" && Number.isFinite(amount) && amount >= 0);
|
|
35
35
|
}
|
|
36
|
+
export function validConnectorTelemetry(value) {
|
|
37
|
+
if (value === undefined)
|
|
38
|
+
return true;
|
|
39
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
40
|
+
return false;
|
|
41
|
+
const telemetry = value;
|
|
42
|
+
if (Object.keys(telemetry).some((key) => !["durationMs", "model", "effort"].includes(key)))
|
|
43
|
+
return false;
|
|
44
|
+
return typeof telemetry.durationMs === "number" && Number.isFinite(telemetry.durationMs) && telemetry.durationMs >= 0
|
|
45
|
+
&& typeof telemetry.model === "string" && telemetry.model.length > 0
|
|
46
|
+
&& (telemetry.effort === undefined || (typeof telemetry.effort === "string" && telemetry.effort.length > 0));
|
|
47
|
+
}
|
|
36
48
|
//# sourceMappingURL=ledger.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../src/engine/ledger.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ledger.js","sourceRoot":"","sources":["../../src/engine/ledger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { BUDGET_KEYS, validConnectorTelemetry, validUsage } from "./ledger.js";
|
|
2
|
+
export class ReceiptValidationError extends Error {
|
|
3
|
+
errorType;
|
|
4
|
+
constructor(message, errorType = "invalid_receipt") {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "ReceiptValidationError";
|
|
7
|
+
this.errorType = errorType;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function buildReceipt(run, input) {
|
|
11
|
+
if (typeof input !== "object" || input === null || Array.isArray(input))
|
|
12
|
+
invalid("receipt must be an object");
|
|
13
|
+
if (typeof input.dispatchId !== "string" || input.dispatchId.length === 0)
|
|
14
|
+
invalid("dispatchId must be a non-empty string");
|
|
15
|
+
if (input.stepId !== undefined && (typeof input.stepId !== "string" || input.stepId.length === 0)) {
|
|
16
|
+
invalid("stepId must be a non-empty string when present");
|
|
17
|
+
}
|
|
18
|
+
if (typeof input.source !== "string" || input.source.length === 0)
|
|
19
|
+
invalid("source must be a non-empty string");
|
|
20
|
+
if (!validUsage(input.usage))
|
|
21
|
+
invalid("usage must be a valid non-negative budget object");
|
|
22
|
+
if (Object.hasOwn(input.usage, "dispatches"))
|
|
23
|
+
invalid("dispatches are engine-accounted and cannot appear in a receipt");
|
|
24
|
+
if (!validConnectorTelemetry(input.telemetry))
|
|
25
|
+
invalid("telemetry must contain a non-empty model and non-negative durationMs");
|
|
26
|
+
if (input.usage.usd !== undefined && input.usdSource === undefined)
|
|
27
|
+
invalid("usdSource is required when usage.usd is present");
|
|
28
|
+
if (input.usdSource !== undefined && !["reported", "estimated", "legacy"].includes(input.usdSource)) {
|
|
29
|
+
invalid('usdSource must be "reported", "estimated", or "legacy"');
|
|
30
|
+
}
|
|
31
|
+
if (input.at !== undefined && typeof input.at !== "string")
|
|
32
|
+
invalid("at must be a string when present");
|
|
33
|
+
if (input.split !== undefined && !validSplit(input.split))
|
|
34
|
+
invalid("split must contain non-negative input/output token counts");
|
|
35
|
+
if (input.detail !== undefined && !isPlainObject(input.detail))
|
|
36
|
+
invalid("detail must be a plain object when present");
|
|
37
|
+
const telemetry = input.telemetry ?? { model: "unknown", durationMs: 0 };
|
|
38
|
+
const seq = (run.receiptCounter ?? 0) + 1;
|
|
39
|
+
run.receiptCounter = seq;
|
|
40
|
+
return {
|
|
41
|
+
seq,
|
|
42
|
+
dispatchId: input.dispatchId,
|
|
43
|
+
...(input.stepId !== undefined ? { stepId: input.stepId } : {}),
|
|
44
|
+
source: input.source,
|
|
45
|
+
amount: { ...input.usage },
|
|
46
|
+
telemetry: { ...telemetry },
|
|
47
|
+
...(input.split !== undefined ? { split: { ...input.split } } : {}),
|
|
48
|
+
...(input.usdSource !== undefined ? { usdSource: input.usdSource } : {}),
|
|
49
|
+
...(input.at !== undefined ? { reportedAt: input.at } : {}),
|
|
50
|
+
...(input.detail !== undefined ? { detail: { ...input.detail } } : {}),
|
|
51
|
+
at: new Date().toISOString(),
|
|
52
|
+
egress: "pending",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function findReceipt(run, dispatchId) {
|
|
56
|
+
return run.receipts?.find((receipt) => receipt.dispatchId === dispatchId);
|
|
57
|
+
}
|
|
58
|
+
export function spineSpent(run) {
|
|
59
|
+
const spent = {};
|
|
60
|
+
for (const receipt of run.receipts ?? []) {
|
|
61
|
+
for (const key of BUDGET_KEYS) {
|
|
62
|
+
const amount = receipt.amount[key];
|
|
63
|
+
if (amount !== undefined && amount !== 0)
|
|
64
|
+
spent[key] = (spent[key] ?? 0) + amount;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return spent;
|
|
68
|
+
}
|
|
69
|
+
function isPlainObject(value) {
|
|
70
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
71
|
+
return false;
|
|
72
|
+
const prototype = Object.getPrototypeOf(value);
|
|
73
|
+
return prototype === Object.prototype || prototype === null;
|
|
74
|
+
}
|
|
75
|
+
function validSplit(value) {
|
|
76
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
77
|
+
return false;
|
|
78
|
+
const split = value;
|
|
79
|
+
if (Object.keys(split).some((key) => !["input", "output", "cacheRead", "cacheCreation"].includes(key)))
|
|
80
|
+
return false;
|
|
81
|
+
return nonNegative(split.input) && nonNegative(split.output)
|
|
82
|
+
&& (split.cacheRead === undefined || nonNegative(split.cacheRead))
|
|
83
|
+
&& (split.cacheCreation === undefined || nonNegative(split.cacheCreation));
|
|
84
|
+
}
|
|
85
|
+
function nonNegative(value) {
|
|
86
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
87
|
+
}
|
|
88
|
+
function invalid(message) {
|
|
89
|
+
throw new ReceiptValidationError(message);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=receipts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"receipts.js","sourceRoot":"","sources":["../../src/engine/receipts.ts"],"names":[],"mappings":"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"}
|
package/dist/engine/state.js
CHANGED
|
@@ -16,7 +16,9 @@ export class StateStore {
|
|
|
16
16
|
await rename(temporary, path);
|
|
17
17
|
}
|
|
18
18
|
async load(runId) {
|
|
19
|
-
|
|
19
|
+
const run = JSON.parse(await readFile(this.path(runId), "utf8"));
|
|
20
|
+
migrateLegacyPolicyRules(run);
|
|
21
|
+
return run;
|
|
20
22
|
}
|
|
21
23
|
async list() {
|
|
22
24
|
let names;
|
|
@@ -36,6 +38,54 @@ export class StateStore {
|
|
|
36
38
|
return join(this.root, `${runId}.json`);
|
|
37
39
|
}
|
|
38
40
|
}
|
|
41
|
+
function migrateLegacyPolicyRules(run) {
|
|
42
|
+
if (run.policy_rules === undefined || run.policy_rules_version === 2)
|
|
43
|
+
return;
|
|
44
|
+
const scoped = new Map();
|
|
45
|
+
const flows = specificationFlows(run.spec);
|
|
46
|
+
for (const [flowName, stepIds] of flows) {
|
|
47
|
+
for (const stepId of stepIds)
|
|
48
|
+
scoped.set(`${flowName}/${stepId}`, `${flowName}/${stepId}`);
|
|
49
|
+
}
|
|
50
|
+
const migrated = {};
|
|
51
|
+
for (const [legacyKey, bindings] of Object.entries(run.policy_rules)) {
|
|
52
|
+
let target = scoped.get(legacyKey);
|
|
53
|
+
if (target === undefined) {
|
|
54
|
+
const matches = flows.filter(([, stepIds]) => stepIds.has(legacyKey)).map(([flowName]) => flowName);
|
|
55
|
+
if (matches.length !== 1) {
|
|
56
|
+
const detail = matches.length === 0
|
|
57
|
+
? "does not exist in any flow"
|
|
58
|
+
: `is ambiguous across flows ${matches.map((name) => JSON.stringify(name)).join(", ")}`;
|
|
59
|
+
throw new Error(`run ${run.id}: legacy policy_rules step ${JSON.stringify(legacyKey)} ${detail}; re-plan required`);
|
|
60
|
+
}
|
|
61
|
+
target = `${matches[0]}/${legacyKey}`;
|
|
62
|
+
}
|
|
63
|
+
(migrated[target] ??= []).push(...bindings);
|
|
64
|
+
}
|
|
65
|
+
run.policy_rules = migrated;
|
|
66
|
+
run.policy_rules_version = 2;
|
|
67
|
+
}
|
|
68
|
+
function specificationFlows(spec) {
|
|
69
|
+
if (spec === null || typeof spec !== "object" || Array.isArray(spec))
|
|
70
|
+
return [];
|
|
71
|
+
const flows = spec.flows;
|
|
72
|
+
if (flows === null || typeof flows !== "object" || Array.isArray(flows))
|
|
73
|
+
return [];
|
|
74
|
+
return Object.entries(flows).flatMap(([flowName, flow]) => {
|
|
75
|
+
if (flowName === "entry" || flow === null || typeof flow !== "object" || Array.isArray(flow))
|
|
76
|
+
return [];
|
|
77
|
+
const steps = flow.steps;
|
|
78
|
+
if (!Array.isArray(steps))
|
|
79
|
+
return [];
|
|
80
|
+
const ids = steps.flatMap((step) => {
|
|
81
|
+
if (step === null || typeof step !== "object" || Array.isArray(step))
|
|
82
|
+
return [];
|
|
83
|
+
const id = step.id;
|
|
84
|
+
return typeof id === "string" ? [id] : [];
|
|
85
|
+
});
|
|
86
|
+
return [[flowName, new Set(ids)]];
|
|
87
|
+
});
|
|
88
|
+
}
|
|
39
89
|
function isNotFound(error) {
|
|
40
90
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
41
91
|
}
|
package/dist/engine/state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/engine/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/engine/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAuO1C,IAAI,iBAAiB,GAAG,CAAC,CAAC;AAE1B,MAAM,OAAO,UAAU;IACZ,IAAI,CAAS;IAEtB,YAAY,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,CAAC;QAC3D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAiB;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,oFAAoF;QACpF,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,iBAAiB,EAAE,MAAM,CAAC;QACtE,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACjE,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,KAAa;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,CAAiB,CAAC;QACjF,wBAAwB,CAAC,GAAG,CAAC,CAAC;QAC9B,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,KAAe,CAAC;QACpB,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,UAAU,CAAC,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YACjC,MAAM,KAAK,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7G,CAAC;IAEO,IAAI,CAAC,KAAa;QACxB,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACtE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC;IAC1C,CAAC;CACF;AAED,SAAS,wBAAwB,CAAC,GAAiB;IACjD,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS,IAAI,GAAG,CAAC,oBAAoB,KAAK,CAAC;QAAE,OAAO;IAC7E,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3C,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,KAAK,EAAE,CAAC;QACxC,KAAK,MAAM,MAAM,IAAI,OAAO;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,MAAM,EAAE,EAAE,GAAG,QAAQ,IAAI,MAAM,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,QAAQ,GAAkB,EAAE,CAAC;IACnC,KAAK,MAAM,CAAC,SAAS,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;QACrE,IAAI,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;YACpG,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACzB,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC;oBACjC,CAAC,CAAC,4BAA4B;oBAC9B,CAAC,CAAC,6BAA6B,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1F,MAAM,IAAI,KAAK,CAAC,OAAO,GAAG,CAAC,EAAE,8BAA8B,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,MAAM,oBAAoB,CAAC,CAAC;YACtH,CAAC;YACD,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAE,IAAI,SAAS,EAAE,CAAC;QACzC,CAAC;QACD,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC;IAC9C,CAAC;IACD,GAAG,CAAC,YAAY,GAAG,QAAQ,CAAC;IAC5B,GAAG,CAAC,oBAAoB,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAa;IACvC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAChF,MAAM,KAAK,GAAI,IAA4B,CAAC,KAAK,CAAC;IAClD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACnF,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAgC,EAAE;QACtF,IAAI,QAAQ,KAAK,OAAO,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACxG,MAAM,KAAK,GAAI,IAA4B,CAAC,KAAK,CAAC;QAClD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YACjC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;gBAAE,OAAO,EAAE,CAAC;YAChF,MAAM,EAAE,GAAI,IAAyB,CAAC,EAAE,CAAC;YACzC,OAAO,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,CAAC,QAAQ,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC;AACnG,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signed one-shot authorizations for the two privileged guard operations that a
|
|
3
|
+
* reviewed descriptor cannot express: `guardOverride` (bypass predicates on one
|
|
4
|
+
* edge) and `guardMigrate` (arbitrary policy change, the emergency path).
|
|
5
|
+
*
|
|
6
|
+
* Replaces `STRATUM_GUARD_OVERRIDE_TOKEN`, which was a shared secret compared
|
|
7
|
+
* against the environment of whatever process happened to be running — so over
|
|
8
|
+
* the CLI a caller set both sides of the comparison and they matched. Verified
|
|
9
|
+
* empirically before removal: an honest transition was refused, and the same walk
|
|
10
|
+
* with a self-invented token returned `deviation` and moved the state.
|
|
11
|
+
*
|
|
12
|
+
* The replacement is a signature over a payload the SERVER reconstructs, so
|
|
13
|
+
* nothing about the authorization travels except the signature itself. Two
|
|
14
|
+
* properties fall out of that:
|
|
15
|
+
*
|
|
16
|
+
* - **It cannot be forged**, because the signing key's passphrase lives only in
|
|
17
|
+
* the operator's head (see `contracts/guard-signers.allowed`).
|
|
18
|
+
* - **It cannot be replayed**, because the payload includes the resource's
|
|
19
|
+
* current ledger head. An authorization is valid at exactly one point in that
|
|
20
|
+
* resource's history; once it is used, the head has moved and the same
|
|
21
|
+
* signature is dead. No clock, no nonce store, no expiry to tune.
|
|
22
|
+
*/
|
|
23
|
+
import { canonicalJson } from "./canonical.js";
|
|
24
|
+
import { OverrideUnavailable } from "./errors.js";
|
|
25
|
+
import { sshFingerprint, verifySshsig } from "./sshsig.js";
|
|
26
|
+
import { loadAllowedSigners } from "./trust.js";
|
|
27
|
+
/** One namespace per purpose, so an authorization for one can never act as another. */
|
|
28
|
+
export const AUTHORIZATION_NAMESPACES = {
|
|
29
|
+
override: "stratum-guard-override",
|
|
30
|
+
migrate: "stratum-guard-migrate",
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* The exact bytes an operator signs. Canonical JSON so the operator's tooling
|
|
34
|
+
* and the server agree byte-for-byte without either transporting the payload.
|
|
35
|
+
*/
|
|
36
|
+
export function authorizationPayload(kind, fields) {
|
|
37
|
+
return canonicalJson({ action: kind, ...fields });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Verify `armored` authorizes exactly this operation on this resource at this
|
|
41
|
+
* point in its history. Throws `OverrideUnavailable` on every failure — the same
|
|
42
|
+
* slug the token check used, so callers and the error contract are unchanged.
|
|
43
|
+
*/
|
|
44
|
+
export function verifyAuthorization(kind, fields, armored) {
|
|
45
|
+
if (!armored || !armored.trim()) {
|
|
46
|
+
throw new OverrideUnavailable(`${kind} requires a signed authorization (see "stratum guard authorize")`);
|
|
47
|
+
}
|
|
48
|
+
let signers;
|
|
49
|
+
try {
|
|
50
|
+
signers = loadAllowedSigners();
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new OverrideUnavailable(error instanceof Error ? error.message : String(error));
|
|
54
|
+
}
|
|
55
|
+
const payload = Buffer.from(authorizationPayload(kind, fields), "utf8");
|
|
56
|
+
let key;
|
|
57
|
+
try {
|
|
58
|
+
key = verifySshsig(payload, armored, AUTHORIZATION_NAMESPACES[kind], signers.map((signer) => signer.publicKey));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// The payload is echoed back because the most common honest failure is a
|
|
62
|
+
// stale ledger_head — the operator signed, the resource moved, and they need
|
|
63
|
+
// to see what the server expected in order to re-sign.
|
|
64
|
+
throw new OverrideUnavailable(`${kind} authorization rejected: ${error instanceof Error ? error.message : String(error)}; `
|
|
65
|
+
+ `expected a signature over ${authorizationPayload(kind, fields)}`);
|
|
66
|
+
}
|
|
67
|
+
const fingerprint = sshFingerprint(key);
|
|
68
|
+
const signer = signers.find((entry) => entry.fingerprint === fingerprint);
|
|
69
|
+
return { principal: signer.principal, fingerprint };
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=authorization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorization.js","sourceRoot":"","sources":["../../src/guard/authorization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhD,uFAAuF;AACvF,MAAM,CAAC,MAAM,wBAAwB,GAAG;IACtC,QAAQ,EAAE,wBAAwB;IAClC,OAAO,EAAE,uBAAuB;CACxB,CAAC;AAqBX;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAuB,EAAE,MAA2B;IACvF,OAAO,aAAa,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;AACpD,CAAC;AAID;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CACjC,IAAuB,EACvB,MAA2B,EAC3B,OAAe;IAEf,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,MAAM,IAAI,mBAAmB,CAC3B,GAAG,IAAI,kEAAkE,CAC1E,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACjC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,mBAAmB,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC;IACxE,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,wBAAwB,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IAClH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,yEAAyE;QACzE,6EAA6E;QAC7E,uDAAuD;QACvD,MAAM,IAAI,mBAAmB,CAC3B,GAAG,IAAI,4BAA4B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI;cAC3F,6BAA6B,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CACpE,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,KAAK,WAAW,CAAE,CAAC;IAC3E,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,WAAW,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-owned guard upgrade descriptors — the authorization primitive that
|
|
3
|
+
* lets a pre-reviewed policy change be applied without the break-glass override
|
|
4
|
+
* token. See `docs/features/STRAT-GUARD-DESCRIPTOR/design.md`.
|
|
5
|
+
*
|
|
6
|
+
* Authorization is a SIGNATURE, not a value in the environment. The descriptor
|
|
7
|
+
* file must carry an sshsig (`ssh-keygen -Y sign`) from a key listed in the
|
|
8
|
+
* in-source trust root `contracts/guard-signers.allowed`, under the namespace
|
|
9
|
+
* `stratum-guard-descriptors`.
|
|
10
|
+
*
|
|
11
|
+
* Why a signature and not the digest pin this replaced: the threat model is an
|
|
12
|
+
* agent with a shell and the operator's filesystem privileges. Such an agent can
|
|
13
|
+
* set any environment variable, so an env-anchored digest is only as strong as
|
|
14
|
+
* the process that reads it — over a CLI, the caller sets both the file and the
|
|
15
|
+
* pin and they agree. It cannot, however, produce an Ed25519 signature without
|
|
16
|
+
* the private key, whose passphrase lives only in the operator's head. The
|
|
17
|
+
* remaining move is to edit the committed trust root, which is loud and shows up
|
|
18
|
+
* in review rather than being invisible.
|
|
19
|
+
*
|
|
20
|
+
* The descriptor file's PATH still comes from the environment. Locating an
|
|
21
|
+
* artifact is not authorizing it: point this anywhere, the signature still has
|
|
22
|
+
* to verify.
|
|
23
|
+
*/
|
|
24
|
+
import { createHash } from "node:crypto";
|
|
25
|
+
import { readFileSync, statSync } from "node:fs";
|
|
26
|
+
import { isAbsolute } from "node:path";
|
|
27
|
+
import { UpgradeDescriptorUnavailable } from "./errors.js";
|
|
28
|
+
import { SshsigError, sshFingerprint, verifySshsig } from "./sshsig.js";
|
|
29
|
+
import { loadAllowedSigners } from "./trust.js";
|
|
30
|
+
export const DESCRIPTOR_PATH_ENV = "STRATUM_GUARD_UPGRADE_DESCRIPTORS";
|
|
31
|
+
/** sshsig namespace for descriptor-file signatures; distinct per authorization purpose. */
|
|
32
|
+
export const DESCRIPTOR_NAMESPACE = "stratum-guard-descriptors";
|
|
33
|
+
export { setGuardTrustRootForTests } from "./trust.js";
|
|
34
|
+
const DESCRIPTOR_ID_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
35
|
+
const DESCRIPTOR_KEYS = new Set(["id", "rationale", "from_checksum", "to_policy"]);
|
|
36
|
+
const POLICY_KEYS = new Set(["graph", "edge_predicates", "terminal", "stakes"]);
|
|
37
|
+
const FILE_KEYS = new Set(["version", "descriptors"]);
|
|
38
|
+
const CHECKSUM_PATTERN = /^[0-9a-f]{64}$/;
|
|
39
|
+
/** Path of the detached signature for a descriptor file. */
|
|
40
|
+
export function signaturePathFor(descriptorPathValue) {
|
|
41
|
+
return `${descriptorPathValue}.sig`;
|
|
42
|
+
}
|
|
43
|
+
function isRecord(value) {
|
|
44
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
46
|
+
function unavailable(message) {
|
|
47
|
+
throw new UpgradeDescriptorUnavailable(message);
|
|
48
|
+
}
|
|
49
|
+
function assertOnlyKeys(value, allowed, where) {
|
|
50
|
+
// Rejected, not ignored: a typo'd key in an authorization artifact must never
|
|
51
|
+
// silently mean something other than what the reviewer read.
|
|
52
|
+
for (const key of Object.keys(value)) {
|
|
53
|
+
if (!allowed.has(key))
|
|
54
|
+
unavailable(`${where} has unknown key ${JSON.stringify(key)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function parsePolicy(value, where) {
|
|
58
|
+
if (!isRecord(value))
|
|
59
|
+
unavailable(`${where} must be an object`);
|
|
60
|
+
assertOnlyKeys(value, POLICY_KEYS, where);
|
|
61
|
+
for (const key of POLICY_KEYS) {
|
|
62
|
+
if (!Object.hasOwn(value, key))
|
|
63
|
+
unavailable(`${where} is missing ${JSON.stringify(key)}`);
|
|
64
|
+
}
|
|
65
|
+
if (!isRecord(value.graph))
|
|
66
|
+
unavailable(`${where}.graph must be an object`);
|
|
67
|
+
if (!isRecord(value.edge_predicates))
|
|
68
|
+
unavailable(`${where}.edge_predicates must be an object`);
|
|
69
|
+
if (!Array.isArray(value.terminal))
|
|
70
|
+
unavailable(`${where}.terminal must be an array`);
|
|
71
|
+
if (!isRecord(value.stakes))
|
|
72
|
+
unavailable(`${where}.stakes must be an object`);
|
|
73
|
+
// Field-level shape (arrays of state names, predicate objects, string stakes)
|
|
74
|
+
// is enforced by _validatePolicy at apply time — authorized is not the same
|
|
75
|
+
// as well-formed, and that check is shared with register/migrate/upgrade.
|
|
76
|
+
return {
|
|
77
|
+
graph: value.graph,
|
|
78
|
+
edge_predicates: value.edge_predicates,
|
|
79
|
+
terminal: value.terminal,
|
|
80
|
+
stakes: value.stakes,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function parseDescriptor(value, index) {
|
|
84
|
+
const where = `descriptors[${index}]`;
|
|
85
|
+
if (!isRecord(value))
|
|
86
|
+
unavailable(`${where} must be an object`);
|
|
87
|
+
assertOnlyKeys(value, DESCRIPTOR_KEYS, where);
|
|
88
|
+
for (const key of DESCRIPTOR_KEYS) {
|
|
89
|
+
if (!Object.hasOwn(value, key))
|
|
90
|
+
unavailable(`${where} is missing ${JSON.stringify(key)}`);
|
|
91
|
+
}
|
|
92
|
+
const { id, rationale, from_checksum: fromChecksum } = value;
|
|
93
|
+
if (typeof id !== "string" || !DESCRIPTOR_ID_PATTERN.test(id)) {
|
|
94
|
+
unavailable(`${where}.id must match [A-Za-z0-9_.-]+`);
|
|
95
|
+
}
|
|
96
|
+
if (typeof rationale !== "string" || !rationale.trim()) {
|
|
97
|
+
// The ledger carries this, so the audit trail says why and not only what.
|
|
98
|
+
unavailable(`${where}.rationale must be a non-empty string`);
|
|
99
|
+
}
|
|
100
|
+
if (typeof fromChecksum !== "string" || !CHECKSUM_PATTERN.test(fromChecksum)) {
|
|
101
|
+
unavailable(`${where}.from_checksum must be a lowercase sha256 hex digest`);
|
|
102
|
+
}
|
|
103
|
+
return { id, rationale, from_checksum: fromChecksum, to_policy: parsePolicy(value.to_policy, `${where}.to_policy`) };
|
|
104
|
+
}
|
|
105
|
+
/** Digest of a descriptor file's exact bytes — what the env pin is compared against. */
|
|
106
|
+
export function descriptorFileDigest(bytes) {
|
|
107
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
108
|
+
}
|
|
109
|
+
/** Trust-root failures are "you do not have this capability" at this surface. */
|
|
110
|
+
function allowedSignersOrUnavailable() {
|
|
111
|
+
try {
|
|
112
|
+
return loadAllowedSigners();
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
unavailable(error instanceof Error ? error.message : String(error));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function descriptorPath(env) {
|
|
119
|
+
const path = env[DESCRIPTOR_PATH_ENV];
|
|
120
|
+
if (!path)
|
|
121
|
+
unavailable(`upgrade descriptors unavailable: ${DESCRIPTOR_PATH_ENV} not set in server env`);
|
|
122
|
+
if (!isAbsolute(path))
|
|
123
|
+
unavailable(`${DESCRIPTOR_PATH_ENV} must be an absolute path`);
|
|
124
|
+
return path;
|
|
125
|
+
}
|
|
126
|
+
function readDescriptorBytes(path) {
|
|
127
|
+
let stats;
|
|
128
|
+
try {
|
|
129
|
+
stats = statSync(path);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
unavailable(`upgrade descriptor file is unreadable: ${JSON.stringify(path)}`);
|
|
133
|
+
}
|
|
134
|
+
if (!stats.isFile())
|
|
135
|
+
unavailable(`upgrade descriptor path is not a regular file: ${JSON.stringify(path)}`);
|
|
136
|
+
try {
|
|
137
|
+
return { bytes: readFileSync(path), groupOrWorldWritable: (stats.mode & 0o022) !== 0 };
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
unavailable(`upgrade descriptor file is unreadable: ${JSON.stringify(path)}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function parseDescriptorFile(bytes) {
|
|
144
|
+
let parsed;
|
|
145
|
+
try {
|
|
146
|
+
parsed = JSON.parse(bytes.toString("utf8"));
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
unavailable(`upgrade descriptor file is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
150
|
+
}
|
|
151
|
+
if (!isRecord(parsed))
|
|
152
|
+
unavailable("upgrade descriptor file must contain a JSON object");
|
|
153
|
+
assertOnlyKeys(parsed, FILE_KEYS, "upgrade descriptor file");
|
|
154
|
+
if (parsed.version !== 1)
|
|
155
|
+
unavailable(`unsupported upgrade descriptor file version ${JSON.stringify(parsed.version)}`);
|
|
156
|
+
if (!Array.isArray(parsed.descriptors))
|
|
157
|
+
unavailable("upgrade descriptor file requires a descriptors array");
|
|
158
|
+
const descriptors = parsed.descriptors.map((entry, index) => parseDescriptor(entry, index));
|
|
159
|
+
const seen = new Set();
|
|
160
|
+
for (const descriptor of descriptors) {
|
|
161
|
+
if (seen.has(descriptor.id))
|
|
162
|
+
unavailable(`duplicate upgrade descriptor id ${JSON.stringify(descriptor.id)}`);
|
|
163
|
+
seen.add(descriptor.id);
|
|
164
|
+
}
|
|
165
|
+
return descriptors;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Read, authenticate and parse the configured descriptor file. Every failure is
|
|
169
|
+
* `upgrade_descriptor_unavailable`: from the caller's side "not configured",
|
|
170
|
+
* "tampered" and "malformed" are the same answer — you do not have this
|
|
171
|
+
* capability — and collapsing them avoids turning the error into an oracle.
|
|
172
|
+
*/
|
|
173
|
+
export function loadDescriptorFile(env = process.env) {
|
|
174
|
+
const path = descriptorPath(env);
|
|
175
|
+
const signers = allowedSignersOrUnavailable();
|
|
176
|
+
const { bytes, groupOrWorldWritable } = readDescriptorBytes(path);
|
|
177
|
+
// Defence in depth now rather than load-bearing: the signature already covers
|
|
178
|
+
// the content, so tampering is detected regardless. A world-writable
|
|
179
|
+
// authorization artifact is still a smell worth refusing.
|
|
180
|
+
if (groupOrWorldWritable) {
|
|
181
|
+
unavailable(`upgrade descriptor file must not be group- or world-writable: ${JSON.stringify(path)}`);
|
|
182
|
+
}
|
|
183
|
+
const signaturePath = signaturePathFor(path);
|
|
184
|
+
let armored;
|
|
185
|
+
try {
|
|
186
|
+
armored = readFileSync(signaturePath, "utf8");
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
unavailable(`upgrade descriptor file is not signed: expected ${JSON.stringify(signaturePath)} `
|
|
190
|
+
+ `(sign it with: ssh-keygen -Y sign -f <key> -n ${DESCRIPTOR_NAMESPACE} ${path})`);
|
|
191
|
+
}
|
|
192
|
+
let signerKey;
|
|
193
|
+
try {
|
|
194
|
+
signerKey = verifySshsig(bytes, armored, DESCRIPTOR_NAMESPACE, signers.map((signer) => signer.publicKey));
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
unavailable(`upgrade descriptor signature rejected: ${error instanceof SshsigError ? error.message : String(error)}`);
|
|
198
|
+
}
|
|
199
|
+
const fingerprint = sshFingerprint(signerKey);
|
|
200
|
+
const signer = signers.find((entry) => entry.fingerprint === fingerprint);
|
|
201
|
+
return {
|
|
202
|
+
path,
|
|
203
|
+
digest: descriptorFileDigest(bytes),
|
|
204
|
+
signedBy: { principal: signer.principal, fingerprint },
|
|
205
|
+
descriptors: parseDescriptorFile(bytes),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Operator-facing inspection. Reports what is installed and whether it verifies,
|
|
210
|
+
* without being able to grant anything: it reads the same trust root the apply
|
|
211
|
+
* path does and reports failures as text rather than treating them as success.
|
|
212
|
+
*/
|
|
213
|
+
export function inspectDescriptorFile(env = process.env) {
|
|
214
|
+
const path = descriptorPath(env);
|
|
215
|
+
const { bytes, groupOrWorldWritable } = readDescriptorBytes(path);
|
|
216
|
+
const signaturePath = signaturePathFor(path);
|
|
217
|
+
let signers = [];
|
|
218
|
+
let signature;
|
|
219
|
+
try {
|
|
220
|
+
signers = allowedSignersOrUnavailable();
|
|
221
|
+
const armored = readFileSync(signaturePath, "utf8");
|
|
222
|
+
const key = verifySshsig(bytes, armored, DESCRIPTOR_NAMESPACE, signers.map((entry) => entry.publicKey));
|
|
223
|
+
const fingerprint = sshFingerprint(key);
|
|
224
|
+
signature = `verified: signed by ${signers.find((entry) => entry.fingerprint === fingerprint).principal} (${fingerprint})`;
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
signature = `NOT VERIFIED: ${error instanceof Error ? error.message : String(error)}`;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
path,
|
|
231
|
+
sha256: descriptorFileDigest(bytes),
|
|
232
|
+
group_or_world_writable: groupOrWorldWritable,
|
|
233
|
+
signature_path: signaturePath,
|
|
234
|
+
signature,
|
|
235
|
+
allowed_signers: signers.map(({ principal, fingerprint }) => ({ principal, fingerprint })),
|
|
236
|
+
descriptors: parseDescriptorFile(bytes).map(({ id, rationale, from_checksum: fromChecksum }) => ({
|
|
237
|
+
id, rationale, from_checksum: fromChecksum,
|
|
238
|
+
})),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/** Resolve one descriptor by id, or refuse. */
|
|
242
|
+
export function findDescriptor(file, descriptorId) {
|
|
243
|
+
const descriptor = file.descriptors.find((entry) => entry.id === descriptorId);
|
|
244
|
+
if (!descriptor)
|
|
245
|
+
unavailable(`no upgrade descriptor ${JSON.stringify(descriptorId)} in ${JSON.stringify(file.path)}`);
|
|
246
|
+
return descriptor;
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=descriptors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptors.js","sourceRoot":"","sources":["../../src/guard/descriptors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,4BAA4B,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAqC,MAAM,YAAY,CAAC;AAGnF,MAAM,CAAC,MAAM,mBAAmB,GAAG,mCAAmC,CAAC;AACvE,2FAA2F;AAC3F,MAAM,CAAC,MAAM,oBAAoB,GAAG,2BAA2B,CAAC;AAEhE,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,qBAAqB,GAAG,mBAAmB,CAAC;AAClD,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC;AACnF,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC;AAChF,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC;AACtD,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;AAwB1C,4DAA4D;AAC5D,MAAM,UAAU,gBAAgB,CAAC,mBAA2B;IAC1D,OAAO,GAAG,mBAAmB,MAAM,CAAC;AACtC,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,MAAM,IAAI,4BAA4B,CAAC,OAAO,CAAC,CAAC;AAClD,CAAC;AAED,SAAS,cAAc,CAAC,KAA8B,EAAE,OAAoB,EAAE,KAAa;IACzF,8EAA8E;IAC9E,6DAA6D;IAC7D,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,WAAW,CAAC,GAAG,KAAK,oBAAoB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,KAAc,EAAE,KAAa;IAChD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,WAAW,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;IAChE,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC9B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,WAAW,CAAC,GAAG,KAAK,eAAe,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC;QAAE,WAAW,CAAC,GAAG,KAAK,0BAA0B,CAAC,CAAC;IAC5E,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC;QAAE,WAAW,CAAC,GAAG,KAAK,oCAAoC,CAAC,CAAC;IAChG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,WAAW,CAAC,GAAG,KAAK,4BAA4B,CAAC,CAAC;IACtF,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,WAAW,CAAC,GAAG,KAAK,2BAA2B,CAAC,CAAC;IAC9E,8EAA8E;IAC9E,4EAA4E;IAC5E,0EAA0E;IAC1E,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAmB;QAChC,eAAe,EAAE,KAAK,CAAC,eAAiC;QACxD,QAAQ,EAAE,KAAK,CAAC,QAAoB;QACpC,MAAM,EAAE,KAAK,CAAC,MAAgC;KAC/C,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,KAAc,EAAE,KAAa;IACpD,MAAM,KAAK,GAAG,eAAe,KAAK,GAAG,CAAC;IACtC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,WAAW,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;IAChE,cAAc,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,CAAC,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,WAAW,CAAC,GAAG,KAAK,eAAe,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC;IACD,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;IAC7D,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QAC9D,WAAW,CAAC,GAAG,KAAK,gCAAgC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC;QACvD,0EAA0E;QAC1E,WAAW,CAAC,GAAG,KAAK,uCAAuC,CAAC,CAAC;IAC/D,CAAC;IACD,IAAI,OAAO,YAAY,KAAK,QAAQ,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7E,WAAW,CAAC,GAAG,KAAK,sDAAsD,CAAC,CAAC;IAC9E,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,KAAK,YAAY,CAAC,EAAE,CAAC;AACvH,CAAC;AAED,wFAAwF;AACxF,MAAM,UAAU,oBAAoB,CAAC,KAAsB;IACzD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC1D,CAAC;AAED,iFAAiF;AACjF,SAAS,2BAA2B;IAClC,IAAI,CAAC;QACH,OAAO,kBAAkB,EAAE,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,WAAW,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IACtE,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,GAAsB;IAC5C,MAAM,IAAI,GAAG,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACtC,IAAI,CAAC,IAAI;QAAE,WAAW,CAAC,oCAAoC,mBAAmB,wBAAwB,CAAC,CAAC;IACxG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,WAAW,CAAC,GAAG,mBAAmB,2BAA2B,CAAC,CAAC;IACtF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAY;IACvC,IAAI,KAAK,CAAC;IACV,IAAI,CAAC;QACH,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,WAAW,CAAC,0CAA0C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;QAAE,WAAW,CAAC,kDAAkD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3G,IAAI,CAAC;QACH,OAAO,EAAE,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,oBAAoB,EAAE,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;IACzF,CAAC;IAAC,MAAM,CAAC;QACP,WAAW,CAAC,0CAA0C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;AACH,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa;IACxC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,WAAW,CAAC,8CAA8C,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACtH,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,WAAW,CAAC,oDAAoD,CAAC,CAAC;IACzF,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,yBAAyB,CAAC,CAAC;IAC7D,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC;QAAE,WAAW,CAAC,+CAA+C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACvH,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC;QAAE,WAAW,CAAC,sDAAsD,CAAC,CAAC;IAE5G,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAC5F,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YAAE,WAAW,CAAC,mCAAmC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC7G,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACrE,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,OAAO,GAAG,2BAA2B,EAAE,CAAC;IAC9C,MAAM,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAClE,8EAA8E;IAC9E,qEAAqE;IACrE,0DAA0D;IAC1D,IAAI,oBAAoB,EAAE,CAAC;QACzB,WAAW,CAAC,iEAAiE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvG,CAAC;IAED,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,WAAW,CACT,mDAAmD,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG;cACjF,iDAAiD,oBAAoB,IAAI,IAAI,GAAG,CACnF,CAAC;IACJ,CAAC;IACD,IAAI,SAAiB,CAAC;IACtB,IAAI,CAAC;QACH,SAAS,GAAG,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,oBAAoB,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IAC5G,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,WAAW,CACT,0CAA0C,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzG,CAAC;IACJ,CAAC;IACD,MAAM,WAAW,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,KAAK,WAAW,CAAE,CAAC;IAE3E,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,oBAAoB,CAAC,KAAK,CAAC;QACnC,QAAQ,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,WAAW,EAAE;QACtD,WAAW,EAAE,mBAAmB,CAAC,KAAK,CAAC;KACxC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAyB,OAAO,CAAC,GAAG;IASxE,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAE7C,IAAI,OAAO,GAAoB,EAAE,CAAC;IAClC,IAAI,SAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,2BAA2B,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACpD,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,OAAO,EAAE,oBAAoB,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QACxG,MAAM,WAAW,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACxC,SAAS,GAAG,uBAAuB,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,KAAK,WAAW,CAAE,CAAC,SAAS,KAAK,WAAW,GAAG,CAAC;IAC9H,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,SAAS,GAAG,iBAAiB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACxF,CAAC;IAED,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,oBAAoB,CAAC,KAAK,CAAC;QACnC,uBAAuB,EAAE,oBAAoB;QAC7C,cAAc,EAAE,aAAa;QAC7B,SAAS;QACT,eAAe,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,CAAC,CAAC;QAC1F,WAAW,EAAE,mBAAmB,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;YAC/F,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY;SAC3C,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,cAAc,CAAC,IAAoB,EAAE,YAAoB;IACvE,MAAM,UAAU,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,YAAY,CAAC,CAAC;IAC/E,IAAI,CAAC,UAAU;QAAE,WAAW,CAAC,yBAAyB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtH,OAAO,UAAU,CAAC;AACpB,CAAC"}
|