martin-loop 0.1.5 → 1.3.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/CODE_OF_CONDUCT.md +32 -0
- package/LICENSE +21 -21
- package/README.md +307 -398
- package/demo/seeded-workspace/README.md +35 -35
- package/demo/seeded-workspace/TASKS.md +29 -29
- package/demo/seeded-workspace/martin.config.yaml +11 -11
- package/demo/seeded-workspace/package.json +8 -8
- package/demo/seeded-workspace/src/invoice-summary.js +11 -11
- package/demo/seeded-workspace/test/invoice-summary.test.js +20 -20
- package/dist/bin/martin-loop.js +0 -0
- package/dist/vendor/adapters/counter.d.ts +1 -0
- package/dist/vendor/adapters/counter.js +4 -0
- package/dist/vendor/adapters/git-baseline.d.ts +50 -0
- package/dist/vendor/adapters/git-baseline.js +233 -0
- package/dist/vendor/adapters/openrouter-adapter.d.ts +15 -0
- package/dist/vendor/adapters/openrouter-adapter.js +302 -0
- package/dist/vendor/adapters/usage.d.ts +48 -0
- package/dist/vendor/adapters/usage.js +66 -0
- package/dist/vendor/cli/bin/exit.d.ts +12 -0
- package/dist/vendor/cli/bin/exit.js +28 -0
- package/dist/vendor/cli/commands/analyze.d.ts +5 -0
- package/dist/vendor/cli/commands/analyze.js +58 -0
- package/dist/vendor/cli/commands/audit-log-verify.d.ts +34 -0
- package/dist/vendor/cli/commands/audit-log-verify.js +99 -0
- package/dist/vendor/cli/commands/audit.d.ts +8 -0
- package/dist/vendor/cli/commands/audit.js +199 -0
- package/dist/vendor/cli/commands/corpus.d.ts +5 -0
- package/dist/vendor/cli/commands/corpus.js +60 -0
- package/dist/vendor/cli/commands/doctor.d.ts +8 -0
- package/dist/vendor/cli/commands/doctor.js +219 -0
- package/dist/vendor/cli/commands/explain.d.ts +17 -0
- package/dist/vendor/cli/commands/explain.js +176 -0
- package/dist/vendor/cli/commands/export.d.ts +5 -0
- package/dist/vendor/cli/commands/export.js +60 -0
- package/dist/vendor/cli/commands/governance.d.ts +8 -0
- package/dist/vendor/cli/commands/governance.js +95 -0
- package/dist/vendor/cli/commands/improve.d.ts +18 -0
- package/dist/vendor/cli/commands/improve.js +396 -0
- package/dist/vendor/cli/commands/init.d.ts +8 -0
- package/dist/vendor/cli/commands/init.js +281 -0
- package/dist/vendor/cli/commands/migration.d.ts +8 -0
- package/dist/vendor/cli/commands/migration.js +67 -0
- package/dist/vendor/cli/commands/prior.d.ts +23 -0
- package/dist/vendor/cli/commands/prior.js +145 -0
- package/dist/vendor/cli/commands/resume.d.ts +21 -0
- package/dist/vendor/cli/commands/resume.js +73 -0
- package/dist/vendor/cli/commands/verify.d.ts +6 -0
- package/dist/vendor/cli/commands/verify.js +43 -0
- package/dist/vendor/cli/research/public-corpus.d.ts +43 -0
- package/dist/vendor/cli/research/public-corpus.js +151 -0
- package/dist/vendor/cli/ui/error-card.d.ts +38 -0
- package/dist/vendor/cli/ui/error-card.js +103 -0
- package/dist/vendor/cli/ui/mission-brief.d.ts +41 -0
- package/dist/vendor/cli/ui/mission-brief.js +173 -0
- package/dist/vendor/cli/ui/summary-card.d.ts +34 -0
- package/dist/vendor/cli/ui/summary-card.js +102 -0
- package/dist/vendor/contracts/audit.d.ts +46 -0
- package/dist/vendor/contracts/audit.js +360 -0
- package/dist/vendor/contracts/post-phase15.d.ts +240 -0
- package/dist/vendor/contracts/post-phase15.js +166 -0
- package/dist/vendor/core/agent/mandates.d.ts +46 -0
- package/dist/vendor/core/agent/mandates.js +178 -0
- package/dist/vendor/core/agent/receipts.d.ts +38 -0
- package/dist/vendor/core/agent/receipts.js +131 -0
- package/dist/vendor/core/agent/signing.d.ts +17 -0
- package/dist/vendor/core/agent/signing.js +91 -0
- package/dist/vendor/core/attestation/sign.d.ts +25 -0
- package/dist/vendor/core/attestation/sign.js +216 -0
- package/dist/vendor/core/autonomy/autonomous-promotion.d.ts +120 -0
- package/dist/vendor/core/autonomy/autonomous-promotion.js +346 -0
- package/dist/vendor/core/autonomy/envelope-v2.d.ts +29 -0
- package/dist/vendor/core/autonomy/envelope-v2.js +60 -0
- package/dist/vendor/core/autonomy/envelope.d.ts +17 -0
- package/dist/vendor/core/autonomy/envelope.js +27 -0
- package/dist/vendor/core/autonomy/escalation-ledger.d.ts +20 -0
- package/dist/vendor/core/autonomy/escalation-ledger.js +18 -0
- package/dist/vendor/core/autonomy/resume.d.ts +15 -0
- package/dist/vendor/core/autonomy/resume.js +23 -0
- package/dist/vendor/core/circuit/circuit-breaker.d.ts +60 -0
- package/dist/vendor/core/circuit/circuit-breaker.js +143 -0
- package/dist/vendor/core/context-distillation.d.ts +3 -0
- package/dist/vendor/core/context-distillation.js +44 -0
- package/dist/vendor/core/context-flow/compile-context.d.ts +8 -0
- package/dist/vendor/core/context-flow/compile-context.js +111 -0
- package/dist/vendor/core/context-flow/entities.d.ts +2 -0
- package/dist/vendor/core/context-flow/entities.js +44 -0
- package/dist/vendor/core/context-flow/evaluate-policy.d.ts +2 -0
- package/dist/vendor/core/context-flow/evaluate-policy.js +42 -0
- package/dist/vendor/core/context-flow/index.d.ts +11 -0
- package/dist/vendor/core/context-flow/index.js +24 -0
- package/dist/vendor/core/context-flow/labels.d.ts +3 -0
- package/dist/vendor/core/context-flow/labels.js +17 -0
- package/dist/vendor/core/context-flow/normalizer.d.ts +9 -0
- package/dist/vendor/core/context-flow/normalizer.js +69 -0
- package/dist/vendor/core/context-flow/profiles.d.ts +33 -0
- package/dist/vendor/core/context-flow/profiles.js +36 -0
- package/dist/vendor/core/context-flow/redaction.d.ts +1 -0
- package/dist/vendor/core/context-flow/redaction.js +6 -0
- package/dist/vendor/core/context-flow/sensitivity.d.ts +2 -0
- package/dist/vendor/core/context-flow/sensitivity.js +27 -0
- package/dist/vendor/core/context-flow/sync-preview.d.ts +2 -0
- package/dist/vendor/core/context-flow/sync-preview.js +22 -0
- package/dist/vendor/core/context-flow/token-estimator.d.ts +3 -0
- package/dist/vendor/core/context-flow/token-estimator.js +13 -0
- package/dist/vendor/core/context-flow/types.d.ts +91 -0
- package/dist/vendor/core/context-flow/types.js +2 -0
- package/dist/vendor/core/context-utility.d.ts +47 -0
- package/dist/vendor/core/context-utility.js +405 -0
- package/dist/vendor/core/cost/pipeline.d.ts +92 -0
- package/dist/vendor/core/cost/pipeline.js +141 -0
- package/dist/vendor/core/cost/tagged-cost.d.ts +27 -0
- package/dist/vendor/core/cost/tagged-cost.js +55 -0
- package/dist/vendor/core/cost-governor.d.ts +2 -0
- package/dist/vendor/core/cost-governor.js +50 -0
- package/dist/vendor/core/cve/cve-check.d.ts +80 -0
- package/dist/vendor/core/cve/cve-check.js +172 -0
- package/dist/vendor/core/digital-twin/index.d.ts +27 -0
- package/dist/vendor/core/digital-twin/index.js +90 -0
- package/dist/vendor/core/drift/drift-graph.d.ts +47 -0
- package/dist/vendor/core/drift/drift-graph.js +100 -0
- package/dist/vendor/core/drift/objective-lock.d.ts +69 -0
- package/dist/vendor/core/drift/objective-lock.js +88 -0
- package/dist/vendor/core/drift/scope.d.ts +46 -0
- package/dist/vendor/core/drift/scope.js +102 -0
- package/dist/vendor/core/drift/signature-lock.d.ts +48 -0
- package/dist/vendor/core/drift/signature-lock.js +202 -0
- package/dist/vendor/core/drift/stale-proof-gate.d.ts +21 -0
- package/dist/vendor/core/drift/stale-proof-gate.js +19 -0
- package/dist/vendor/core/eval/known-bad-world-runner.d.ts +24 -0
- package/dist/vendor/core/eval/known-bad-world-runner.js +256 -0
- package/dist/vendor/core/evidence/claim-audit.d.ts +18 -0
- package/dist/vendor/core/evidence/claim-audit.js +89 -0
- package/dist/vendor/core/exit-intelligence.d.ts +2 -0
- package/dist/vendor/core/exit-intelligence.js +58 -0
- package/dist/vendor/core/explain/formatter.d.ts +42 -0
- package/dist/vendor/core/explain/formatter.js +171 -0
- package/dist/vendor/core/explain/timeline.d.ts +29 -0
- package/dist/vendor/core/explain/timeline.js +213 -0
- package/dist/vendor/core/failure-taxonomy.d.ts +2 -0
- package/dist/vendor/core/failure-taxonomy.js +76 -0
- package/dist/vendor/core/gateway/index.d.ts +10 -0
- package/dist/vendor/core/gateway/index.js +12 -0
- package/dist/vendor/core/gateway/registry.d.ts +40 -0
- package/dist/vendor/core/gateway/registry.js +97 -0
- package/dist/vendor/core/gateway/transport.d.ts +31 -0
- package/dist/vendor/core/gateway/transport.js +82 -0
- package/dist/vendor/core/gateway/vault.d.ts +19 -0
- package/dist/vendor/core/gateway/vault.js +29 -0
- package/dist/vendor/core/graph/adapters.d.ts +43 -0
- package/dist/vendor/core/graph/adapters.js +91 -0
- package/dist/vendor/core/graph/hotspots.d.ts +22 -0
- package/dist/vendor/core/graph/hotspots.js +30 -0
- package/dist/vendor/core/graph/index.d.ts +1 -0
- package/dist/vendor/core/graph/index.js +2 -0
- package/dist/vendor/core/honey/honey-tokens.d.ts +32 -0
- package/dist/vendor/core/honey/honey-tokens.js +44 -0
- package/dist/vendor/core/index.d.ts +2 -2
- package/dist/vendor/core/index.js +38 -12
- package/dist/vendor/core/learning/bayesian-update.d.ts +31 -0
- package/dist/vendor/core/learning/bayesian-update.js +60 -0
- package/dist/vendor/core/learning/prior-sets.d.ts +42 -0
- package/dist/vendor/core/learning/prior-sets.js +111 -0
- package/dist/vendor/core/learning/promotion-gate.d.ts +17 -0
- package/dist/vendor/core/learning/promotion-gate.js +23 -0
- package/dist/vendor/core/leash/blast-radius.d.ts +42 -0
- package/dist/vendor/core/leash/blast-radius.js +156 -0
- package/dist/vendor/core/leash/policy-leash.d.ts +31 -0
- package/dist/vendor/core/leash/policy-leash.js +117 -0
- package/dist/vendor/core/memo/memo.d.ts +63 -0
- package/dist/vendor/core/memo/memo.js +97 -0
- package/dist/vendor/core/memory/learning-pipeline.d.ts +154 -0
- package/dist/vendor/core/memory/learning-pipeline.js +391 -0
- package/dist/vendor/core/memory/palace.d.ts +84 -0
- package/dist/vendor/core/memory/palace.js +379 -0
- package/dist/vendor/core/merge/ast-merge.d.ts +22 -0
- package/dist/vendor/core/merge/ast-merge.js +350 -0
- package/dist/vendor/core/merge/text-merge.d.ts +12 -0
- package/dist/vendor/core/merge/text-merge.js +182 -0
- package/dist/vendor/core/otel/tracer.d.ts +45 -0
- package/dist/vendor/core/otel/tracer.js +116 -0
- package/dist/vendor/core/parallel/parallel-attempts.d.ts +28 -0
- package/dist/vendor/core/parallel/parallel-attempts.js +41 -0
- package/dist/vendor/core/parallel/scorer.d.ts +24 -0
- package/dist/vendor/core/parallel/scorer.js +65 -0
- package/dist/vendor/core/pattern-detection.d.ts +64 -0
- package/dist/vendor/core/pattern-detection.js +108 -0
- package/dist/vendor/core/persistence/checkpoint.d.ts +44 -0
- package/dist/vendor/core/persistence/checkpoint.js +156 -0
- package/dist/vendor/core/persistence/cleanup.d.ts +22 -0
- package/dist/vendor/core/persistence/cleanup.js +131 -0
- package/dist/vendor/core/persistence/index.d.ts +2 -0
- package/dist/vendor/core/persistence/index.js +1 -0
- package/dist/vendor/core/persistence/runs-reader.d.ts +52 -0
- package/dist/vendor/core/persistence/runs-reader.js +84 -0
- package/dist/vendor/core/persistence/store.d.ts +6 -1
- package/dist/vendor/core/persistence/store.js +5 -0
- package/dist/vendor/core/policy/file-touch-quota.d.ts +60 -0
- package/dist/vendor/core/policy/file-touch-quota.js +105 -0
- package/dist/vendor/core/policy/policy-loader.d.ts +30 -0
- package/dist/vendor/core/policy/policy-loader.js +170 -0
- package/dist/vendor/core/policy/policy-schema.d.ts +55 -0
- package/dist/vendor/core/policy/policy-schema.js +78 -0
- package/dist/vendor/core/probe/probe.d.ts +49 -0
- package/dist/vendor/core/probe/probe.js +115 -0
- package/dist/vendor/core/proof/patch-proof.d.ts +58 -0
- package/dist/vendor/core/proof/patch-proof.js +84 -0
- package/dist/vendor/core/proof/semantic-probe.d.ts +25 -0
- package/dist/vendor/core/proof/semantic-probe.js +82 -0
- package/dist/vendor/core/recovery/failure-mode-runner.d.ts +29 -0
- package/dist/vendor/core/recovery/failure-mode-runner.js +39 -0
- package/dist/vendor/core/red-blue/red-phase.d.ts +64 -0
- package/dist/vendor/core/red-blue/red-phase.js +141 -0
- package/dist/vendor/core/red-blue/risk-tiers.d.ts +22 -0
- package/dist/vendor/core/red-blue/risk-tiers.js +33 -0
- package/dist/vendor/core/replay/replay.d.ts +85 -0
- package/dist/vendor/core/replay/replay.js +109 -0
- package/dist/vendor/core/router/engine.d.ts +54 -0
- package/dist/vendor/core/router/engine.js +131 -0
- package/dist/vendor/core/router/index.d.ts +1 -0
- package/dist/vendor/core/router/index.js +2 -0
- package/dist/vendor/core/router/trust-calibration.d.ts +57 -0
- package/dist/vendor/core/router/trust-calibration.js +127 -0
- package/dist/vendor/core/run-martin.d.ts +2 -0
- package/dist/vendor/core/run-martin.js +287 -0
- package/dist/vendor/core/security/cve-scanner.d.ts +62 -0
- package/dist/vendor/core/security/cve-scanner.js +178 -0
- package/dist/vendor/core/sentinel/efficiency-sentinel.d.ts +29 -0
- package/dist/vendor/core/sentinel/efficiency-sentinel.js +30 -0
- package/dist/vendor/core/sentinel/progress-guard.d.ts +35 -0
- package/dist/vendor/core/sentinel/progress-guard.js +46 -0
- package/dist/vendor/core/siem/siem-emitter.d.ts +49 -0
- package/dist/vendor/core/siem/siem-emitter.js +157 -0
- package/dist/vendor/core/strategy/attempt-brief.d.ts +22 -0
- package/dist/vendor/core/strategy/attempt-brief.js +89 -0
- package/dist/vendor/core/summarize/diff-summary.d.ts +35 -0
- package/dist/vendor/core/summarize/diff-summary.js +204 -0
- package/dist/vendor/core/surface-signals.d.ts +21 -0
- package/dist/vendor/core/surface-signals.js +139 -0
- package/dist/vendor/core/truth/truth-wall.d.ts +51 -0
- package/dist/vendor/core/truth/truth-wall.js +69 -0
- package/dist/vendor/core/truth-spine.d.ts +26 -0
- package/dist/vendor/core/truth-spine.js +62 -0
- package/dist/vendor/core/types.d.ts +115 -0
- package/dist/vendor/core/types.js +2 -0
- package/dist/vendor/core/verification/tiered-verify.d.ts +17 -0
- package/dist/vendor/core/verification/tiered-verify.js +29 -0
- package/dist/vendor/core/verifier-pyramid.d.ts +32 -0
- package/dist/vendor/core/verifier-pyramid.js +111 -0
- package/dist/vendor/core/workflow-artifacts.d.ts +99 -0
- package/dist/vendor/core/workflow-artifacts.js +668 -0
- package/dist/vendor/core/wrap/supervised-run.d.ts +96 -0
- package/dist/vendor/core/wrap/supervised-run.js +178 -0
- package/docs/assets/cli-animated.svg +139 -0
- package/docs/assets/cli-static.svg +34 -0
- package/docs/assets/github-hero-v2.svg +23 -0
- package/docs/assets/martin-raplph.png.jpg +0 -0
- package/docs/assets/martinloop-logo.png +0 -0
- package/docs/assets/nvidia-inception-program-light.png +0 -0
- package/docs/assets/nvidia-inception-program.png +0 -0
- package/docs/assets/phase3c-sidesidebyside-demo.html +228 -0
- package/docs/assets/side-by-side.svg +134 -0
- package/docs/oss/CLAUDE-CODE-WALKTHROUGH.md +142 -142
- package/docs/oss/EXAMPLES.md +134 -134
- package/docs/oss/OSS-BOUNDARY-REPORT.json +1 -1
- package/docs/oss/OSS-BOUNDARY-REPORT.md +1 -1
- package/docs/oss/QUICKSTART.md +170 -165
- package/docs/oss/RALPH-LOOP-SAFETY.md +113 -113
- package/docs/oss/README.md +96 -96
- package/docs/oss/RELEASE-SURFACE-REPORT.json +2 -1
- package/docs/oss/RELEASE-SURFACE-REPORT.md +2 -1
- package/package.json +130 -58
- package/docs/distribution/DIRECTORY-SUBMISSIONS.md +0 -89
- package/docs/distribution/INTEGRATION-OUTREACH.md +0 -61
- package/docs/distribution/UNDER-3-CHALLENGE.md +0 -65
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { evaluate as evaluateOpaPolicy } from "@martin/policy";
|
|
2
|
+
export async function applyPolicyToLeashDecision(decision, inputs, options = {}) {
|
|
3
|
+
const policyVerdict = await evaluatePolicyInputs(inputs, options);
|
|
4
|
+
if (policyVerdict.allow) {
|
|
5
|
+
return {
|
|
6
|
+
...decision,
|
|
7
|
+
policyVerdict
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
const policyViolations = policyVerdict.deniedInputs.flatMap(toPolicyViolations);
|
|
11
|
+
const policyBlockedCommands = policyVerdict.deniedInputs
|
|
12
|
+
.map((input) => input.command)
|
|
13
|
+
.filter((command) => typeof command === "string" && command.length > 0);
|
|
14
|
+
return {
|
|
15
|
+
...decision,
|
|
16
|
+
allowed: false,
|
|
17
|
+
blocked: true,
|
|
18
|
+
riskLevel: "blocked",
|
|
19
|
+
blockedCommands: [...new Set([...decision.blockedCommands, ...policyBlockedCommands])],
|
|
20
|
+
violations: [...decision.violations, ...policyViolations],
|
|
21
|
+
reason: joinReasons([
|
|
22
|
+
decision.reason,
|
|
23
|
+
`OPA policy denied ${policyVerdict.deniedInputs.length} safety input(s): ${policyVerdict.reasons.join("; ")}`
|
|
24
|
+
]),
|
|
25
|
+
policyVerdict
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
async function evaluatePolicyInputs(inputs, options) {
|
|
29
|
+
if (inputs.length === 0) {
|
|
30
|
+
return {
|
|
31
|
+
allow: true,
|
|
32
|
+
reasons: [],
|
|
33
|
+
deniedInputs: [],
|
|
34
|
+
inputsEvaluated: 0
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const deniedInputs = [];
|
|
38
|
+
const reasons = new Set();
|
|
39
|
+
let source;
|
|
40
|
+
let policyPath;
|
|
41
|
+
for (const input of inputs) {
|
|
42
|
+
try {
|
|
43
|
+
const result = await withTimeout(evaluateOpaPolicy(input, options.policyWasmPath ?? options.policyPath, options.repoRoot ? { repoRoot: options.repoRoot } : undefined), options.timeoutMs ?? 2_000);
|
|
44
|
+
source ??= result.source;
|
|
45
|
+
policyPath ??= result.policyPath;
|
|
46
|
+
if (!result.allow) {
|
|
47
|
+
for (const reason of result.reasons) {
|
|
48
|
+
reasons.add(reason);
|
|
49
|
+
}
|
|
50
|
+
deniedInputs.push({
|
|
51
|
+
surface: input.surface,
|
|
52
|
+
...(input.command !== undefined ? { command: input.command } : {}),
|
|
53
|
+
...(input.path !== undefined ? { path: input.path } : {}),
|
|
54
|
+
...(input.value !== undefined ? { value: input.value } : {}),
|
|
55
|
+
reasons: result.reasons
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
reasons.add(message);
|
|
62
|
+
deniedInputs.push({
|
|
63
|
+
surface: input.surface,
|
|
64
|
+
...(input.command !== undefined ? { command: input.command } : {}),
|
|
65
|
+
...(input.path !== undefined ? { path: input.path } : {}),
|
|
66
|
+
...(input.value !== undefined ? { value: input.value } : {}),
|
|
67
|
+
reasons: [message]
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
allow: false,
|
|
71
|
+
reasons: [...reasons],
|
|
72
|
+
deniedInputs,
|
|
73
|
+
inputsEvaluated: inputs.length,
|
|
74
|
+
...(source ? { source } : {}),
|
|
75
|
+
...(policyPath ? { policyPath } : {}),
|
|
76
|
+
error: message
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
allow: deniedInputs.length === 0,
|
|
82
|
+
reasons: [...reasons],
|
|
83
|
+
deniedInputs,
|
|
84
|
+
inputsEvaluated: inputs.length,
|
|
85
|
+
...(source ? { source } : {}),
|
|
86
|
+
...(policyPath ? { policyPath } : {})
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function toPolicyViolations(input) {
|
|
90
|
+
return input.reasons.map((reason) => ({
|
|
91
|
+
kind: "policy_denied",
|
|
92
|
+
message: `OPA policy denied ${input.surface}: ${reason}`,
|
|
93
|
+
...(input.command ? { command: input.command } : {}),
|
|
94
|
+
...(input.path ? { file: input.path } : {}),
|
|
95
|
+
...(input.value ? { match: input.value } : {})
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
function joinReasons(values) {
|
|
99
|
+
return values.filter((value) => Boolean(value && value.length > 0)).join(" ");
|
|
100
|
+
}
|
|
101
|
+
async function withTimeout(promise, timeoutMs) {
|
|
102
|
+
let timeout;
|
|
103
|
+
try {
|
|
104
|
+
return await Promise.race([
|
|
105
|
+
promise,
|
|
106
|
+
new Promise((_, reject) => {
|
|
107
|
+
timeout = setTimeout(() => reject(new Error(`OPA policy evaluation timed out after ${timeoutMs}ms.`)), timeoutMs);
|
|
108
|
+
})
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
if (timeout) {
|
|
113
|
+
clearTimeout(timeout);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=policy-leash.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memo.ts — SLICE-03
|
|
3
|
+
*
|
|
4
|
+
* Attempt memoization via objective + verifier + file hash key.
|
|
5
|
+
*
|
|
6
|
+
* On a cache HIT: the caller replays the stored patch with zero model calls.
|
|
7
|
+
* On a cache MISS: the caller runs the normal attempt path, then calls storeMemo.
|
|
8
|
+
*
|
|
9
|
+
* Cache key: SHA-256 of (objective, verifierCommand, sorted file path → hash pairs)
|
|
10
|
+
* Cache location: <memoRoot>/<key>/ (default: ~/.martin/memo/)
|
|
11
|
+
* Invalidation: any file hash change produces a different key → automatic miss
|
|
12
|
+
* TTL: keys never expire (deterministic inputs → deterministic output is safe to reuse)
|
|
13
|
+
*/
|
|
14
|
+
export interface MemoInput {
|
|
15
|
+
/** Exact objective text. */
|
|
16
|
+
objective: string;
|
|
17
|
+
/** The verifier command string (e.g. "pnpm test"). */
|
|
18
|
+
verifierCommand: string;
|
|
19
|
+
/**
|
|
20
|
+
* Map of relevant file paths → their SHA-256 (or similar) hashes.
|
|
21
|
+
* Any change in any hash invalidates the cache key.
|
|
22
|
+
*/
|
|
23
|
+
fileHashes: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
export interface MemoEntry {
|
|
26
|
+
/** The SHA-256 cache key that produced this entry. */
|
|
27
|
+
key: string;
|
|
28
|
+
/** Unified diff text that was produced by the original attempt. */
|
|
29
|
+
patch: string;
|
|
30
|
+
/** Raw verifier output from the original successful attempt. */
|
|
31
|
+
verifierOutput: string;
|
|
32
|
+
/** ISO timestamp when the entry was stored. */
|
|
33
|
+
storedAt: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Computes the deterministic SHA-256 cache key for a given MemoInput.
|
|
37
|
+
*
|
|
38
|
+
* File hashes are sorted by path before hashing so that insertion order
|
|
39
|
+
* does not affect the key (same files → same key).
|
|
40
|
+
*
|
|
41
|
+
* verificationPlan step order IS significant (pass-order matters for verifiers).
|
|
42
|
+
*/
|
|
43
|
+
export declare function computeMemoKey(input: MemoInput): string;
|
|
44
|
+
/**
|
|
45
|
+
* Returns the stored MemoEntry for the given inputs, or null on a cache miss.
|
|
46
|
+
*
|
|
47
|
+
* A miss occurs when:
|
|
48
|
+
* - No entry exists for this key (never been run)
|
|
49
|
+
* - Any file hash changed (different key → automatic miss)
|
|
50
|
+
*/
|
|
51
|
+
export declare function lookupMemo(input: MemoInput, memoRoot?: string): Promise<MemoEntry | null>;
|
|
52
|
+
/**
|
|
53
|
+
* Persists a successful attempt result to the memo cache.
|
|
54
|
+
*
|
|
55
|
+
* Writes two files under <memoRoot>/<key>/:
|
|
56
|
+
* - entry.json — the full MemoEntry (patch + verifier output)
|
|
57
|
+
* - cache-hit.json — lightweight provenance artifact for audit trail
|
|
58
|
+
*
|
|
59
|
+
* The `entry.key` is set to the computed key before writing.
|
|
60
|
+
*/
|
|
61
|
+
export declare function storeMemo(input: MemoInput, entry: Omit<MemoEntry, "key"> & {
|
|
62
|
+
key?: string;
|
|
63
|
+
}, memoRoot?: string): Promise<string>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* memo.ts — SLICE-03
|
|
3
|
+
*
|
|
4
|
+
* Attempt memoization via objective + verifier + file hash key.
|
|
5
|
+
*
|
|
6
|
+
* On a cache HIT: the caller replays the stored patch with zero model calls.
|
|
7
|
+
* On a cache MISS: the caller runs the normal attempt path, then calls storeMemo.
|
|
8
|
+
*
|
|
9
|
+
* Cache key: SHA-256 of (objective, verifierCommand, sorted file path → hash pairs)
|
|
10
|
+
* Cache location: <memoRoot>/<key>/ (default: ~/.martin/memo/)
|
|
11
|
+
* Invalidation: any file hash change produces a different key → automatic miss
|
|
12
|
+
* TTL: keys never expire (deterministic inputs → deterministic output is safe to reuse)
|
|
13
|
+
*/
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Key computation
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
/**
|
|
22
|
+
* Computes the deterministic SHA-256 cache key for a given MemoInput.
|
|
23
|
+
*
|
|
24
|
+
* File hashes are sorted by path before hashing so that insertion order
|
|
25
|
+
* does not affect the key (same files → same key).
|
|
26
|
+
*
|
|
27
|
+
* verificationPlan step order IS significant (pass-order matters for verifiers).
|
|
28
|
+
*/
|
|
29
|
+
export function computeMemoKey(input) {
|
|
30
|
+
const sortedFiles = Object.entries(input.fileHashes)
|
|
31
|
+
.sort(([a], [b]) => a.localeCompare(b));
|
|
32
|
+
const canonical = JSON.stringify({
|
|
33
|
+
objective: input.objective,
|
|
34
|
+
verifierCommand: input.verifierCommand,
|
|
35
|
+
fileHashes: Object.fromEntries(sortedFiles)
|
|
36
|
+
});
|
|
37
|
+
return createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
38
|
+
}
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// Lookup
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
/**
|
|
43
|
+
* Returns the stored MemoEntry for the given inputs, or null on a cache miss.
|
|
44
|
+
*
|
|
45
|
+
* A miss occurs when:
|
|
46
|
+
* - No entry exists for this key (never been run)
|
|
47
|
+
* - Any file hash changed (different key → automatic miss)
|
|
48
|
+
*/
|
|
49
|
+
export async function lookupMemo(input, memoRoot = defaultMemoRoot()) {
|
|
50
|
+
const key = computeMemoKey(input);
|
|
51
|
+
const entryPath = join(memoRoot, key, "entry.json");
|
|
52
|
+
try {
|
|
53
|
+
const raw = await readFile(entryPath, "utf8");
|
|
54
|
+
return JSON.parse(raw);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Store
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
/**
|
|
64
|
+
* Persists a successful attempt result to the memo cache.
|
|
65
|
+
*
|
|
66
|
+
* Writes two files under <memoRoot>/<key>/:
|
|
67
|
+
* - entry.json — the full MemoEntry (patch + verifier output)
|
|
68
|
+
* - cache-hit.json — lightweight provenance artifact for audit trail
|
|
69
|
+
*
|
|
70
|
+
* The `entry.key` is set to the computed key before writing.
|
|
71
|
+
*/
|
|
72
|
+
export async function storeMemo(input, entry, memoRoot = defaultMemoRoot()) {
|
|
73
|
+
const key = computeMemoKey(input);
|
|
74
|
+
const keyDir = join(memoRoot, key);
|
|
75
|
+
await mkdir(keyDir, { recursive: true });
|
|
76
|
+
const fullEntry = { ...entry, key };
|
|
77
|
+
// Write the main entry
|
|
78
|
+
await writeFile(join(keyDir, "entry.json"), `${JSON.stringify(fullEntry, null, 2)}\n`, "utf8");
|
|
79
|
+
// Write the provenance artifact (lightweight, safe to read without loading full patch)
|
|
80
|
+
const provenance = {
|
|
81
|
+
key,
|
|
82
|
+
objective: input.objective,
|
|
83
|
+
verifierCommand: input.verifierCommand,
|
|
84
|
+
fileCount: Object.keys(input.fileHashes).length,
|
|
85
|
+
storedAt: entry.storedAt
|
|
86
|
+
};
|
|
87
|
+
await writeFile(join(keyDir, "cache-hit.json"), `${JSON.stringify(provenance, null, 2)}\n`, "utf8");
|
|
88
|
+
return key;
|
|
89
|
+
}
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Internals
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
function defaultMemoRoot() {
|
|
94
|
+
return process.env["MARTIN_MEMO_DIR"]?.trim()
|
|
95
|
+
?? join(homedir(), ".martin", "memo");
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=memo.js.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* learning-pipeline.ts
|
|
3
|
+
*
|
|
4
|
+
* Guarded promotion pipeline for self-improving memory heuristics.
|
|
5
|
+
*
|
|
6
|
+
* Flow: candidate → shadow test → holdout validation → approval gate → promoted
|
|
7
|
+
*
|
|
8
|
+
* A learning candidate NEVER becomes an active heuristic without passing:
|
|
9
|
+
* 1. Shadow tests (runs in parallel with existing logic, results compared)
|
|
10
|
+
* 2. Holdout validation (tested on a held-out set, not the training data)
|
|
11
|
+
* 3. Approval gate (auto-approved if confidence >= threshold, else requires manual sign-off)
|
|
12
|
+
*
|
|
13
|
+
* Fail-open: any pipeline error keeps the existing heuristic active untouched.
|
|
14
|
+
*/
|
|
15
|
+
export type CandidateStatus = "pending_shadow" | "shadow_passed" | "shadow_failed" | "holdout_passed" | "holdout_failed" | "approved" | "manual_review" | "promoted" | "blocked_protected_surface" | "rejected" | "rolled_back";
|
|
16
|
+
export interface LearningCandidateProvenance {
|
|
17
|
+
sourceType: "trace-intelligence" | "manual" | "memory-palace";
|
|
18
|
+
patternId?: string;
|
|
19
|
+
evidenceRunIds?: string[];
|
|
20
|
+
generatedAt: string;
|
|
21
|
+
confidenceScore?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface LearningCandidateRollbackPlan {
|
|
24
|
+
strategy: "restore_last_known_good" | "disable_candidate";
|
|
25
|
+
note?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface LearningCandidate {
|
|
28
|
+
candidateId: string;
|
|
29
|
+
sourceRunId: string;
|
|
30
|
+
heuristicFamily: string;
|
|
31
|
+
description: string;
|
|
32
|
+
payload: Record<string, unknown>;
|
|
33
|
+
status: CandidateStatus;
|
|
34
|
+
provenance?: LearningCandidateProvenance;
|
|
35
|
+
proposedPaths?: string[];
|
|
36
|
+
rollbackPlan?: LearningCandidateRollbackPlan;
|
|
37
|
+
shadowPassRate?: number;
|
|
38
|
+
holdoutPassRate?: number;
|
|
39
|
+
approvedAt?: string;
|
|
40
|
+
rejectedAt?: string;
|
|
41
|
+
rejectedReason?: string;
|
|
42
|
+
createdAt: string;
|
|
43
|
+
}
|
|
44
|
+
export interface ShadowTestResult {
|
|
45
|
+
candidateId: string;
|
|
46
|
+
totalCases: number;
|
|
47
|
+
passedCases: number;
|
|
48
|
+
passRate: number;
|
|
49
|
+
passed: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface HoldoutResult {
|
|
52
|
+
candidateId: string;
|
|
53
|
+
holdoutSize: number;
|
|
54
|
+
passedCases: number;
|
|
55
|
+
passRate: number;
|
|
56
|
+
passed: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface ApprovalGateConfig {
|
|
59
|
+
/** Auto-approve when holdout pass rate meets or exceeds this threshold (0–1). Default: 0.85 */
|
|
60
|
+
autoApproveThreshold: number;
|
|
61
|
+
/** If true, always require manual sign-off regardless of pass rate */
|
|
62
|
+
requireManualApproval: boolean;
|
|
63
|
+
}
|
|
64
|
+
export interface PromotionResult {
|
|
65
|
+
candidateId: string;
|
|
66
|
+
promoted: boolean;
|
|
67
|
+
reason: string;
|
|
68
|
+
}
|
|
69
|
+
export interface ActiveLearningHeuristics {
|
|
70
|
+
schemaVersion: "1.0";
|
|
71
|
+
updatedAt: string;
|
|
72
|
+
recallScoring: {
|
|
73
|
+
failureClassWeight: number;
|
|
74
|
+
interventionWeight: number;
|
|
75
|
+
tokenOverlapWeight: number;
|
|
76
|
+
completionWeight: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export interface LearningPipelineSummaryEntry {
|
|
80
|
+
candidateId: string;
|
|
81
|
+
heuristicFamily: string;
|
|
82
|
+
status: CandidateStatus;
|
|
83
|
+
sourceRunId: string;
|
|
84
|
+
createdAt: string;
|
|
85
|
+
confidenceScore?: number;
|
|
86
|
+
summary: string;
|
|
87
|
+
patternId?: string;
|
|
88
|
+
}
|
|
89
|
+
export interface LearningPipelineSummary {
|
|
90
|
+
counts: {
|
|
91
|
+
proposed: number;
|
|
92
|
+
shadowPassed: number;
|
|
93
|
+
holdoutPassed: number;
|
|
94
|
+
approved: number;
|
|
95
|
+
promoted: number;
|
|
96
|
+
rejected: number;
|
|
97
|
+
blockedProtectedSurface: number;
|
|
98
|
+
rolledBack: number;
|
|
99
|
+
};
|
|
100
|
+
protectedPaths: string[];
|
|
101
|
+
entries: LearningPipelineSummaryEntry[];
|
|
102
|
+
}
|
|
103
|
+
export declare const DEFAULT_PROTECTED_LEARNING_PATHS: readonly ["packages/core/src/leash.ts", "packages/core/src/leash/**", "packages/core/src/policy.ts", "packages/core/src/grounding.ts", "packages/core/src/security/**", "packages/core/src/attestation/**", "packages/contracts/src/**", "packages/mcp/src/**", "packages/cli/src/index.ts", "docs/release/**", "docs/security/**", "docs/handoffs/**", ".planning/**", "scripts/**"];
|
|
104
|
+
export declare const DEFAULT_ACTIVE_LEARNING_HEURISTICS: ActiveLearningHeuristics;
|
|
105
|
+
/**
|
|
106
|
+
* Runs the candidate heuristic in shadow mode alongside the existing logic.
|
|
107
|
+
* The shadow result is compared but never used to change actual behaviour.
|
|
108
|
+
*
|
|
109
|
+
* `existingScores` and `candidateScores` are parallel arrays — one per recall query.
|
|
110
|
+
* A case is "passed" when the candidate score >= existing score (does not regress).
|
|
111
|
+
*/
|
|
112
|
+
export declare function runShadowTest(candidateId: string, existingScores: number[], candidateScores: number[]): ShadowTestResult;
|
|
113
|
+
/**
|
|
114
|
+
* Validates the candidate against a held-out set (data not used to build the candidate).
|
|
115
|
+
* The holdout set is scored by both the existing and candidate heuristic;
|
|
116
|
+
* the candidate passes if it meets or exceeds the existing on >= 80% of cases.
|
|
117
|
+
*/
|
|
118
|
+
export declare function runHoldoutValidation(candidateId: string, holdoutExistingScores: number[], holdoutCandidateScores: number[]): HoldoutResult;
|
|
119
|
+
/**
|
|
120
|
+
* Determines whether a candidate can be promoted based on holdout results and
|
|
121
|
+
* the configured approval policy.
|
|
122
|
+
*
|
|
123
|
+
* Auto-approves when:
|
|
124
|
+
* - holdout passed AND holdoutPassRate >= autoApproveThreshold
|
|
125
|
+
* - AND requireManualApproval is false
|
|
126
|
+
*
|
|
127
|
+
* In all other cases, the candidate is held at "holdout_passed" status and
|
|
128
|
+
* awaits manual sign-off (future: webhook / dashboard approval flow).
|
|
129
|
+
*/
|
|
130
|
+
export declare function evaluateApprovalGate(holdout: HoldoutResult, config?: ApprovalGateConfig): {
|
|
131
|
+
approved: boolean;
|
|
132
|
+
reason: string;
|
|
133
|
+
};
|
|
134
|
+
export declare function persistCandidate(candidate: LearningCandidate, pipelineRoot: string): Promise<void>;
|
|
135
|
+
export declare function loadCandidate(candidateId: string, pipelineRoot: string): Promise<LearningCandidate | undefined>;
|
|
136
|
+
export declare function loadActiveLearningHeuristics(pipelineRoot: string): Promise<ActiveLearningHeuristics>;
|
|
137
|
+
export declare function summarizeLearningPipeline(pipelineRoot: string): Promise<LearningPipelineSummary>;
|
|
138
|
+
export interface PipelineInput {
|
|
139
|
+
candidate: LearningCandidate;
|
|
140
|
+
existingScores: number[];
|
|
141
|
+
candidateScores: number[];
|
|
142
|
+
holdoutExistingScores: number[];
|
|
143
|
+
holdoutCandidateScores: number[];
|
|
144
|
+
approvalConfig?: ApprovalGateConfig;
|
|
145
|
+
pipelineRoot: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Runs the full guarded promotion pipeline:
|
|
149
|
+
* shadow → holdout → approval gate → persist result.
|
|
150
|
+
*
|
|
151
|
+
* Returns whether the candidate was promoted to "approved" status.
|
|
152
|
+
* Never throws — any error is caught and candidate is marked rejected.
|
|
153
|
+
*/
|
|
154
|
+
export declare function runPromotionPipeline(input: PipelineInput): Promise<PromotionResult>;
|