@clear-capabilities/agentic-security-scanner 0.162.0 → 0.163.1
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/CHANGELOG.md +34 -0
- package/bin/agentic-security.js +87 -0
- package/dist/1340.index.js +1871 -0
- package/dist/3477.index.js +727 -0
- package/dist/3499.index.js +23 -3
- package/dist/4113.index.js +1 -1
- package/dist/5014.index.js +833 -0
- package/dist/5756.index.js +2 -2
- package/dist/6257.index.js +1 -1
- package/dist/6907.index.js +248 -0
- package/dist/6994.index.js +1 -1
- package/dist/7033.index.js +33 -5
- package/dist/7439.index.js +1822 -0
- package/dist/7838.index.js +1 -1
- package/dist/9560.index.js +2318 -23
- package/dist/957.index.js +1 -1
- package/dist/9999.index.js +43 -7
- package/dist/agentic-security.mjs +12 -12
- package/dist/agentic-security.mjs.sha256 +1 -1
- package/package.json +33 -6
- package/src/capabilities/CLAUDE.md +114 -0
- package/src/capabilities/address.js +73 -0
- package/src/capabilities/attack-coverage.js +93 -0
- package/src/capabilities/decide.js +256 -0
- package/src/capabilities/delegate.js +69 -0
- package/src/capabilities/egress.js +20 -0
- package/src/capabilities/hook-advice.js +50 -0
- package/src/capabilities/index.js +32 -0
- package/src/capabilities/manifest.js +344 -0
- package/src/capabilities/outbound.js +189 -0
- package/src/capabilities/paths.js +57 -0
- package/src/capabilities/probes.js +211 -0
- package/src/capabilities/proxy.js +182 -0
- package/src/capabilities/reasons.js +67 -0
- package/src/capabilities/receipts.js +356 -0
- package/src/capabilities/records.js +58 -0
- package/src/capabilities/recovery.js +367 -0
- package/src/capabilities/report.js +81 -0
- package/src/capabilities/runner.js +236 -0
- package/src/capabilities/secrets.js +26 -0
- package/src/capabilities/tool-gate.js +119 -0
- package/src/capabilities/tool-registry.js +60 -0
- package/src/dataflow/CLAUDE.md +30 -0
- package/src/dataflow/catalog.js +42 -1
- package/src/dataflow/engine.js +67 -2
- package/src/dataflow/guard-dominance.js +352 -0
- package/src/discovery/CLAUDE.md +9 -0
- package/src/discovery/index.js +15 -1
- package/src/discovery/lenses.js +18 -0
- package/src/engine.js +80 -15
- package/src/integrations/index.js +5 -2
- package/src/ir/CLAUDE.md +14 -0
- package/src/ir/implicit-handler-params.js +74 -0
- package/src/ir/parser-cs.js +20 -0
- package/src/ir/parser-go.js +30 -13
- package/src/ir/parser-java.js +11 -0
- package/src/language/nix-eval-isolation.js +33 -5
- package/src/lineage/CLAUDE.md +31 -0
- package/src/lineage/deployment/adapter-cloud.js +202 -0
- package/src/lineage/deployment/adapter-compose.js +90 -0
- package/src/lineage/deployment/adapter-kit.js +142 -0
- package/src/lineage/deployment/adapter-kubernetes.js +247 -0
- package/src/lineage/deployment/attack-paths.js +443 -0
- package/src/lineage/deployment/boundaries-run.js +223 -0
- package/src/lineage/deployment/boundary-graph.js +567 -0
- package/src/lineage/deployment/cross-repo.js +253 -0
- package/src/lineage/deployment/drift.js +383 -0
- package/src/lineage/deployment/ingest.js +221 -0
- package/src/lineage/deployment/projection.js +244 -0
- package/src/lineage/deployment/trace-correlation.js +541 -0
- package/src/lineage/source-registry.js +19 -0
- package/src/mcp/CLAUDE.md +5 -2
- package/src/mcp/invariant-tools.js +64 -0
- package/src/mcp/portfolio-tools.js +79 -0
- package/src/mcp/redact.js +15 -0
- package/src/mcp/server.js +24 -3
- package/src/mcp/tools.js +13 -1
- package/src/posture/CLAUDE.md +332 -1
- package/src/posture/accuracy-scorecard.js +95 -4
- package/src/posture/assurance/rollback.js +214 -0
- package/src/posture/assurance/rollout.js +200 -0
- package/src/posture/business-logic.js +26 -4
- package/src/posture/evaluation/bakeoff.js +148 -0
- package/src/posture/evaluation/custody.js +200 -0
- package/src/posture/evaluation/deployment-ablation.js +416 -0
- package/src/posture/evaluation/dev-cases.js +65 -0
- package/src/posture/evaluation/economics.js +85 -0
- package/src/posture/evaluation/gates.js +161 -0
- package/src/posture/evaluation/grouping.js +101 -0
- package/src/posture/evaluation/interval.js +132 -0
- package/src/posture/evaluation/invariant-ablation.js +325 -0
- package/src/posture/evaluation/labels.js +200 -0
- package/src/posture/evaluation/protocol.js +308 -0
- package/src/posture/evaluation/release-gate.js +209 -0
- package/src/posture/evaluation/report.js +158 -0
- package/src/posture/evaluation/runner.js +273 -0
- package/src/posture/evaluation/scan-child.js +80 -0
- package/src/posture/evaluation/score.js +174 -0
- package/src/posture/evaluation/synthetic.js +90 -0
- package/src/posture/evaluation/why-missed.js +219 -0
- package/src/posture/fleet.js +11 -2
- package/src/posture/invariants/cli.js +95 -0
- package/src/posture/invariants/coverage.js +164 -0
- package/src/posture/invariants/export.js +178 -0
- package/src/posture/invariants/expressions.js +46 -0
- package/src/posture/invariants/lifecycle.js +289 -0
- package/src/posture/invariants/project-input.js +55 -0
- package/src/posture/invariants/repair.js +340 -0
- package/src/posture/invariants/run.js +45 -0
- package/src/posture/invariants/scenarios.js +311 -0
- package/src/posture/invariants/schema.js +167 -0
- package/src/posture/invariants/shrink.js +407 -0
- package/src/posture/invariants/state-assertions.js +165 -0
- package/src/posture/logic-claims.js +12 -0
- package/src/posture/model-routing.js +40 -0
- package/src/posture/oracles/adapters.js +172 -1
- package/src/posture/oracles/conformance.js +3 -2
- package/src/posture/oracles/oracle.js +23 -3
- package/src/posture/oracles/registry.js +1 -1
- package/src/posture/portfolio/backend.js +279 -0
- package/src/posture/portfolio/bundle.js +314 -0
- package/src/posture/portfolio/cli.js +142 -0
- package/src/posture/portfolio/manifest.js +263 -0
- package/src/posture/portfolio/progress.js +267 -0
- package/src/posture/portfolio/resume.js +130 -0
- package/src/posture/portfolio/retention.js +207 -0
- package/src/posture/portfolio/scheduler.js +488 -0
- package/src/posture/portfolio/signing.js +155 -0
- package/src/posture/portfolio/wording.js +59 -0
- package/src/posture/portfolio/work-units.js +405 -0
- package/src/posture/replay/replay.js +1 -0
- package/src/posture/routing/adapter.js +140 -0
- package/src/posture/routing/calibration.js +278 -0
- package/src/posture/routing/canary.js +141 -0
- package/src/posture/routing/control.js +35 -0
- package/src/posture/routing/decide.js +211 -0
- package/src/posture/routing/drift.js +137 -0
- package/src/posture/routing/economics.js +185 -0
- package/src/posture/routing/feedback.js +207 -0
- package/src/posture/routing/index.js +31 -0
- package/src/posture/routing/outcomes.js +281 -0
- package/src/posture/routing/promotion.js +291 -0
- package/src/posture/routing/receipts.js +59 -0
- package/src/posture/routing/report.js +142 -0
- package/src/posture/routing/shadow.js +53 -0
- package/src/posture/specification-mining.js +26 -0
- package/src/posture/verification/patch-negative.js +16 -4
- package/src/posture/verification/patch-promotion.js +10 -2
- package/src/report/index.js +51 -1
- package/src/sandbox/CLAUDE.md +10 -0
- package/src/sandbox/backend-userspace.js +91 -13
- package/dist/5613.index.js +0 -500
- package/dist/6239.index.js +0 -469
package/dist/5613.index.js
DELETED
|
@@ -1,500 +0,0 @@
|
|
|
1
|
-
export const id = 5613;
|
|
2
|
-
export const ids = [5613];
|
|
3
|
-
export const modules = {
|
|
4
|
-
|
|
5
|
-
/***/ 95613:
|
|
6
|
-
/***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// EXPORTS
|
|
10
|
-
__webpack_require__.d(__webpack_exports__, {
|
|
11
|
-
verifyPatchNegative: () => (/* binding */ verifyPatchNegative)
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// EXTERNAL MODULE: ./src/posture/assurance/identity.js
|
|
15
|
-
var identity = __webpack_require__(41877);
|
|
16
|
-
// EXTERNAL MODULE: ./src/posture/assurance/schema-kit.js
|
|
17
|
-
var schema_kit = __webpack_require__(53353);
|
|
18
|
-
// EXTERNAL MODULE: ./src/posture/assurance/config.js
|
|
19
|
-
var assurance_config = __webpack_require__(90385);
|
|
20
|
-
// EXTERNAL MODULE: ./src/sandbox/capabilities.js
|
|
21
|
-
var capabilities = __webpack_require__(60450);
|
|
22
|
-
// EXTERNAL MODULE: ./src/posture/oracles/oracle.js + 4 modules
|
|
23
|
-
var oracles_oracle = __webpack_require__(80219);
|
|
24
|
-
// EXTERNAL MODULE: ./src/posture/oracles/registry.js + 1 modules
|
|
25
|
-
var registry = __webpack_require__(56239);
|
|
26
|
-
;// CONCATENATED MODULE: ./src/posture/replay/replay.js
|
|
27
|
-
// Replay manifests and environment identity (X-203).
|
|
28
|
-
//
|
|
29
|
-
// Extends the existing proof-of-concept sandbox path (a payload run under the
|
|
30
|
-
// confinement backend, judged by an observed effect) with an identity for the run
|
|
31
|
-
// and a way to repeat it. It is NOT a second sandbox or a second runner: a replay
|
|
32
|
-
// is an oracle run (`oracles/oracle.js`), which goes through `runInBoundary` and
|
|
33
|
-
// therefore through the supervised process tree, output cap and cleanup of
|
|
34
|
-
// CORE-003. What this module adds is the pinned, validated description of the run.
|
|
35
|
-
//
|
|
36
|
-
// A manifest pins: the repository commit, the patch hash, the fixture hash, the
|
|
37
|
-
// toolchain (and container digest when there is one), the oracle id, version and
|
|
38
|
-
// logic digest, the inputs and the budgets. Replaying it on the same supported
|
|
39
|
-
// environment reproduces the verdict, and the identical verification record id.
|
|
40
|
-
//
|
|
41
|
-
// Honesty rules, each with a test:
|
|
42
|
-
// - A manifest is REJECTED, and nothing executes, when a content hash does not
|
|
43
|
-
// match the content, when the oracle changed since the manifest was made, when
|
|
44
|
-
// its scope asks for more than the oracle's declared scope (network, another
|
|
45
|
-
// oracle, a write outside the workspace) or when a budget exceeds the oracle's
|
|
46
|
-
// ceiling. The manifest is closed-world, so a field such as a model verdict
|
|
47
|
-
// cannot ride along.
|
|
48
|
-
// - A missing toolchain, container image or container runtime, or a disabled
|
|
49
|
-
// feature, is a typed PREREQUISITE and the attempt is resumable. Nothing is
|
|
50
|
-
// substituted for the execution that did not happen: the outcome is `not-run`
|
|
51
|
-
// (or `unsupported` when this build can never meet it), with no evidence.
|
|
52
|
-
// - This build never fetches anything. A prerequisite whose acquisition would
|
|
53
|
-
// need the network is reported as such, with the network denied.
|
|
54
|
-
// - Container execution is not implemented. A manifest pinning a container
|
|
55
|
-
// digest is therefore `unsupported` here, never run outside the container it
|
|
56
|
-
// names.
|
|
57
|
-
// - Toolchain identity is exact (runtime version, platform, architecture). A
|
|
58
|
-
// different patch release is a different environment, reported as an unmet
|
|
59
|
-
// prerequisite rather than silently tolerated.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const REPLAY_SCHEMA = 'agentic-security/replay-manifest';
|
|
67
|
-
const ATTEMPT_SCHEMA = 'agentic-security/replay-attempt';
|
|
68
|
-
const MAX_ATTEMPTS = 10;
|
|
69
|
-
|
|
70
|
-
const ALLOWED = ['schema', 'schemaVersion', 'id', 'hypothesisId', 'repository', 'patch', 'fixture', 'toolchain', 'oracle', 'entry', 'inputs', 'inputsDigest', 'budgets', 'scope', 'expected'];
|
|
71
|
-
const REQUIRED = ['schema', 'schemaVersion', 'id', 'hypothesisId', 'repository', 'patch', 'fixture', 'toolchain', 'oracle', 'entry', 'inputs', 'inputsDigest', 'budgets', 'scope'];
|
|
72
|
-
const ID_FIELDS = ALLOWED.filter((k) => k !== 'id');
|
|
73
|
-
const REPLAY_BUDGET_KEYS = ['timeoutMs', 'graceMs', 'maxOutputBytes'];
|
|
74
|
-
const EXPECTED_OUTCOMES = ['confirmed', 'refuted', 'inconclusive'];
|
|
75
|
-
|
|
76
|
-
/** The pinned scope a replay may have: exactly the oracle it names, no network, workspace-only writes. */
|
|
77
|
-
function allowedScope(oracleId) {
|
|
78
|
-
return { executes: [oracleId], network: false, writes: 'workspace-only' };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** Sanitized identity of the execution environment: facts about the toolchain, nothing about the host. */
|
|
82
|
-
function environmentIdentity(overrides = {}) {
|
|
83
|
-
const base = {
|
|
84
|
-
runtime: 'node', version: process.versions.node, platform: process.platform, arch: process.arch,
|
|
85
|
-
containerDigest: null, backend: (0,capabilities/* detectBackend */.kS)(),
|
|
86
|
-
};
|
|
87
|
-
const env = { ...base, ...overrides };
|
|
88
|
-
return { ...env, toolchainDigest: (0,identity/* digestOf */.ol)({ runtime: env.runtime, version: env.version, platform: env.platform, arch: env.arch, containerDigest: env.containerDigest }) };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Build a manifest from the pieces of a verification. The oracle identity and the
|
|
93
|
-
* environment come from this build, not from the caller.
|
|
94
|
-
*/
|
|
95
|
-
function createReplayManifest({ hypothesisId, commit, fixtureFiles, patchFiles = null, oracleId, entry, inputs, budgets = {}, expected, environment }) {
|
|
96
|
-
const oracle = registry.getOracle(oracleId);
|
|
97
|
-
if (!oracle) throw new Error(`unknown oracle '${oracleId}'`);
|
|
98
|
-
const env = environment || environmentIdentity();
|
|
99
|
-
const m = {
|
|
100
|
-
schema: REPLAY_SCHEMA, schemaVersion: schema_kit/* SCHEMA_VERSION */.f$, hypothesisId,
|
|
101
|
-
repository: { commit },
|
|
102
|
-
patch: patchFiles && Object.keys(patchFiles).length ? { digest: (0,identity/* digestOf */.ol)(patchFiles) } : null,
|
|
103
|
-
fixture: { digest: (0,identity/* digestOf */.ol)(fixtureFiles) },
|
|
104
|
-
toolchain: { runtime: env.runtime, version: env.version, platform: env.platform, arch: env.arch, containerDigest: env.containerDigest ?? null },
|
|
105
|
-
oracle: { id: oracle.id, version: oracle.version, logicDigest: oracle.logicDigest },
|
|
106
|
-
entry, inputs, inputsDigest: (0,identity/* digestOf */.ol)(inputs),
|
|
107
|
-
budgets: { timeoutMs: oracle.budgets.timeoutMs, graceMs: oracle.budgets.graceMs, maxOutputBytes: oracle.budgets.maxOutputBytes, ...budgets },
|
|
108
|
-
scope: allowedScope(oracle.id),
|
|
109
|
-
};
|
|
110
|
-
if (expected) m.expected = expected;
|
|
111
|
-
m.id = manifestId(m);
|
|
112
|
-
return m;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function manifestId(m) { return (0,identity/* semanticId */.YN)('rpl', m, ID_FIELDS); }
|
|
116
|
-
|
|
117
|
-
function err(errors, code, path, message) { errors.push({ code, path, message }); }
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Validate a manifest against the content it describes. Rejects, and the caller
|
|
121
|
-
* must not execute, on any error. Pure: reads no file and runs nothing.
|
|
122
|
-
*/
|
|
123
|
-
function validateReplayManifest(m, { fixtureFiles, patchFiles = null } = {}) {
|
|
124
|
-
const errors = [];
|
|
125
|
-
if (!(0,schema_kit/* isPlainObject */.Qd)(m)) { err(errors, 'NOT_AN_OBJECT', '', 'manifest must be an object'); return { ok: false, errors }; }
|
|
126
|
-
if (m.schema !== REPLAY_SCHEMA) { err(errors, 'SCHEMA_MISMATCH', 'schema', `expected '${REPLAY_SCHEMA}'`); return { ok: false, errors }; }
|
|
127
|
-
if (typeof m.schemaVersion !== 'string' || m.schemaVersion.split('.')[0] !== '1') { err(errors, 'UNSUPPORTED_MAJOR', 'schemaVersion', 'only major version 1 is supported'); return { ok: false, errors }; }
|
|
128
|
-
for (const k of Object.keys(m)) if (!ALLOWED.includes(k)) err(errors, 'UNKNOWN_FIELD', k, `field '${k}' is not part of a replay manifest`);
|
|
129
|
-
for (const k of REQUIRED) if (m[k] === undefined) err(errors, 'MISSING_FIELD', k, `required field '${k}' is missing`);
|
|
130
|
-
if (errors.length) return { ok: false, errors };
|
|
131
|
-
|
|
132
|
-
if (typeof m.hypothesisId !== 'string' || !m.hypothesisId) err(errors, 'BAD_TYPE', 'hypothesisId', 'must be a non-empty string');
|
|
133
|
-
if (!(0,schema_kit/* isPlainObject */.Qd)(m.repository) || !(0,schema_kit/* isCommit */.WM)(m.repository.commit)) err(errors, 'BAD_COMMIT', 'repository.commit', 'a replay pins an exact 40 or 64 character commit');
|
|
134
|
-
if (!(0,schema_kit/* isPlainObject */.Qd)(m.fixture) || !(0,schema_kit/* isDigest */.Bc)(m.fixture.digest)) err(errors, 'BAD_DIGEST', 'fixture.digest', 'must be sha256:<64 hex>');
|
|
135
|
-
else if (!(0,schema_kit/* isPlainObject */.Qd)(fixtureFiles) || (0,identity/* digestOf */.ol)(fixtureFiles) !== m.fixture.digest) err(errors, 'HASH_MISMATCH', 'fixture.digest', 'the fixture content does not match its pinned hash');
|
|
136
|
-
const havePatch = (0,schema_kit/* isPlainObject */.Qd)(patchFiles) && Object.keys(patchFiles).length > 0;
|
|
137
|
-
if (m.patch === null) {
|
|
138
|
-
if (havePatch) err(errors, 'HASH_MISMATCH', 'patch', 'patch content was supplied but the manifest pins no patch');
|
|
139
|
-
} else if (!(0,schema_kit/* isPlainObject */.Qd)(m.patch) || !(0,schema_kit/* isDigest */.Bc)(m.patch.digest)) err(errors, 'BAD_DIGEST', 'patch.digest', 'must be null or { digest: sha256:<64 hex> }');
|
|
140
|
-
else if (!havePatch || (0,identity/* digestOf */.ol)(patchFiles) !== m.patch.digest) err(errors, 'HASH_MISMATCH', 'patch.digest', 'the patch content does not match its pinned hash');
|
|
141
|
-
if (!(0,schema_kit/* isDigest */.Bc)(m.inputsDigest) || !(0,schema_kit/* isPlainObject */.Qd)(m.inputs) || (0,identity/* digestOf */.ol)(m.inputs) !== m.inputsDigest) err(errors, 'HASH_MISMATCH', 'inputsDigest', 'the inputs do not match their pinned hash');
|
|
142
|
-
|
|
143
|
-
// Paths are part of the allowed execution scope: a fixture or patch that names a file outside the workspace is refused here,
|
|
144
|
-
// before anything can run, even when its hash matches.
|
|
145
|
-
for (const [label, files] of [['fixture', fixtureFiles], ['patch', patchFiles]]) {
|
|
146
|
-
for (const rel of Object.keys((0,schema_kit/* isPlainObject */.Qd)(files) ? files : {})) {
|
|
147
|
-
if (!(0,oracles_oracle/* isSafeRelativePath */.VH)(rel)) err(errors, 'SCOPE_VIOLATION', `${label}.files`, `path ${JSON.stringify(String(rel).slice(0, 60))} is not a safe relative path inside the workspace`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
if (typeof m.entry === 'string' && (0,schema_kit/* isPlainObject */.Qd)(fixtureFiles) && !(m.entry in { ...fixtureFiles, ...((0,schema_kit/* isPlainObject */.Qd)(patchFiles) ? patchFiles : {}) })) err(errors, 'SCOPE_VIOLATION', 'entry', 'the entry must be one of the pinned files');
|
|
151
|
-
|
|
152
|
-
const tc = m.toolchain;
|
|
153
|
-
if (!(0,schema_kit/* isPlainObject */.Qd)(tc) || tc.runtime !== 'node' || typeof tc.version !== 'string' || typeof tc.platform !== 'string' || typeof tc.arch !== 'string') err(errors, 'BAD_TYPE', 'toolchain', 'needs runtime node, version, platform and arch');
|
|
154
|
-
else if (tc.containerDigest !== null && !(0,schema_kit/* isDigest */.Bc)(tc.containerDigest)) err(errors, 'BAD_DIGEST', 'toolchain.containerDigest', 'must be null or sha256:<64 hex>');
|
|
155
|
-
|
|
156
|
-
const oracle = (0,schema_kit/* isPlainObject */.Qd)(m.oracle) ? registry.getOracle(m.oracle.id) : null;
|
|
157
|
-
if (!oracle) err(errors, 'UNKNOWN_ORACLE', 'oracle.id', 'the manifest names an oracle this build does not have');
|
|
158
|
-
else {
|
|
159
|
-
if (m.oracle.version !== oracle.version || m.oracle.logicDigest !== oracle.logicDigest) err(errors, 'ORACLE_MISMATCH', 'oracle', 'the oracle version or logic digest differs from the pinned one: this is not the oracle the manifest was made with');
|
|
160
|
-
const sc = m.scope;
|
|
161
|
-
const want = allowedScope(oracle.id);
|
|
162
|
-
if (!(0,schema_kit/* isPlainObject */.Qd)(sc) || Object.keys(sc).some((k) => !(k in want))) err(errors, 'SCOPE_VIOLATION', 'scope', 'unknown scope keys');
|
|
163
|
-
else {
|
|
164
|
-
if (JSON.stringify(sc.executes) !== JSON.stringify(want.executes)) err(errors, 'SCOPE_VIOLATION', 'scope.executes', 'a replay may execute only the oracle it names');
|
|
165
|
-
if (sc.network !== false) err(errors, 'SCOPE_VIOLATION', 'scope.network', 'replay grants no network access');
|
|
166
|
-
if (sc.writes !== 'workspace-only') err(errors, 'SCOPE_VIOLATION', 'scope.writes', 'replay writes only inside its workspace');
|
|
167
|
-
}
|
|
168
|
-
const b = m.budgets;
|
|
169
|
-
if (!(0,schema_kit/* isPlainObject */.Qd)(b)) err(errors, 'BAD_TYPE', 'budgets', 'must be an object');
|
|
170
|
-
else {
|
|
171
|
-
for (const k of Object.keys(b)) if (!REPLAY_BUDGET_KEYS.includes(k)) err(errors, 'UNKNOWN_FIELD', `budgets.${k}`, 'not a replay budget');
|
|
172
|
-
for (const k of REPLAY_BUDGET_KEYS) {
|
|
173
|
-
if (!Number.isInteger(b[k]) || b[k] < 1) err(errors, 'BAD_TYPE', `budgets.${k}`, 'must be a positive integer');
|
|
174
|
-
else if (b[k] > oracle.budgets[k]) err(errors, 'BUDGET_EXCEEDED', `budgets.${k}`, `exceeds the oracle ceiling of ${oracle.budgets[k]}`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
if (typeof m.entry !== 'string' || !m.entry) err(errors, 'BAD_TYPE', 'entry', 'must be a non-empty string');
|
|
179
|
-
if (m.expected !== undefined && (!(0,schema_kit/* isPlainObject */.Qd)(m.expected) || Object.keys(m.expected).some((k) => k !== 'outcome') || !EXPECTED_OUTCOMES.includes(m.expected.outcome))) err(errors, 'BAD_TYPE', 'expected', `must be { outcome: one of ${EXPECTED_OUTCOMES.join(', ')} }`);
|
|
180
|
-
if (m.id !== manifestId(m)) err(errors, 'ID_MISMATCH', 'id', 'the manifest id does not match its content');
|
|
181
|
-
return { ok: errors.length === 0, errors };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// ---------------------------------------------------------------- prerequisites
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Typed prerequisites that are not met on this host. `resumable` is true when
|
|
188
|
-
* meeting it is something an operator can do (install a toolchain, enable the
|
|
189
|
-
* feature) and false when this build cannot meet it at all.
|
|
190
|
-
*/
|
|
191
|
-
function unmetPrerequisites(m, host = environmentIdentity()) {
|
|
192
|
-
const out = [];
|
|
193
|
-
const tc = m.toolchain;
|
|
194
|
-
if (tc.containerDigest !== null) {
|
|
195
|
-
out.push({ kind: 'container-runtime', id: tc.containerDigest, state: 'unsupported', resumable: false, reason: 'container execution is not implemented in this build; the run is never executed outside the container the manifest names' });
|
|
196
|
-
out.push({ kind: 'container-image', id: tc.containerDigest, state: 'missing', resumable: false, reason: 'this build neither holds nor pulls container images' });
|
|
197
|
-
out.push({ kind: 'network', id: 'image-acquisition', state: 'denied', resumable: false, reason: 'acquiring a container image needs network access, which replay does not grant' });
|
|
198
|
-
}
|
|
199
|
-
if (tc.version !== host.version || tc.platform !== host.platform || tc.arch !== host.arch) {
|
|
200
|
-
out.push({
|
|
201
|
-
kind: 'toolchain', id: `${tc.runtime}@${tc.version} ${tc.platform}/${tc.arch}`, state: 'missing', resumable: true,
|
|
202
|
-
reason: `this host runs ${host.runtime}@${host.version} ${host.platform}/${host.arch}; the manifest pins an exact toolchain`,
|
|
203
|
-
});
|
|
204
|
-
out.push({ kind: 'network', id: 'toolchain-acquisition', state: 'denied', resumable: true, reason: 'installing the pinned toolchain needs network access, which replay does not grant; install it out of band and resume' });
|
|
205
|
-
}
|
|
206
|
-
return out;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function nextAttempt(resume, manifestId_) {
|
|
210
|
-
if (resume === undefined || resume === null) return { n: 1, history: [] };
|
|
211
|
-
if (!(0,schema_kit/* isPlainObject */.Qd)(resume) || resume.schema !== ATTEMPT_SCHEMA || resume.manifestId !== manifestId_) return { error: 'the attempt does not belong to this manifest' };
|
|
212
|
-
if (resume.status !== 'awaiting-prerequisites') return { error: `an attempt in status '${resume.status}' is not resumable` };
|
|
213
|
-
if (!Number.isInteger(resume.attempt) || resume.attempt < 1 || !Array.isArray(resume.history)) return { error: 'the attempt record is malformed' };
|
|
214
|
-
if (resume.attempt >= MAX_ATTEMPTS) return { error: `the attempt limit of ${MAX_ATTEMPTS} was reached` };
|
|
215
|
-
return { n: resume.attempt + 1, history: resume.history.slice(-MAX_ATTEMPTS) };
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function attemptRecord(manifestId_, n, history, status, entry) {
|
|
219
|
-
return { schema: ATTEMPT_SCHEMA, manifestId: manifestId_, attempt: n, status, history: [...history, { attempt: n, ...entry }] };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Replay a manifest. Never throws.
|
|
224
|
-
*
|
|
225
|
-
* @param {object} bundle { manifest, fixtureFiles, patchFiles? }
|
|
226
|
-
* @param {object} [o]
|
|
227
|
-
* @param {object} [o.resume] an attempt returned earlier with status 'awaiting-prerequisites'
|
|
228
|
-
* @param {object} [o.hostEnvironment] environment identity override (test seam)
|
|
229
|
-
* @param {object} [o.config] assurance config (the verification-oracles feature is off by default)
|
|
230
|
-
* @param {object} [o.runOptions] passed to runOracle (test seams: deps, probeEnv, signal)
|
|
231
|
-
* @returns {Promise<object>} { status: 'completed'|'awaiting-prerequisites'|'rejected', executed, outcome, record, receipt, reproduced, prerequisites, attempt, errors }
|
|
232
|
-
*/
|
|
233
|
-
async function replayManifest(bundle, o = {}) {
|
|
234
|
-
const { manifest, fixtureFiles, patchFiles = null } = bundle || {};
|
|
235
|
-
const valid = validateReplayManifest(manifest, { fixtureFiles, patchFiles });
|
|
236
|
-
if (!valid.ok) return { status: 'rejected', executed: false, outcome: null, record: null, receipt: null, reproduced: null, prerequisites: [], attempt: null, errors: valid.errors };
|
|
237
|
-
|
|
238
|
-
const step = nextAttempt(o.resume, manifest.id);
|
|
239
|
-
if (step.error) return { status: 'rejected', executed: false, outcome: null, record: null, receipt: null, reproduced: null, prerequisites: [], attempt: null, errors: [{ code: 'ATTEMPT_REJECTED', path: 'resume', message: step.error }] };
|
|
240
|
-
|
|
241
|
-
const request = {
|
|
242
|
-
oracleId: manifest.oracle.id, hypothesisId: manifest.hypothesisId, commit: manifest.repository.commit,
|
|
243
|
-
files: { ...fixtureFiles, ...(patchFiles || {}) }, entry: manifest.entry, inputs: manifest.inputs, attempt: step.n,
|
|
244
|
-
budgets: manifest.budgets,
|
|
245
|
-
};
|
|
246
|
-
const waiting = (prerequisites, outcome, reason) => {
|
|
247
|
-
const record = (0,oracles_oracle/* recordWithoutExecution */.Wp)(manifest.oracle.id, request, outcome, reason, registry);
|
|
248
|
-
return {
|
|
249
|
-
status: 'awaiting-prerequisites', executed: false, outcome, record, receipt: null, reproduced: null, prerequisites, errors: [],
|
|
250
|
-
attempt: attemptRecord(manifest.id, step.n, step.history, 'awaiting-prerequisites', { outcome, prerequisites: prerequisites.map((p) => `${p.kind}:${p.state}`) }),
|
|
251
|
-
};
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const unmet = unmetPrerequisites(manifest, o.hostEnvironment || environmentIdentity());
|
|
255
|
-
if (unmet.length) {
|
|
256
|
-
const never = unmet.some((p) => !p.resumable);
|
|
257
|
-
return waiting(unmet, never ? 'unsupported' : 'not-run', `replay prerequisite(s) not met: ${unmet.map((p) => `${p.kind} ${p.id} (${p.state})`).join('; ')}; nothing was executed`);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const run = await (0,oracles_oracle/* runOracle */.qm)(request, { config: o.config, ...(o.runOptions || {}) });
|
|
261
|
-
if (run.status === 'rejected') return { status: 'rejected', executed: false, outcome: null, record: null, receipt: null, reproduced: null, prerequisites: [], attempt: null, errors: (run.errors || []).map((m) => ({ code: 'REQUEST_REJECTED', path: '', message: m })) };
|
|
262
|
-
if (!run.executed) {
|
|
263
|
-
const feature = run.status === 'disabled';
|
|
264
|
-
const prereq = feature
|
|
265
|
-
? [{ kind: 'feature', id: oracles_oracle/* FEATURE_ID */.DU, state: 'disabled', resumable: true, reason: run.reason }]
|
|
266
|
-
: (run.prerequisites || [{ id: 'trust-boundary', reason: run.reason }]).map((p) => ({ kind: p.id === 'trust-boundary' ? 'trust-boundary' : 'oracle-prerequisite', id: p.id, state: 'unmet', resumable: false, reason: p.reason }));
|
|
267
|
-
return waiting(prereq, run.outcome === 'unsupported' ? 'unsupported' : 'not-run', run.reason);
|
|
268
|
-
}
|
|
269
|
-
const reproduced = manifest.expected ? run.outcome === manifest.expected.outcome : null;
|
|
270
|
-
return {
|
|
271
|
-
status: 'completed', executed: true, outcome: run.outcome, record: run.record, receipt: run.receipt, reproduced, disclosure: run.disclosure,
|
|
272
|
-
prerequisites: [], errors: [], run: run.run, environment: run.environment,
|
|
273
|
-
attempt: attemptRecord(manifest.id, step.n, step.history, 'completed', { outcome: run.outcome }),
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
// EXTERNAL MODULE: ./src/posture/verification/patch-promotion.js
|
|
278
|
-
var patch_promotion = __webpack_require__(51487);
|
|
279
|
-
// EXTERNAL MODULE: ./src/posture/verification/repair-records.js
|
|
280
|
-
var repair_records = __webpack_require__(38899);
|
|
281
|
-
;// CONCATENATED MODULE: ./src/posture/verification/patch-negative.js
|
|
282
|
-
// Patch-negative verification (X-204).
|
|
283
|
-
//
|
|
284
|
-
// A re-scan that no longer fires proves the DETECTOR went quiet; a cosmetic edit can do that. A repair is `verified-fix` here
|
|
285
|
-
// only when three things are shown, each by an executed oracle run in an equivalent disposable environment:
|
|
286
|
-
//
|
|
287
|
-
// 1. original-positive the original revision reproduces the exploit (oracle outcome: confirmed)
|
|
288
|
-
// 2. patched-negative the patched revision no longer does, same oracle (oracle outcome: refuted)
|
|
289
|
-
// 3. functional-regression declared legitimate behaviour is intact on the patched revision, with the same expectations
|
|
290
|
-
// holding on the original first (a baseline), so a wrong expectation is reported rather than blamed on the patch.
|
|
291
|
-
//
|
|
292
|
-
// All four runs (the baseline is one) go through `replayManifest`, so each is pinned by commit, fixture hash, patch hash,
|
|
293
|
-
// toolchain, oracle version and inputs, executes inside the trust boundary, and issues a verifier receipt. The result is then
|
|
294
|
-
// handed to `promotePatch`, which re-derives the diff and revision from the proposal and refuses any receipt that does not
|
|
295
|
-
// match them. `verified-fix` needs the promotion; nothing else grants it.
|
|
296
|
-
//
|
|
297
|
-
// A failure never degrades to a vague "not verified": it names the SPECIFIC step that is incomplete and a typed failure code:
|
|
298
|
-
// original-not-reproduced the original did not reproduce under the oracle (nothing to fix, or the scenario is wrong)
|
|
299
|
-
// patched-still-exploitable the exploit still works on the patched revision
|
|
300
|
-
// patched-build-broken the patched revision did not load or its benign control failed
|
|
301
|
-
// patched-negative-inconclusive the patched run decided nothing
|
|
302
|
-
// functional-omitted no functional check was supplied
|
|
303
|
-
// functional-baseline-invalid the declared expectations do not hold on the ORIGINAL revision
|
|
304
|
-
// functional-regression-detected the patched revision behaves differently from the declared expectations
|
|
305
|
-
// oracle-changed the oracle, its version or its inputs differ between the original and patched runs
|
|
306
|
-
// revision-unbound no exact commit: a verdict cannot be tied to a revision
|
|
307
|
-
// prerequisite-unmet a toolchain, feature or isolation prerequisite is missing; resumable where stated
|
|
308
|
-
// environment-mismatch the runs were not in equivalent environments
|
|
309
|
-
// promotion-refused the receipts do not match the exact proposed diff and revision
|
|
310
|
-
//
|
|
311
|
-
// Scope, stated so no caller overclaims: the exploit scenario and the functional cases are the REQUESTER's; this verifies the
|
|
312
|
-
// declared scenario and cases, not that the patch is correct elsewhere. Linux is not claimed (the oracle platform statements
|
|
313
|
-
// stay `unverified`). Receipts are valid inside this process only (see patch-promotion.js).
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const PATCH_NEGATIVE_FEATURE = 'patch-negative-verification';
|
|
323
|
-
const FUNCTIONAL_ORACLE = 'functional-regression';
|
|
324
|
-
|
|
325
|
-
const STEPS = Object.freeze(['original-positive', 'patched-negative', 'functional-baseline', 'functional-regression']);
|
|
326
|
-
|
|
327
|
-
const ROLE_OF_STEP = Object.freeze({ 'original-positive': 'original', 'patched-negative': 'patched', 'functional-baseline': 'functionalBaseline', 'functional-regression': 'functional' });
|
|
328
|
-
const EXPECTED = Object.freeze({ 'original-positive': 'confirmed', 'patched-negative': 'refuted', 'functional-baseline': 'refuted', 'functional-regression': 'refuted' });
|
|
329
|
-
|
|
330
|
-
const stepResult = (step, status, extra = {}) => ({ step, status, expected: EXPECTED[step], outcome: null, code: null, reason: null, recordId: null, receiptDigest: null, ...extra });
|
|
331
|
-
|
|
332
|
-
function replayCode(run) {
|
|
333
|
-
const first = (run.errors || [])[0];
|
|
334
|
-
if (!first) return 'manifest-rejected';
|
|
335
|
-
if (first.code === 'ORACLE_MISMATCH') return 'oracle-changed';
|
|
336
|
-
if (first.code === 'HASH_MISMATCH') return 'content-hash-mismatch';
|
|
337
|
-
return 'manifest-rejected';
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function classify(step, run) {
|
|
341
|
-
if (run.status === 'rejected') return { status: 'incomplete', code: replayCode(run), reason: (run.errors || []).map((e) => e.message).join('; ').slice(0, 300) || 'the replay manifest was rejected' };
|
|
342
|
-
if (run.status === 'awaiting-prerequisites') {
|
|
343
|
-
const resumable = (run.prerequisites || []).every((p) => p.resumable);
|
|
344
|
-
return { status: 'incomplete', code: 'prerequisite-unmet', outcome: run.outcome ?? null, resumable, reason: `${(run.prerequisites || []).map((p) => `${p.kind} ${p.id} (${p.state})`).join('; ') || 'a prerequisite is unmet'}; nothing was executed${resumable ? ' (resumable)' : ''}` };
|
|
345
|
-
}
|
|
346
|
-
const outcome = run.outcome;
|
|
347
|
-
const observed = run.receipt?.observed || {};
|
|
348
|
-
const loadBroken = observed.targetLoaded === false || observed.benignCompleted === false;
|
|
349
|
-
if (outcome === 'error') return { status: 'incomplete', code: 'harness-error', reason: String(run.record?.reason || 'the oracle run failed').slice(0, 300), outcome };
|
|
350
|
-
if (outcome === EXPECTED[step]) return { status: 'passed', code: null, reason: run.record?.reason ?? null, outcome };
|
|
351
|
-
const reason = String(run.record?.reason || '').slice(0, 300);
|
|
352
|
-
switch (step) {
|
|
353
|
-
case 'original-positive':
|
|
354
|
-
return { status: 'failed', code: 'original-not-reproduced', reason: `the original revision did not reproduce the exploit (outcome '${outcome}'): ${reason}`, outcome };
|
|
355
|
-
case 'patched-negative':
|
|
356
|
-
if (outcome === 'confirmed') return { status: 'failed', code: 'patched-still-exploitable', reason: `the exploit still works on the patched revision: ${reason}`, outcome };
|
|
357
|
-
return loadBroken
|
|
358
|
-
? { status: 'failed', code: 'patched-build-broken', reason: `the patched revision did not load or its benign control failed: ${reason}`, outcome }
|
|
359
|
-
: { status: 'failed', code: 'patched-negative-inconclusive', reason: `the patched run decided nothing (outcome '${outcome}'): ${reason}`, outcome };
|
|
360
|
-
case 'functional-baseline':
|
|
361
|
-
return { status: 'failed', code: 'functional-baseline-invalid', reason: `the declared functional expectations do not hold on the original revision (outcome '${outcome}'): ${reason}`, outcome };
|
|
362
|
-
default:
|
|
363
|
-
if (outcome === 'confirmed') return { status: 'failed', code: 'functional-regression-detected', reason: `the patched revision behaves differently from the declared expectations: ${reason}`, outcome };
|
|
364
|
-
return loadBroken
|
|
365
|
-
? { status: 'failed', code: 'patched-build-broken', reason: `the patched revision did not load for the functional check: ${reason}`, outcome }
|
|
366
|
-
: { status: 'failed', code: 'functional-inconclusive', reason: `the functional check decided nothing (outcome '${outcome}'): ${reason}`, outcome };
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
function summaryOf(steps, verified, incomplete, code) {
|
|
371
|
-
const label = (s) => ({ passed: 'PASS', failed: 'FAIL', incomplete: 'INCOMPLETE', 'not-run': 'not run' })[s.status];
|
|
372
|
-
const lines = steps.map((s) => `${s.step}: ${label(s)}${s.code ? ` (${s.code})` : ''}${s.reason && s.status !== 'passed' ? ` - ${s.reason}` : ''}`);
|
|
373
|
-
lines.push(verified
|
|
374
|
-
? 'verified-fix: the exploit reproduced on the original revision, did not reproduce on the patched revision, and the declared functional cases still hold. Only the declared scenario and cases were exercised.'
|
|
375
|
-
: `NOT verified-fix: incomplete step '${incomplete}'${code ? ` (${code})` : ''}.`);
|
|
376
|
-
return lines.join('\n');
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Run the three-part patch-negative verification for one proposed patch. Never throws.
|
|
381
|
-
*
|
|
382
|
-
* @param {object} req
|
|
383
|
-
* @param {string} req.hypothesisId the finding's stable id
|
|
384
|
-
* @param {string} req.commit exact revision the patch is proposed against (40 or 64 hex)
|
|
385
|
-
* @param {object} req.original { files, entry, oracleId, inputs, budgets? } the exploit scenario on the original files
|
|
386
|
-
* @param {object} req.patch { files } the proposed diff: the new content of each changed file
|
|
387
|
-
* @param {object} [req.patched] { oracleId?, inputs?, entry? }: must equal the original's; anything else is `oracle-changed`
|
|
388
|
-
* @param {object} [req.functional] { inputs, budgets? } for the functional-regression oracle; omitting it prevents verified-fix
|
|
389
|
-
* @param {object} [req.pinnedOracle] { id, version, logicDigest } the oracle identity the requester recorded earlier
|
|
390
|
-
* @param {object[]} [req.ledger] earlier repair records to extend
|
|
391
|
-
* @param {object} [o] { config, runOptions } config is the assurance config; runOptions go to the oracle runner (test seams)
|
|
392
|
-
*/
|
|
393
|
-
async function verifyPatchNegative(req, o = {}) {
|
|
394
|
-
const config = o.config || (0,assurance_config/* resolveAssuranceConfig */.cc)({ env: process.env });
|
|
395
|
-
const gate = (0,assurance_config/* featureStatus */.FX)(config, PATCH_NEGATIVE_FEATURE);
|
|
396
|
-
const steps = STEPS.map((s) => stepResult(s, 'not-run', { reason: 'not reached' }));
|
|
397
|
-
const base = {
|
|
398
|
-
status: 'not-verified-fix', verifiedFix: false, incompleteStep: null, failureCode: null, reason: null, steps,
|
|
399
|
-
patchDigest: null, promotion: null, receipts: null, records: {}, verificationRecord: null, repairRecords: Array.isArray(req?.ledger) ? req.ledger : [], summary: '',
|
|
400
|
-
};
|
|
401
|
-
if (gate.status !== 'ok') {
|
|
402
|
-
return { ...base, status: 'disabled', incompleteStep: 'feature-gate', failureCode: gate.code || 'disabled', reason: `${PATCH_NEGATIVE_FEATURE} is not available: ${gate.reason}`, summary: `patch-negative verification did not run: ${gate.reason}` };
|
|
403
|
-
}
|
|
404
|
-
const refuse = (step, code, reason, extra = {}) => {
|
|
405
|
-
const rejected = req && (0,schema_kit/* isPlainObject */.Qd)(req.patch) && (0,schema_kit/* isPlainObject */.Qd)(req.patch.files) && Object.keys(req.patch.files).length && typeof req.hypothesisId === 'string'
|
|
406
|
-
? ledgerFor(base.repairRecords, req, base.patchDigest ?? (0,identity/* digestOf */.ol)(req.patch.files), { rejected: { step, code, reason } }) : { records: base.repairRecords };
|
|
407
|
-
return { ...base, ...extra, patchDigest: req?.patch?.files ? (0,identity/* digestOf */.ol)(req.patch.files) : null, incompleteStep: step, failureCode: code, reason, repairRecords: rejected.records, summary: summaryOf(steps, false, step, code) };
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
// ---- shape and preconditions that need no execution
|
|
411
|
-
const patchFiles = req?.patch?.files;
|
|
412
|
-
if (!(0,schema_kit/* isPlainObject */.Qd)(req) || typeof req.hypothesisId !== 'string' || !req.hypothesisId || !(0,schema_kit/* isPlainObject */.Qd)(req.original) || !(0,schema_kit/* isPlainObject */.Qd)(req.original.files)
|
|
413
|
-
|| typeof req.original.entry !== 'string' || typeof req.original.oracleId !== 'string' || !(0,schema_kit/* isPlainObject */.Qd)(req.original.inputs)
|
|
414
|
-
|| !(0,schema_kit/* isPlainObject */.Qd)(patchFiles) || Object.keys(patchFiles).length === 0) {
|
|
415
|
-
return refuse('input-validation', 'bad-request', 'a request needs a hypothesis id, the original files, entry, oracle and inputs, and a non-empty patch');
|
|
416
|
-
}
|
|
417
|
-
if (!(0,schema_kit/* isCommit */.WM)(req.commit)) return refuse('input-validation', 'revision-unbound', 'no exact commit was given: a verdict cannot be tied to a revision');
|
|
418
|
-
const original = req.original;
|
|
419
|
-
const oracle = registry.getOracle(original.oracleId);
|
|
420
|
-
if (!oracle) return refuse('input-validation', 'unknown-oracle', `the exploit oracle '${String(original.oracleId).slice(0, 60)}' is not registered`);
|
|
421
|
-
if (oracle.id === FUNCTIONAL_ORACLE) return refuse('input-validation', 'bad-request', 'the exploit oracle cannot be the functional-regression oracle');
|
|
422
|
-
const patched = (0,schema_kit/* isPlainObject */.Qd)(req.patched) ? req.patched : {};
|
|
423
|
-
if ((patched.oracleId !== undefined && patched.oracleId !== original.oracleId) || (patched.inputs !== undefined && (0,identity/* digestOf */.ol)(patched.inputs) !== (0,identity/* digestOf */.ol)(original.inputs)) || (patched.entry !== undefined && patched.entry !== original.entry)) {
|
|
424
|
-
return refuse('oracle-consistency', 'oracle-changed', 'the patched run names a different oracle, entry or inputs than the original run: a changed oracle cannot show the exploit is closed');
|
|
425
|
-
}
|
|
426
|
-
const pin = req.pinnedOracle;
|
|
427
|
-
if (pin !== undefined && (!(0,schema_kit/* isPlainObject */.Qd)(pin) || pin.id !== oracle.id || pin.version !== oracle.version || pin.logicDigest !== oracle.logicDigest)) {
|
|
428
|
-
return refuse('oracle-consistency', 'oracle-changed', 'the oracle differs from the identity the requester pinned (id, version or logic digest)');
|
|
429
|
-
}
|
|
430
|
-
const fn = req.functional;
|
|
431
|
-
if (!(0,schema_kit/* isPlainObject */.Qd)(fn) || !(0,schema_kit/* isPlainObject */.Qd)(fn.inputs)) {
|
|
432
|
-
return refuse('functional-regression', 'functional-omitted', 'no functional regression check was supplied: a patch that was never checked for behaviour cannot be a verified fix');
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const patchDigest = (0,identity/* digestOf */.ol)(patchFiles);
|
|
436
|
-
base.patchDigest = patchDigest;
|
|
437
|
-
let ledger = ledgerFor(base.repairRecords, req, patchDigest, {}).records; // proposed
|
|
438
|
-
const common = { hypothesisId: req.hypothesisId, commit: req.commit };
|
|
439
|
-
const plan = [
|
|
440
|
-
['original-positive', { oracleId: original.oracleId, entry: original.entry, inputs: original.inputs, patchFiles: null, budgets: original.budgets }],
|
|
441
|
-
['patched-negative', { oracleId: original.oracleId, entry: original.entry, inputs: original.inputs, patchFiles, budgets: original.budgets }],
|
|
442
|
-
['functional-baseline', { oracleId: FUNCTIONAL_ORACLE, entry: original.entry, inputs: fn.inputs, patchFiles: null, budgets: fn.budgets }],
|
|
443
|
-
['functional-regression', { oracleId: FUNCTIONAL_ORACLE, entry: original.entry, inputs: fn.inputs, patchFiles, budgets: fn.budgets }],
|
|
444
|
-
];
|
|
445
|
-
const runs = {};
|
|
446
|
-
let failed = null;
|
|
447
|
-
for (const [i, [step, spec]] of plan.entries()) {
|
|
448
|
-
if (failed) continue;
|
|
449
|
-
let run;
|
|
450
|
-
try {
|
|
451
|
-
const manifest = createReplayManifest({ ...common, fixtureFiles: original.files, patchFiles: spec.patchFiles, oracleId: spec.oracleId, entry: spec.entry, inputs: spec.inputs, budgets: spec.budgets || {}, expected: { outcome: EXPECTED[step] } });
|
|
452
|
-
run = await replayManifest({ manifest, fixtureFiles: original.files, patchFiles: spec.patchFiles }, { config: o.config || config, runOptions: o.runOptions });
|
|
453
|
-
} catch (e) {
|
|
454
|
-
run = { status: 'rejected', errors: [{ code: 'RUN_FAILED', message: String(e?.message || e).slice(0, 200) }] };
|
|
455
|
-
}
|
|
456
|
-
const c = classify(step, run);
|
|
457
|
-
steps[i] = stepResult(step, c.status, { ...c, recordId: run.record?.id ?? null, receiptDigest: run.receipt?.receiptDigest ?? null });
|
|
458
|
-
if (run.executed && run.receipt) runs[ROLE_OF_STEP[step]] = run;
|
|
459
|
-
if (c.status !== 'passed') failed = { step, ...c };
|
|
460
|
-
}
|
|
461
|
-
base.records = Object.fromEntries(Object.entries(runs).map(([k, r]) => [k, r.record]));
|
|
462
|
-
if (failed) {
|
|
463
|
-
ledger = ledgerFor(ledger, req, patchDigest, { rejected: { step: failed.step, code: failed.code, reason: failed.reason } }).records;
|
|
464
|
-
const original0 = base.records.original ? (0,repair_records/* withRepairStatus */.m$)(base.records.original, (0,repair_records/* verificationRepairFor */.Kf)(ledger, req.hypothesisId, patchDigest)) : null;
|
|
465
|
-
return { ...base, incompleteStep: failed.step, failureCode: failed.code, reason: failed.reason, resumable: failed.resumable ?? false, repairRecords: ledger, verificationRecord: original0, summary: summaryOf(steps, false, failed.step, failed.code) };
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// ---- the receipts must match the exact proposed diff and revision before anything is promoted
|
|
469
|
-
const receipts = Object.fromEntries(Object.entries(runs).map(([k, r]) => [k, r.receipt]));
|
|
470
|
-
const promotion = (0,patch_promotion/* promotePatch */.l)({ proposal: { hypothesisId: req.hypothesisId, revision: req.commit, originalFiles: original.files, patchFiles }, receipts });
|
|
471
|
-
if (!promotion.ok) {
|
|
472
|
-
const envBroken = promotion.reasons.some((r) => r.code === 'environment-mismatch');
|
|
473
|
-
const code = envBroken ? 'environment-mismatch' : promotion.reasons.some((r) => r.code === 'oracle-changed') ? 'oracle-changed' : 'promotion-refused';
|
|
474
|
-
const step = envBroken ? 'environment-equivalence' : code === 'oracle-changed' ? 'oracle-consistency' : 'promotion';
|
|
475
|
-
const reason = promotion.reasons.map((r) => r.message).join('; ').slice(0, 400);
|
|
476
|
-
ledger = ledgerFor(ledger, req, patchDigest, { rejected: { step, code, reason } }).records;
|
|
477
|
-
return { ...base, promotion, incompleteStep: step, failureCode: code, reason, repairRecords: ledger, verificationRecord: base.records.original ? (0,repair_records/* withRepairStatus */.m$)(base.records.original, (0,repair_records/* verificationRepairFor */.Kf)(ledger, req.hypothesisId, patchDigest)) : null, summary: summaryOf(steps, false, step, code) };
|
|
478
|
-
}
|
|
479
|
-
const promoted = (0,repair_records/* recordPromotion */.Zw)(ledger, promotion, { verificationRecordId: base.records.patched?.id ?? null });
|
|
480
|
-
ledger = promoted.records;
|
|
481
|
-
const verificationRecord = (0,repair_records/* withRepairStatus */.m$)(base.records.original, (0,repair_records/* verificationRepairFor */.Kf)(ledger, req.hypothesisId, patchDigest, base.records.patched?.id));
|
|
482
|
-
return {
|
|
483
|
-
...base, status: 'verified-fix', verifiedFix: true, promotion, receipts, repairRecords: ledger, verificationRecord,
|
|
484
|
-
reason: 'verified', summary: summaryOf(steps, true, null, null),
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// The ledger for one verification: always starts with a `proposed` record, and records a rejection when verification stopped.
|
|
489
|
-
function ledgerFor(records, req, patchDigest, { rejected } = {}) {
|
|
490
|
-
let list = Array.isArray(records) ? records : [];
|
|
491
|
-
const fields = { hypothesisId: req.hypothesisId, revision: (0,schema_kit/* isCommit */.WM)(req.commit) ? req.commit : null, diffDigest: patchDigest };
|
|
492
|
-
if (!list.some((r) => r.hypothesisId === fields.hypothesisId && r.diffDigest === patchDigest)) list = (0,repair_records/* appendRepairRecord */.f7)(list, { ...fields, kind: 'proposed', reason: 'a patch was proposed for verification' }).records;
|
|
493
|
-
if (rejected) list = (0,repair_records/* appendRepairRecord */.f7)(list, { ...fields, kind: 'rejected', step: rejected.step, reason: `${rejected.code}: ${String(rejected.reason || '').slice(0, 300)}` }).records;
|
|
494
|
-
return { records: list };
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
/***/ })
|
|
499
|
-
|
|
500
|
-
};
|