@cassiomc1/forgeloop 1.13.0 → 1.14.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/AGENT_COMPATIBILITY.md +8 -0
- package/DOCS_INDEX.md +35 -3
- package/ENG/nodejs-backend-development-eng.md +2 -2
- package/ENG/sec-code-eng.md +7 -7
- package/EXECUTION_STATE.md +12 -0
- package/LOOP_ENGINEERING.md +28 -2
- package/ORCHESTRATOR_INTEGRATION.md +9 -5
- package/PROTOCOL_INTEGRATION.md +55 -2
- package/README.md +40 -25
- package/TERMINOLOGY.md +2 -0
- package/THREAT_MODEL.md +140 -1
- package/completions/_forgeloop +19 -1
- package/completions/forgeloop.bash +37 -1
- package/completions/forgeloop.fish +123 -1
- package/docs/ADVISORY_CONTEXT.md +25 -0
- package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
- package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
- package/docs/AGENT_PROTOCOL_SUMMARY.md +27 -2
- package/docs/AGENT_SKILL.md +66 -0
- package/docs/ARTIFACT_REFERENCE.md +123 -0
- package/docs/AUDIT_UX.md +46 -0
- package/docs/BROWSER_VERIFICATION.md +136 -0
- package/docs/CLI_REFERENCE.md +366 -6
- package/docs/CODE_ATTESTATION.md +2 -2
- package/docs/DOCUMENTATION_GUIDE.md +32 -11
- package/docs/JEV_BENCHMARKS.md +31 -0
- package/docs/MODEL_ROUTING.md +37 -0
- package/docs/OPENSRC_ADAPTER.md +241 -0
- package/docs/PACKAGE_CONTENTS.md +35 -8
- package/docs/PROVIDERS.md +126 -0
- package/docs/PROVIDER_ARCHITECTURE.md +199 -0
- package/docs/RECIPES.md +9 -0
- package/docs/RELEASE_CHECKLIST.md +38 -5
- package/docs/SECURITY_REVIEW.md +71 -0
- package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
- package/docs/TEST_INTELLIGENCE.md +29 -0
- package/docs/TEST_PRUNING.md +14 -0
- package/docs/TROUBLESHOOTING.md +198 -1
- package/docs/UNIVERSAL_INTEGRATION.md +31 -0
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
- package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
- package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
- package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
- package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
- package/docs/diagrams/README.md +13 -9
- package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
- package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
- package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
- package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
- package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
- package/docs/documentation-manifest.json +750 -5
- package/docs/protocol-requirements.json +24 -0
- package/package.json +30 -3
- package/schemas/config.schema.json +14 -0
- package/schemas/context-plan.schema.json +18 -0
- package/schemas/semantic-decision.schema.json +46 -0
- package/schemas/test-utility.schema.json +44 -0
- package/scripts/CI_VALIDATORS.md +6 -6
- package/scripts/benchmark-jev.mjs +5 -0
- package/scripts/benchmark-test-intelligence.mjs +4 -0
- package/scripts/generate-agent-protocol-summary.mjs +4 -1
- package/scripts/generate-forgeloop-skill.mjs +133 -0
- package/scripts/jev-smoke.mjs +19 -0
- package/skills/forgeloop/README.md +9 -0
- package/skills/forgeloop/SKILL.md +77 -0
- package/skills/forgeloop/references/lifecycle.md +9 -0
- package/skills/forgeloop/references/recovery.md +7 -0
- package/skills/forgeloop/references/verification.md +7 -0
- package/src/adapters/agent-browser/assertions.js +47 -0
- package/src/adapters/agent-browser/commands.js +54 -0
- package/src/adapters/agent-browser/index.js +3 -0
- package/src/adapters/agent-browser/locator.js +40 -0
- package/src/adapters/agent-browser/process.js +215 -0
- package/src/adapters/agent-browser/provider.js +313 -0
- package/src/adapters/emulated-services/constants.js +24 -0
- package/src/adapters/emulated-services/index.js +7 -0
- package/src/adapters/emulated-services/process.js +162 -0
- package/src/adapters/emulated-services/provider.js +282 -0
- package/src/adapters/opensrc/normalize.js +90 -0
- package/src/adapters/opensrc/process.js +248 -0
- package/src/adapters/opensrc/provider.js +338 -0
- package/src/adapters/opensrc/search.js +264 -0
- package/src/adapters/typesafe/client.js +28 -0
- package/src/adapters/typesafe/engine.js +63 -0
- package/src/adapters/typesafe/normalize.js +41 -0
- package/src/cli.js +108 -0
- package/src/commands/checkpoint-revalidate.js +176 -0
- package/src/commands/context-plan.js +38 -0
- package/src/commands/contract-create.js +264 -0
- package/src/commands/contract-revise.js +236 -0
- package/src/commands/decision-show.js +14 -0
- package/src/commands/decision-status.js +22 -0
- package/src/commands/discover.js +41 -0
- package/src/commands/doctor.js +15 -0
- package/src/commands/gate-record.js +205 -0
- package/src/commands/gate-revalidate.js +137 -0
- package/src/commands/model-route.js +32 -0
- package/src/commands/route.js +146 -18
- package/src/commands/semantic-plan.js +17 -0
- package/src/commands/task-abandon.js +224 -0
- package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
- package/src/commands/task-repair-contract-bootstrap.js +263 -0
- package/src/commands/test-inventory.js +5 -0
- package/src/commands/test-prune-plan.js +5 -0
- package/src/commands/test-prune-probe.js +5 -0
- package/src/commands/test-utility.js +5 -0
- package/src/commands/validate-protocol.js +10 -1
- package/src/core/artifact-registry.js +24 -0
- package/src/core/audit-ux.js +514 -0
- package/src/core/browser-verification/constants.js +149 -0
- package/src/core/browser-verification/normalize.js +254 -0
- package/src/core/browser-verification/provider.js +519 -0
- package/src/core/browser-verification/service.js +115 -0
- package/src/core/checkpoint-revalidation.js +319 -0
- package/src/core/cli-command-definitions.js +241 -0
- package/src/core/command-executors.js +110 -0
- package/src/core/command-input.js +115 -43
- package/src/core/completion-artifacts.js +14 -5
- package/src/core/completion.js +4 -6
- package/src/core/config.js +3 -0
- package/src/core/context-compiler/budget.js +9 -0
- package/src/core/context-compiler/candidates.js +39 -0
- package/src/core/context-compiler/compiler.js +63 -0
- package/src/core/context-compiler/fingerprint.js +11 -0
- package/src/core/context-compiler/policy.js +13 -0
- package/src/core/context-compiler/result.js +23 -0
- package/src/core/contract-bootstrap-recovery.js +655 -0
- package/src/core/contract-revision.js +210 -0
- package/src/core/decision/artifact.js +69 -0
- package/src/core/decision/benchmarks.js +103 -0
- package/src/core/decision/cache.js +27 -0
- package/src/core/decision/constants.js +58 -0
- package/src/core/decision/cutover.js +34 -0
- package/src/core/decision/engine.js +22 -0
- package/src/core/decision/errors.js +68 -0
- package/src/core/decision/events.js +101 -0
- package/src/core/decision/freshness.js +19 -0
- package/src/core/decision/normalizers/index.js +115 -0
- package/src/core/decision/policy.js +18 -0
- package/src/core/decision/projection.js +16 -0
- package/src/core/decision/question-registry.js +201 -0
- package/src/core/decision/request.js +26 -0
- package/src/core/decision/resolver.js +130 -0
- package/src/core/decision/result.js +58 -0
- package/src/core/decision/service.js +156 -0
- package/src/core/decision/state-builder.js +65 -0
- package/src/core/decision/task-bindings.js +30 -0
- package/src/core/decision/test-provider.js +32 -0
- package/src/core/decision/thresholds.js +15 -0
- package/src/core/error-codes.js +278 -0
- package/src/core/events.js +226 -57
- package/src/core/evidence-readiness.js +9 -0
- package/src/core/execution-prerequisites.js +14 -0
- package/src/core/execution-profile.js +63 -38
- package/src/core/gate-provenance.js +124 -0
- package/src/core/integration-invocation-policy.js +27 -4
- package/src/core/integration-resources.js +86 -61
- package/src/core/model-router/constants.js +10 -0
- package/src/core/model-router/policy.js +103 -0
- package/src/core/model-router/router.js +37 -0
- package/src/core/next-action-model.js +58 -0
- package/src/core/next-action-phases.js +130 -42
- package/src/core/next-action-refresh.js +43 -9
- package/src/core/next-action-review-phase.js +7 -2
- package/src/core/next-action.js +35 -7
- package/src/core/phase.js +128 -10
- package/src/core/preflight-consistency.js +23 -9
- package/src/core/preflight-loaders.js +37 -5
- package/src/core/protocol-info.js +65 -0
- package/src/core/protocol.js +20 -0
- package/src/core/reconcile-closure.js +128 -52
- package/src/core/recovery-history.js +1 -0
- package/src/core/resumability.js +154 -44
- package/src/core/route-artifact.js +15 -1
- package/src/core/router.js +67 -1
- package/src/core/runtime-context.js +118 -61
- package/src/core/schema-validation.js +3 -0
- package/src/core/security-review/constants.js +64 -0
- package/src/core/security-review/normalize.js +245 -0
- package/src/core/security-review/provider.js +204 -0
- package/src/core/security-review/service.js +134 -0
- package/src/core/semantic-planning/constants.js +19 -0
- package/src/core/semantic-planning/projection.js +94 -0
- package/src/core/semantic-planning/service.js +15 -0
- package/src/core/sources.js +37 -0
- package/src/core/task-claim-state.js +201 -1
- package/src/core/task-conflict-inspection.js +31 -5
- package/src/core/task-paths.js +13 -0
- package/src/core/task-recovery.js +1 -0
- package/src/core/templates.js +3 -0
- package/src/core/test-intelligence/benchmarks.js +68 -0
- package/src/core/test-intelligence/inventory.js +73 -0
- package/src/core/test-intelligence/prune.js +90 -0
- package/src/core/test-intelligence/semantic-state.js +15 -0
- package/src/core/test-intelligence/service.js +40 -0
- package/src/core/test-intelligence/utility.js +50 -0
- package/src/core/trace.js +11 -7
- package/src/core/transaction.js +1 -0
- package/src/integration.d.ts +492 -0
- package/src/integration.js +54 -0
- package/src/providers/README.md +47 -0
- package/src/providers/capabilities.js +46 -0
- package/src/providers/errors.js +15 -0
- package/src/providers/index.js +29 -0
- package/src/providers/json-snapshot.js +105 -0
- package/src/providers/registry.js +152 -0
package/src/core/resumability.js
CHANGED
|
@@ -4,6 +4,12 @@ import { createWorkState, initializeWorkState, readWorkState, mutateWorkState }
|
|
|
4
4
|
|
|
5
5
|
const DEFAULT_PENDING_STEPS = ["planning", "implementation", "verification"];
|
|
6
6
|
|
|
7
|
+
const CURRENT_ROUTE_CHECKPOINT_PHASES = new Set(["ROUTED"]);
|
|
8
|
+
|
|
9
|
+
export function routeCheckpointMustMatchCurrentRoute(phase) {
|
|
10
|
+
return CURRENT_ROUTE_CHECKPOINT_PHASES.has(phase);
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
/**
|
|
8
14
|
* Resume phase derived from the highest lifecycle milestone already recorded in
|
|
9
15
|
* a validated ledger. Recreating a checkpoint at ROUTED for a task whose ledger
|
|
@@ -16,9 +22,88 @@ const RESUME_PHASE_BY_MILESTONE = Object.freeze({
|
|
|
16
22
|
EXECUTION_STARTED: "EXECUTING",
|
|
17
23
|
VERIFICATION_STARTED: "VERIFYING",
|
|
18
24
|
VERIFICATION_RECORDED: "VERIFYING",
|
|
25
|
+
REVIEW_STARTED: "REVIEWING",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const RESUME_STEPS_BY_PHASE = Object.freeze({
|
|
29
|
+
CONTRACT_READY: {
|
|
30
|
+
completedSteps: ["contract"],
|
|
31
|
+
pendingSteps: ["route", ...DEFAULT_PENDING_STEPS],
|
|
32
|
+
},
|
|
33
|
+
ROUTED: {
|
|
34
|
+
completedSteps: ["contract", "route"],
|
|
35
|
+
pendingSteps: [...DEFAULT_PENDING_STEPS],
|
|
36
|
+
},
|
|
37
|
+
PLANNED: {
|
|
38
|
+
completedSteps: ["contract", "route", "planning"],
|
|
39
|
+
pendingSteps: [...DEFAULT_PENDING_STEPS.filter((step) => step !== "planning")],
|
|
40
|
+
},
|
|
41
|
+
EXECUTING: {
|
|
42
|
+
completedSteps: ["contract", "route", "planning", "implementation"],
|
|
43
|
+
pendingSteps: ["verification"],
|
|
44
|
+
},
|
|
45
|
+
VERIFYING: {
|
|
46
|
+
completedSteps: ["contract", "route", "planning", "implementation"],
|
|
47
|
+
pendingSteps: ["verification"],
|
|
48
|
+
},
|
|
49
|
+
REVIEWING: {
|
|
50
|
+
completedSteps: ["contract", "route", "planning", "implementation", "verification"],
|
|
51
|
+
pendingSteps: [],
|
|
52
|
+
},
|
|
19
53
|
});
|
|
20
54
|
|
|
21
|
-
|
|
55
|
+
function cycleEvents(events) {
|
|
56
|
+
const CYCLE_EVENT_NAMES = new Set([
|
|
57
|
+
"VERIFICATION_STARTED",
|
|
58
|
+
"VERIFICATION_RECORDED",
|
|
59
|
+
"DIAGNOSIS_RECORDED",
|
|
60
|
+
"DIAGNOSTIC_CASE_RECORDED",
|
|
61
|
+
]);
|
|
62
|
+
const cycles = events
|
|
63
|
+
.filter((event) => CYCLE_EVENT_NAMES.has(event.event))
|
|
64
|
+
.map((event) => event.details?.verificationCycle)
|
|
65
|
+
.filter((cycle) => Number.isInteger(cycle) && cycle >= 1);
|
|
66
|
+
return cycles.at(-1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Canonical reconstruction projection: for a resumed phase derived from a
|
|
71
|
+
* validated ledger, returns the single source of truth for completedSteps and
|
|
72
|
+
* pendingSteps. Unknown phases fall back to the ROUTED projection, matching the
|
|
73
|
+
* historical default resume checkpoint.
|
|
74
|
+
*/
|
|
75
|
+
export function resumeStepsForPhase(phase) {
|
|
76
|
+
return RESUME_STEPS_BY_PHASE[phase] ?? RESUME_STEPS_BY_PHASE.ROUTED;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Canonical verification cycle derived from the ledger when history proves
|
|
81
|
+
* verification has started; undefined when the ledger has no cycle metadata.
|
|
82
|
+
*/
|
|
83
|
+
export function resumeVerificationCycleForPhase(events) {
|
|
84
|
+
return cycleEvents(events);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Canonical work-state identity fields for reconstruction from a validated
|
|
89
|
+
* ledger: resumed phase, completedSteps, pendingSteps, and verificationCycle.
|
|
90
|
+
* This is the one projection shared by ensureResumableState and
|
|
91
|
+
* contract-create reconstruction so later phases cannot be reconstructed with
|
|
92
|
+
* contradictory steps.
|
|
93
|
+
*/
|
|
94
|
+
export function buildResumableWorkStateFields({ events, resumedPhase }) {
|
|
95
|
+
const steps = resumeStepsForPhase(resumedPhase);
|
|
96
|
+
return {
|
|
97
|
+
phase: resumedPhase,
|
|
98
|
+
completedSteps: [...steps.completedSteps],
|
|
99
|
+
pendingSteps: [...steps.pendingSteps],
|
|
100
|
+
...(events && events.length > 0 && cycleEvents(events) !== undefined
|
|
101
|
+
? { verificationCycle: cycleEvents(events) }
|
|
102
|
+
: {}),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function deriveResumePhaseFromLedger(target, packageRoot, taskId) {
|
|
22
107
|
let ledger;
|
|
23
108
|
try {
|
|
24
109
|
ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
@@ -32,71 +117,37 @@ async function deriveResumePhaseFromLedger(target, packageRoot, taskId) {
|
|
|
32
117
|
let derived = null;
|
|
33
118
|
for (const event of scoped) {
|
|
34
119
|
const phase = positions[event.event];
|
|
35
|
-
if (
|
|
36
|
-
if (!derived) {
|
|
37
|
-
derived = phase;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (phase === "VERIFYING") derived = "VERIFYING";
|
|
120
|
+
if (phase) derived = phase;
|
|
41
121
|
}
|
|
42
122
|
return derived;
|
|
43
123
|
}
|
|
44
124
|
|
|
45
|
-
function deriveVerificationCycleFromLedger(events) {
|
|
46
|
-
const cycleEvents = new Set([
|
|
47
|
-
"VERIFICATION_STARTED",
|
|
48
|
-
"VERIFICATION_RECORDED",
|
|
49
|
-
"DIAGNOSIS_RECORDED",
|
|
50
|
-
"DIAGNOSTIC_CASE_RECORDED",
|
|
51
|
-
]);
|
|
52
|
-
const cycles = events
|
|
53
|
-
.filter((event) => cycleEvents.has(event.event))
|
|
54
|
-
.map((event) => event.details?.verificationCycle)
|
|
55
|
-
.filter((cycle) => Number.isInteger(cycle) && cycle >= 1);
|
|
56
|
-
return cycles.at(-1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function resumeSteps(phase) {
|
|
60
|
-
if (phase === "EXECUTING" || phase === "VERIFYING") {
|
|
61
|
-
return {
|
|
62
|
-
completedSteps: ["contract", "route", "planning", "implementation"],
|
|
63
|
-
pendingSteps: ["verification"],
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
completedSteps: ["contract", "route"],
|
|
68
|
-
pendingSteps: [...DEFAULT_PENDING_STEPS],
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
125
|
export async function ensureResumableState({ target, packageRoot, contract, route, taskId, statePath }) {
|
|
73
126
|
if (!contract || !route) return null;
|
|
74
127
|
const existing = await readWorkState(target, { packageRoot, taskId, statePath });
|
|
75
128
|
if (existing) return existing;
|
|
76
129
|
|
|
77
130
|
const resumedPhase = await deriveResumePhaseFromLedger(target, packageRoot, taskId) ?? "ROUTED";
|
|
78
|
-
let
|
|
131
|
+
let events = [];
|
|
79
132
|
try {
|
|
80
133
|
const ledger = await validateEventLedger(target, packageRoot, { taskId });
|
|
81
134
|
if (ledger.valid) {
|
|
82
|
-
|
|
83
|
-
(ledger.events ?? []).filter((event) => !taskId || event.taskId === taskId),
|
|
84
|
-
);
|
|
135
|
+
events = (ledger.events ?? []).filter((event) => !taskId || event.taskId === taskId);
|
|
85
136
|
}
|
|
86
137
|
} catch {
|
|
87
|
-
|
|
138
|
+
events = [];
|
|
88
139
|
}
|
|
89
|
-
const
|
|
140
|
+
const projection = buildResumableWorkStateFields({ events, resumedPhase });
|
|
90
141
|
const state = createWorkState({
|
|
91
142
|
taskId: contract.value.taskId,
|
|
92
143
|
contractFingerprint: contract.fingerprint,
|
|
93
144
|
routeFingerprint: route.fingerprint,
|
|
94
145
|
repositoryFingerprint: await currentRepositoryFingerprint(target),
|
|
95
|
-
phase:
|
|
146
|
+
phase: projection.phase,
|
|
96
147
|
selectedGuides: route.value.guides,
|
|
97
|
-
completedSteps:
|
|
98
|
-
pendingSteps:
|
|
99
|
-
...(verificationCycle !== undefined ? { verificationCycle } : {}),
|
|
148
|
+
completedSteps: projection.completedSteps,
|
|
149
|
+
pendingSteps: projection.pendingSteps,
|
|
150
|
+
...(projection.verificationCycle !== undefined ? { verificationCycle: projection.verificationCycle } : {}),
|
|
100
151
|
checks: [],
|
|
101
152
|
failures: [],
|
|
102
153
|
blockers: [],
|
|
@@ -105,6 +156,65 @@ export async function ensureResumableState({ target, packageRoot, contract, rout
|
|
|
105
156
|
return initializeWorkState(target, state, { packageRoot, taskId, statePath });
|
|
106
157
|
}
|
|
107
158
|
|
|
159
|
+
function routeSyncError(code, message) {
|
|
160
|
+
const error = new Error(message);
|
|
161
|
+
error.code = code;
|
|
162
|
+
return error;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function sameStringList(left, right) {
|
|
166
|
+
const a = [...(left ?? [])].sort();
|
|
167
|
+
const b = [...(right ?? [])].sort();
|
|
168
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Rebinds an existing ROUTED checkpoint to an already-persisted route.
|
|
173
|
+
*
|
|
174
|
+
* Only the route-bound identity fields are updated; contract, repository,
|
|
175
|
+
* steps, checks, evidence, and phase are preserved. Same-identity reruns are
|
|
176
|
+
* a no-op. Every other situation fails closed.
|
|
177
|
+
*/
|
|
178
|
+
export async function synchronizePersistedRouteState({ target, packageRoot, taskId, route, contract = null, statePath } = {}) {
|
|
179
|
+
if (!route || !route.value || typeof route.fingerprint !== "string") {
|
|
180
|
+
throw routeSyncError("E_ROUTE_STALE", "A persisted route artifact is required to synchronize checkpoint identity");
|
|
181
|
+
}
|
|
182
|
+
const state = await readWorkState(target, { packageRoot, taskId, statePath });
|
|
183
|
+
if (!state) return null;
|
|
184
|
+
if (state.phase !== "ROUTED") {
|
|
185
|
+
throw routeSyncError(
|
|
186
|
+
"E_ROUTE_PHASE_UNSUPPORTED",
|
|
187
|
+
`Route checkpoint synchronization supports phase ROUTED, found ${state.phase}`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
if (taskId && state.taskId !== taskId) {
|
|
191
|
+
throw routeSyncError("E_ROUTE_STALE", "Work state does not belong to the current route task");
|
|
192
|
+
}
|
|
193
|
+
if (contract && state.contractFingerprint !== contract.fingerprint) {
|
|
194
|
+
// Contract evolution with regenerated routing is a pre-existing flow whose
|
|
195
|
+
// stale checkpoint is recovered through the sanctioned clear-state and
|
|
196
|
+
// preflight recreation path. Never rebind route identity across a contract
|
|
197
|
+
// boundary here; leave the checkpoint untouched.
|
|
198
|
+
return state;
|
|
199
|
+
}
|
|
200
|
+
if (route.value.contractFingerprint !== undefined && state.contractFingerprint !== route.value.contractFingerprint) {
|
|
201
|
+
return state;
|
|
202
|
+
}
|
|
203
|
+
if (state.routeFingerprint === route.fingerprint && sameStringList(state.selectedGuides, route.value.guides)) {
|
|
204
|
+
return state;
|
|
205
|
+
}
|
|
206
|
+
return mutateWorkState(target, {
|
|
207
|
+
expectedRevision: state.revision ?? 0,
|
|
208
|
+
packageRoot,
|
|
209
|
+
taskId,
|
|
210
|
+
statePath,
|
|
211
|
+
}, () => ({
|
|
212
|
+
...state,
|
|
213
|
+
routeFingerprint: route.fingerprint,
|
|
214
|
+
selectedGuides: [...route.value.guides],
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
|
|
108
218
|
export async function synchronizePreflightState({
|
|
109
219
|
target,
|
|
110
220
|
packageRoot,
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { assertRouteInvariants } from "./router.js";
|
|
2
2
|
import { ARTIFACT_PATHS, readJsonArtifact, writeJsonArtifact } from "./artifacts.js";
|
|
3
3
|
import { readContract } from "./contract.js";
|
|
4
|
-
import { ensureResumableState } from "./resumability.js";
|
|
4
|
+
import { ensureResumableState, synchronizePersistedRouteState } from "./resumability.js";
|
|
5
|
+
import { readWorkState } from "./work-state.js";
|
|
5
6
|
import { taskArtifactPath } from "./task-paths.js";
|
|
6
7
|
|
|
8
|
+
|
|
9
|
+
|
|
7
10
|
export async function persistRoute(target, route, packageRoot, options = {}) {
|
|
8
11
|
assertRouteInvariants(route);
|
|
9
12
|
const { contractFingerprint, ...writeOptions } = options;
|
|
@@ -21,6 +24,14 @@ export async function persistRoute(target, route, packageRoot, options = {}) {
|
|
|
21
24
|
: { ...route, contractFingerprint };
|
|
22
25
|
assertRouteInvariants(value);
|
|
23
26
|
const taskId = options.taskId ?? contractArtifact?.value?.taskId ?? null;
|
|
27
|
+
let existingState = null;
|
|
28
|
+
if (taskId) {
|
|
29
|
+
try {
|
|
30
|
+
existingState = await readWorkState(target, { packageRoot, taskId });
|
|
31
|
+
} catch {
|
|
32
|
+
existingState = null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
24
35
|
const relPath = options.routePath ?? options.routeFile ?? options.relativePath ?? (taskId ? taskArtifactPath(taskId, "route") : ARTIFACT_PATHS.route);
|
|
25
36
|
const artifact = await writeJsonArtifact(
|
|
26
37
|
target,
|
|
@@ -33,6 +44,9 @@ export async function persistRoute(target, route, packageRoot, options = {}) {
|
|
|
33
44
|
if (contractArtifact && contractArtifact.fingerprint === artifact.value.contractFingerprint) {
|
|
34
45
|
await ensureResumableState({ target, packageRoot, contract: contractArtifact, route: artifact, taskId });
|
|
35
46
|
}
|
|
47
|
+
if (existingState?.phase === "ROUTED") {
|
|
48
|
+
await synchronizePersistedRouteState({ target, packageRoot, taskId, route: artifact, contract: contractArtifact });
|
|
49
|
+
}
|
|
36
50
|
return artifact;
|
|
37
51
|
}
|
|
38
52
|
|
package/src/core/router.js
CHANGED
|
@@ -313,6 +313,64 @@ function reasonForSignal(prefix, signal) {
|
|
|
313
313
|
return `${prefix}_${signal.toUpperCase().replaceAll("-", "_")}`;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
+
const SECURITY_TRUST_BOUNDARY_RISKS = Object.freeze([
|
|
317
|
+
"untrusted-input", "personal-data", "secrets", "external-service", "publication",
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
// Canonical deterministic safety floor. These are the route reason codes the
|
|
321
|
+
// router assigns when a trust-boundary signal selects the security guide. The
|
|
322
|
+
// semantic exclusion policy reads protection from this same source, so a
|
|
323
|
+
// mandatory safety guide can never drift out of protection (for example the
|
|
324
|
+
// previously missing external-service boundary).
|
|
325
|
+
const MANDATORY_SAFETY_REASONS = Object.freeze(new Set([
|
|
326
|
+
"SURFACE_AUTH",
|
|
327
|
+
...SECURITY_TRUST_BOUNDARY_RISKS.map((risk) => reasonForSignal("RISK", risk)),
|
|
328
|
+
]));
|
|
329
|
+
|
|
330
|
+
export function isMandatorySafetyReason(reason) {
|
|
331
|
+
return MANDATORY_SAFETY_REASONS.has(reason);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function isMandatorySafetyGuide(guide, reasons) {
|
|
335
|
+
return guide === "security"
|
|
336
|
+
&& Array.isArray(reasons)
|
|
337
|
+
&& reasons.some((reason) => isMandatorySafetyReason(reason));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function applySemanticGuideRecommendation(selected, excluded, recommendation) {
|
|
341
|
+
const guideDecision = recommendation?.guideRelevance
|
|
342
|
+
?? (recommendation?.rankedIds || recommendation?.excludedIds ? recommendation : null);
|
|
343
|
+
if (!guideDecision) return;
|
|
344
|
+
const excludedIds = new Set(Array.isArray(guideDecision.excludedIds) ? guideDecision.excludedIds : []);
|
|
345
|
+
const confidenceById = guideDecision.confidenceById ?? {};
|
|
346
|
+
for (const guide of [...selected.keys()]) {
|
|
347
|
+
const reasons = selected.get(guide);
|
|
348
|
+
if (isMandatorySafetyGuide(guide, reasons)) {
|
|
349
|
+
if (!reasons.includes("MANDATORY_SAFETY_GUIDE")) reasons.push("MANDATORY_SAFETY_GUIDE");
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (!excludedIds.has(guide)) {
|
|
353
|
+
if (!reasons.includes("JEV_RELEVANT")) reasons.push("JEV_RELEVANT");
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if ((confidenceById[guide] ?? 0) < 0.75) {
|
|
357
|
+
if (!reasons.includes("JEV_LOW_CONFIDENCE_RETAINED")) reasons.push("JEV_LOW_CONFIDENCE_RETAINED");
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
selected.delete(guide);
|
|
361
|
+
excluded[guide] = ["JEV_EXCLUDED"];
|
|
362
|
+
}
|
|
363
|
+
const ranked = Array.isArray(guideDecision.rankedIds) ? guideDecision.rankedIds : [];
|
|
364
|
+
const order = new Map(ranked.map((guide, index) => [guide, index]));
|
|
365
|
+
const entries = [...selected.entries()].sort((left, right) => {
|
|
366
|
+
const leftRank = order.has(left[0]) ? order.get(left[0]) : ranked.length + [...selected.keys()].indexOf(left[0]);
|
|
367
|
+
const rightRank = order.has(right[0]) ? order.get(right[0]) : ranked.length + [...selected.keys()].indexOf(right[0]);
|
|
368
|
+
return leftRank - rightRank;
|
|
369
|
+
});
|
|
370
|
+
selected.clear();
|
|
371
|
+
for (const [guide, reasons] of entries) selected.set(guide, reasons);
|
|
372
|
+
}
|
|
373
|
+
|
|
316
374
|
function normalizeArray(value, name, allowed) {
|
|
317
375
|
if (value === undefined) return [];
|
|
318
376
|
if (!Array.isArray(value)) throw new RouteInputError(`${name} must be an array`);
|
|
@@ -440,7 +498,7 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
|
|
|
440
498
|
}
|
|
441
499
|
|
|
442
500
|
for (const risk of normalized.risks) {
|
|
443
|
-
if (
|
|
501
|
+
if (SECURITY_TRUST_BOUNDARY_RISKS.includes(risk)) {
|
|
444
502
|
add("security", reasonForSignal("RISK", risk));
|
|
445
503
|
}
|
|
446
504
|
if (["critical-path", "performance"].includes(risk)) {
|
|
@@ -481,12 +539,19 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
|
|
|
481
539
|
excluded[guide] = [exclusionReasonForGuide(guide, projectEvidence, normalized.workType)];
|
|
482
540
|
}
|
|
483
541
|
|
|
542
|
+
applySemanticGuideRecommendation(
|
|
543
|
+
selected,
|
|
544
|
+
excluded,
|
|
545
|
+
profileOptions.semanticRecommendation ?? null,
|
|
546
|
+
);
|
|
547
|
+
|
|
484
548
|
const guides = [...selected.keys()];
|
|
485
549
|
const result = {
|
|
486
550
|
schemaVersion: ROUTING_SCHEMA_VERSION,
|
|
487
551
|
protocolVersion: PROTOCOL_VERSION,
|
|
488
552
|
input: normalized,
|
|
489
553
|
primary: Object.prototype.hasOwnProperty.call(PRIMARY_GUIDES, normalized.workType)
|
|
554
|
+
&& guides.includes(PRIMARY_GUIDES[normalized.workType])
|
|
490
555
|
? PRIMARY_GUIDES[normalized.workType]
|
|
491
556
|
: guides[0] ?? null,
|
|
492
557
|
guides,
|
|
@@ -499,6 +564,7 @@ export function evaluateRoute(input = {}, profileOptions = {}) {
|
|
|
499
564
|
configuredProfile: profileOptions.configuredProfile ?? input.configuredProfile ?? "auto",
|
|
500
565
|
requestedProfile: profileOptions.requestedProfile
|
|
501
566
|
?? (Object.prototype.hasOwnProperty.call(input, "executionProfile") ? input.executionProfile : null),
|
|
567
|
+
semanticRecommendation: profileOptions.semanticRecommendation ?? null,
|
|
502
568
|
}),
|
|
503
569
|
};
|
|
504
570
|
return assertRouteInvariants(result);
|
|
@@ -4,8 +4,22 @@ import {
|
|
|
4
4
|
normalizeVerificationExecutionPolicy,
|
|
5
5
|
} from "./verification-execution.js";
|
|
6
6
|
import { STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN } from "./structural-quality/constants.js";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
E_ADVISORY_CONTEXT_PROVIDER_INVALID,
|
|
9
|
+
E_BROWSER_VERIFICATION_PROVIDER_INVALID,
|
|
10
|
+
E_SECURITY_REVIEW_PROVIDER_INVALID,
|
|
11
|
+
} from "./error-codes.js";
|
|
8
12
|
import { assertAdvisoryContextProviderIdentity } from "./advisory-context/provider.js";
|
|
13
|
+
import {
|
|
14
|
+
BROWSER_VERIFICATION_PROVIDER_ID_PATTERN,
|
|
15
|
+
assertBrowserVerificationProvider,
|
|
16
|
+
assertBrowserVerificationProviderIdentity,
|
|
17
|
+
} from "./browser-verification/provider.js";
|
|
18
|
+
import {
|
|
19
|
+
SECURITY_REVIEW_PROVIDER_ID_PATTERN,
|
|
20
|
+
assertSecurityReviewProvider,
|
|
21
|
+
assertSecurityReviewProviderIdentity,
|
|
22
|
+
} from "./security-review/provider.js";
|
|
9
23
|
|
|
10
24
|
export const AUTHORITY_TRUST_MODES = Object.freeze(["NONE", "HOST_ATTESTED"]);
|
|
11
25
|
|
|
@@ -83,6 +97,100 @@ export function resolveAuthorityContext(options = {}) {
|
|
|
83
97
|
return hasDirectAuthority ? createAuthorityContext({ ...source, trustMode: "NONE" }) : createAuthorityContext();
|
|
84
98
|
}
|
|
85
99
|
|
|
100
|
+
function isProviderObject(value) {
|
|
101
|
+
return value && typeof value === "object" && !Array.isArray(value);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function providerEntries(configured, label, code) {
|
|
105
|
+
if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
|
|
106
|
+
const error = new Error(`${label} must be an object or Map`);
|
|
107
|
+
error.code = code;
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
return configured instanceof Map ? [...configured.entries()] : Object.entries(configured);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function registerProviders(configured, { label, code, idError, validateId, providerError, validateProvider }) {
|
|
114
|
+
const providers = {};
|
|
115
|
+
for (const [id, provider] of providerEntries(configured, label, code)) {
|
|
116
|
+
if (!validateId(id)) {
|
|
117
|
+
const error = new Error(idError(id));
|
|
118
|
+
error.code = code;
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
if (typeof provider !== "function" && !isProviderObject(provider)) {
|
|
122
|
+
const error = new Error(providerError(id));
|
|
123
|
+
error.code = code;
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
if (typeof provider !== "function") validateProvider(provider, id);
|
|
127
|
+
providers[id] = provider;
|
|
128
|
+
}
|
|
129
|
+
return Object.freeze(providers);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function configureUsageProvider(context, provider) {
|
|
133
|
+
if (!provider
|
|
134
|
+
|| typeof provider !== "object"
|
|
135
|
+
|| Array.isArray(provider)
|
|
136
|
+
|| typeof provider.getTaskUsage !== "function") {
|
|
137
|
+
const error = new Error("Usage provider must expose getTaskUsage({ projectPath, taskId })");
|
|
138
|
+
error.code = "E_USAGE_INVALID";
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
context.usageProvider = provider;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function configureStructuralQualityProviders(context, configured) {
|
|
145
|
+
context.structuralQualityProviders = registerProviders(configured, {
|
|
146
|
+
label: "structuralQualityProviders",
|
|
147
|
+
code: "E_STRUCTURAL_QUALITY_PROVIDER_INVALID",
|
|
148
|
+
idError: id => `Invalid or reserved structural-quality provider ID: ${id}`,
|
|
149
|
+
validateId: id => STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN.test(id) && id !== "sentrux",
|
|
150
|
+
providerError: id => `Structural-quality provider ${id} must be an object or factory`,
|
|
151
|
+
validateProvider: (provider, id) => {},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function configureAdvisoryContextProviders(context, configured) {
|
|
156
|
+
context.advisoryContextProviders = registerProviders(configured, {
|
|
157
|
+
label: "advisoryContextProviders",
|
|
158
|
+
code: E_ADVISORY_CONTEXT_PROVIDER_INVALID,
|
|
159
|
+
idError: id => `Invalid advisory-context provider ID: ${id}`,
|
|
160
|
+
validateId: id => /^[a-z0-9][a-z0-9_-]*$/.test(id),
|
|
161
|
+
providerError: id => `Advisory-context provider ${id} must be an object or factory`,
|
|
162
|
+
validateProvider: assertAdvisoryContextProviderIdentity,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function configureBrowserVerificationProviders(context, configured) {
|
|
167
|
+
context.browserVerificationProviders = registerProviders(configured, {
|
|
168
|
+
label: "browserVerificationProviders",
|
|
169
|
+
code: E_BROWSER_VERIFICATION_PROVIDER_INVALID,
|
|
170
|
+
idError: id => `Invalid browser-verification provider ID: ${id}`,
|
|
171
|
+
validateId: id => BROWSER_VERIFICATION_PROVIDER_ID_PATTERN.test(id),
|
|
172
|
+
providerError: id => `Browser-verification provider ${id} must be an object with verify() or a factory`,
|
|
173
|
+
validateProvider: (provider, id) => {
|
|
174
|
+
assertBrowserVerificationProviderIdentity(provider, { expectedId: id });
|
|
175
|
+
assertBrowserVerificationProvider(provider, { label: `browser-verification provider "${id}"` });
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function configureSecurityReviewProviders(context, configured) {
|
|
181
|
+
context.securityReviewProviders = registerProviders(configured, {
|
|
182
|
+
label: "securityReviewProviders",
|
|
183
|
+
code: E_SECURITY_REVIEW_PROVIDER_INVALID,
|
|
184
|
+
idError: id => `Invalid security-review provider ID: ${id}`,
|
|
185
|
+
validateId: id => SECURITY_REVIEW_PROVIDER_ID_PATTERN.test(id),
|
|
186
|
+
providerError: id => `Security-review provider ${id} must be an object with review() or a factory`,
|
|
187
|
+
validateProvider: (provider, id) => {
|
|
188
|
+
assertSecurityReviewProviderIdentity(provider, { expectedId: id });
|
|
189
|
+
assertSecurityReviewProvider(provider, { label: `security-review provider "${id}"` });
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
86
194
|
export function createForgeLoopContext(options = {}) {
|
|
87
195
|
const authorityContext = createAuthorityContext(options);
|
|
88
196
|
const context = { authorityContext };
|
|
@@ -98,70 +206,19 @@ export function createForgeLoopContext(options = {}) {
|
|
|
98
206
|
context.verificationExecutionPolicy = normalizeVerificationExecutionPolicy(options.verificationExecutionPolicy);
|
|
99
207
|
}
|
|
100
208
|
if (options?.usageProvider !== undefined) {
|
|
101
|
-
|
|
102
|
-
|| typeof options.usageProvider !== "object"
|
|
103
|
-
|| Array.isArray(options.usageProvider)
|
|
104
|
-
|| typeof options.usageProvider.getTaskUsage !== "function") {
|
|
105
|
-
const error = new Error("Usage provider must expose getTaskUsage({ projectPath, taskId })");
|
|
106
|
-
error.code = "E_USAGE_INVALID";
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
context.usageProvider = options.usageProvider;
|
|
209
|
+
configureUsageProvider(context, options.usageProvider);
|
|
110
210
|
}
|
|
111
211
|
if (options?.structuralQualityProviders !== undefined) {
|
|
112
|
-
|
|
113
|
-
? Object.fromEntries(options.structuralQualityProviders.entries())
|
|
114
|
-
: options.structuralQualityProviders;
|
|
115
|
-
if (!configured || typeof configured !== "object" || Array.isArray(configured)) {
|
|
116
|
-
const error = new Error("structuralQualityProviders must be an object or Map");
|
|
117
|
-
error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
|
|
118
|
-
throw error;
|
|
119
|
-
}
|
|
120
|
-
const providers = {};
|
|
121
|
-
for (const [id, provider] of Object.entries(configured)) {
|
|
122
|
-
if (!STRUCTURAL_QUALITY_PROVIDER_ID_PATTERN.test(id) || id === "sentrux") {
|
|
123
|
-
const error = new Error(`Invalid or reserved structural-quality provider ID: ${id}`);
|
|
124
|
-
error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
if (typeof provider !== "function"
|
|
128
|
-
&& (!provider || typeof provider !== "object" || Array.isArray(provider))) {
|
|
129
|
-
const error = new Error(`Structural-quality provider ${id} must be an object or factory`);
|
|
130
|
-
error.code = "E_STRUCTURAL_QUALITY_PROVIDER_INVALID";
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
providers[id] = provider;
|
|
134
|
-
}
|
|
135
|
-
context.structuralQualityProviders = Object.freeze(providers);
|
|
212
|
+
configureStructuralQualityProviders(context, options.structuralQualityProviders);
|
|
136
213
|
}
|
|
137
214
|
if (options?.advisoryContextProviders !== undefined) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
const providers = {};
|
|
147
|
-
for (const [id, provider] of Object.entries(configured)) {
|
|
148
|
-
if (!/^[a-z0-9][a-z0-9_-]*$/.test(id)) {
|
|
149
|
-
const error = new Error(`Invalid advisory-context provider ID: ${id}`);
|
|
150
|
-
error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
|
|
151
|
-
throw error;
|
|
152
|
-
}
|
|
153
|
-
if (typeof provider !== "function"
|
|
154
|
-
&& (!provider || typeof provider !== "object" || Array.isArray(provider))) {
|
|
155
|
-
const error = new Error(`Advisory-context provider ${id} must be an object or factory`);
|
|
156
|
-
error.code = E_ADVISORY_CONTEXT_PROVIDER_INVALID;
|
|
157
|
-
throw error;
|
|
158
|
-
}
|
|
159
|
-
if (typeof provider !== "function") {
|
|
160
|
-
assertAdvisoryContextProviderIdentity(provider, id);
|
|
161
|
-
}
|
|
162
|
-
providers[id] = provider;
|
|
163
|
-
}
|
|
164
|
-
context.advisoryContextProviders = Object.freeze(providers);
|
|
215
|
+
configureAdvisoryContextProviders(context, options.advisoryContextProviders);
|
|
216
|
+
}
|
|
217
|
+
if (options?.browserVerificationProviders !== undefined) {
|
|
218
|
+
configureBrowserVerificationProviders(context, options.browserVerificationProviders);
|
|
219
|
+
}
|
|
220
|
+
if (options?.securityReviewProviders !== undefined) {
|
|
221
|
+
configureSecurityReviewProviders(context, options.securityReviewProviders);
|
|
165
222
|
}
|
|
166
223
|
return Object.freeze(context);
|
|
167
224
|
}
|
|
@@ -55,6 +55,9 @@ export const SHIPPED_SCHEMA_NAMES = Object.freeze([
|
|
|
55
55
|
"execution-profile-benchmark-run",
|
|
56
56
|
"execution-profile-benchmark-aggregate",
|
|
57
57
|
"structural-quality",
|
|
58
|
+
"semantic-decision",
|
|
59
|
+
"context-plan",
|
|
60
|
+
"test-utility",
|
|
58
61
|
]);
|
|
59
62
|
|
|
60
63
|
export class SchemaValidationError extends Error {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { E_SECURITY_REVIEW_REQUEST_INVALID } from "../error-codes.js";
|
|
2
|
+
|
|
3
|
+
export const SECURITY_REVIEW_LIMITS = Object.freeze({
|
|
4
|
+
maxProjectPathChars: 4096,
|
|
5
|
+
maxTaskIdChars: 128,
|
|
6
|
+
maxReviewIdChars: 128,
|
|
7
|
+
maxRequirementChars: 2000,
|
|
8
|
+
maxRequirements: 64,
|
|
9
|
+
maxPaths: 128,
|
|
10
|
+
maxPathChars: 512,
|
|
11
|
+
maxCategories: 32,
|
|
12
|
+
maxCategoryChars: 64,
|
|
13
|
+
maxRevisionChars: 256,
|
|
14
|
+
maxFindings: 256,
|
|
15
|
+
maxFindingIdChars: 128,
|
|
16
|
+
maxSeverityChars: 16,
|
|
17
|
+
maxTitleChars: 512,
|
|
18
|
+
maxSummaryChars: 4096,
|
|
19
|
+
maxRuleIdChars: 128,
|
|
20
|
+
maxConfidenceChars: 16,
|
|
21
|
+
maxDiagnosticChars: 4000,
|
|
22
|
+
maxDiagnostics: 64,
|
|
23
|
+
maxResultChars: 524288,
|
|
24
|
+
maxSnapshotDepth: 32,
|
|
25
|
+
maxSnapshotNodes: 4096,
|
|
26
|
+
maxSnapshotChars: 524288,
|
|
27
|
+
maxDurationMs: 86_400_000,
|
|
28
|
+
defaultTimeoutMs: 30_000,
|
|
29
|
+
maxTimeoutMs: 120_000,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
export const SECURITY_REVIEW_SCOPES = Object.freeze(["FULL", "CHANGED", "SELECTED"]);
|
|
33
|
+
export const SECURITY_REVIEW_SEVERITIES = Object.freeze(["INFO", "LOW", "MEDIUM", "HIGH", "CRITICAL"]);
|
|
34
|
+
export const SECURITY_REVIEW_CONFIDENCE = Object.freeze(["LOW", "MEDIUM", "HIGH"]);
|
|
35
|
+
export const SECURITY_REVIEW_CATEGORIES = Object.freeze([
|
|
36
|
+
"DEPENDENCY", "SECRETS", "INJECTION", "AUTHENTICATION", "AUTHORIZATION",
|
|
37
|
+
"CRYPTOGRAPHY", "CONFIGURATION", "NETWORK", "FILESYSTEM", "SUPPLY_CHAIN",
|
|
38
|
+
"UNSAFE_EXECUTION", "OTHER",
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
export const SECURITY_REVIEW_TRUST = Object.freeze({
|
|
42
|
+
authority: "OBSERVATION",
|
|
43
|
+
evidenceAuthority: "NONE",
|
|
44
|
+
actionability: "NON_EXECUTABLE",
|
|
45
|
+
trustRole: "NON_EVIDENCE_SECURITY_REVIEW",
|
|
46
|
+
persisted: false,
|
|
47
|
+
lifecycleAuthority: false,
|
|
48
|
+
completionAuthority: false,
|
|
49
|
+
evidenceRequiresForgeLoopValidation: true,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const SECURITY_REVIEW_BLOCKED_RESULT_FIELDS = Object.freeze([
|
|
53
|
+
"complete", "completed", "nextAction", "releaseClaims", "mutationAllowed", "taskPhase",
|
|
54
|
+
"lifecycleState", "receipt", "evidence", "evidenceStatus", "check", "gate", "satisfied",
|
|
55
|
+
"ownership", "writeClaims", "event", "events", "transaction", "installation", "command",
|
|
56
|
+
"commandArgv", "executable", "shell", "environment", "credentials", "secrets",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
export function securityReviewRequestError(message, details = null) {
|
|
60
|
+
const error = new Error(message);
|
|
61
|
+
error.code = E_SECURITY_REVIEW_REQUEST_INVALID;
|
|
62
|
+
error.details = details;
|
|
63
|
+
return error;
|
|
64
|
+
}
|