agentic-flow 2.0.14 → 2.1.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 +87 -0
- package/README.md +376 -186
- package/dist/.tsbuildinfo +1 -1
- package/dist/agent-booster/index.d.ts +14 -0
- package/dist/agent-booster/index.d.ts.map +1 -0
- package/dist/agent-booster/index.js +25 -0
- package/dist/agent-booster/index.js.map +1 -0
- package/dist/core/embedding-service.js +1 -1
- package/dist/core/embedding-service.js.map +1 -1
- package/dist/embeddings/optimized-embedder.js +1 -1
- package/dist/embeddings/optimized-embedder.js.map +1 -1
- package/dist/harness/flywheel-governance.d.ts +85 -0
- package/dist/harness/flywheel-governance.d.ts.map +1 -0
- package/dist/harness/flywheel-governance.js +123 -0
- package/dist/harness/flywheel-governance.js.map +1 -0
- package/dist/harness/metaharness.d.ts +6 -0
- package/dist/harness/metaharness.d.ts.map +1 -0
- package/dist/harness/metaharness.js +6 -0
- package/dist/harness/metaharness.js.map +1 -0
- package/dist/harness/provenance.d.ts +72 -0
- package/dist/harness/provenance.d.ts.map +1 -0
- package/dist/harness/provenance.js +92 -0
- package/dist/harness/provenance.js.map +1 -0
- package/dist/mcp/mcp-policy.d.ts +15 -0
- package/dist/mcp/mcp-policy.d.ts.map +1 -0
- package/dist/mcp/mcp-policy.js +36 -0
- package/dist/mcp/mcp-policy.js.map +1 -0
- package/dist/mcp/standalone-stdio.js +11 -0
- package/dist/mcp/standalone-stdio.js.map +1 -1
- package/dist/mcp/tools/harness-tools.d.ts +29 -0
- package/dist/mcp/tools/harness-tools.d.ts.map +1 -0
- package/dist/mcp/tools/harness-tools.js +102 -0
- package/dist/mcp/tools/harness-tools.js.map +1 -0
- package/dist/optimizations/configuration-tuning.d.ts +1 -1
- package/dist/reasoningbank/utils/embeddings.js +2 -2
- package/dist/reasoningbank/utils/embeddings.js.map +1 -1
- package/dist/repair/cli.d.ts +24 -0
- package/dist/repair/cli.d.ts.map +1 -0
- package/dist/repair/cli.js +69 -0
- package/dist/repair/cli.js.map +1 -0
- package/dist/repair/darwin-repair.d.ts +92 -0
- package/dist/repair/darwin-repair.d.ts.map +1 -0
- package/dist/repair/darwin-repair.js +81 -0
- package/dist/repair/darwin-repair.js.map +1 -0
- package/dist/router/cost-optimal-router.d.ts +89 -0
- package/dist/router/cost-optimal-router.d.ts.map +1 -0
- package/dist/router/cost-optimal-router.js +94 -0
- package/dist/router/cost-optimal-router.js.map +1 -0
- package/dist/router/providers/onnx.js +2 -2
- package/dist/router/providers/onnx.js.map +1 -1
- package/dist/router/router.d.ts +29 -0
- package/dist/router/router.d.ts.map +1 -1
- package/dist/router/router.js +83 -0
- package/dist/router/router.js.map +1 -1
- package/dist/router/types.d.ts +1 -1
- package/dist/router/types.d.ts.map +1 -1
- package/dist/router/types.js.map +1 -1
- package/dist/services/embedding-service.js +1 -1
- package/dist/services/embedding-service.js.map +1 -1
- package/dist/transport/quic-loader.d.ts +33 -0
- package/dist/transport/quic-loader.d.ts.map +1 -1
- package/dist/transport/quic-loader.js +95 -0
- package/dist/transport/quic-loader.js.map +1 -1
- package/dist/utils/model-cache.js +1 -1
- package/dist/utils/model-cache.js.map +1 -1
- package/package.json +27 -11
- package/wasm/reasoningbank/reasoningbank_wasm_bg.js +31 -31
- package/wasm/reasoningbank/reasoningbank_wasm_bg.wasm +0 -0
- package/wasm/reasoningbank/reasoningbank_wasm_bg.wasm.d.ts +2 -2
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trusted evaluation/promotion boundary (ADR-077).
|
|
3
|
+
* Candidate jobs create evidence; only a trusted signer plus an atomic CAS can promote.
|
|
4
|
+
*/
|
|
5
|
+
import { createHash, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';
|
|
6
|
+
import { evaluateMetaHarnessPromotion } from './metaharness.js';
|
|
7
|
+
function canonical(value) {
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
10
|
+
if (value && typeof value === 'object') {
|
|
11
|
+
const object = value;
|
|
12
|
+
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonical(object[key])}`).join(',')}}`;
|
|
13
|
+
}
|
|
14
|
+
return JSON.stringify(value);
|
|
15
|
+
}
|
|
16
|
+
export function governanceHash(value) {
|
|
17
|
+
return createHash('sha256').update(canonical(value)).digest('hex');
|
|
18
|
+
}
|
|
19
|
+
function assertDigest(name, digest) {
|
|
20
|
+
if (!/^[a-f0-9]{64}$/.test(digest))
|
|
21
|
+
throw new TypeError(`${name} must be a lowercase sha256 digest`);
|
|
22
|
+
}
|
|
23
|
+
export function createBenchmarkAnchor(input) {
|
|
24
|
+
for (const field of ['corpusHash', 'embeddingSpaceHash', 'indexTopologyHash', 'evaluatorHash']) {
|
|
25
|
+
assertDigest(field, input[field]);
|
|
26
|
+
}
|
|
27
|
+
if (!input.project.trim())
|
|
28
|
+
throw new TypeError('project is required');
|
|
29
|
+
return { version: 1, ...input };
|
|
30
|
+
}
|
|
31
|
+
export function createEvaluationReceipt(input) {
|
|
32
|
+
assertDigest('candidateHash', input.candidateHash);
|
|
33
|
+
assertDigest('gateFingerprint', input.gateFingerprint);
|
|
34
|
+
if (!input.experimentRevision.trim() || !input.candidateCommit.trim() || !input.baselineGeneration.trim()) {
|
|
35
|
+
throw new TypeError('experimentRevision, candidateCommit, and baselineGeneration are required');
|
|
36
|
+
}
|
|
37
|
+
if (!Number.isFinite(Date.parse(input.createdAt)))
|
|
38
|
+
throw new TypeError('createdAt must be an ISO timestamp');
|
|
39
|
+
return { version: 1, ...input, anchorHash: governanceHash(input.anchor) };
|
|
40
|
+
}
|
|
41
|
+
export async function evaluateCandidate(receipt) {
|
|
42
|
+
if (receipt.anchorHash !== governanceHash(receipt.anchor))
|
|
43
|
+
throw new Error('benchmark anchor hash mismatch');
|
|
44
|
+
const decision = await evaluateMetaHarnessPromotion({
|
|
45
|
+
baseline: receipt.baseline,
|
|
46
|
+
candidate: receipt.candidate,
|
|
47
|
+
...(receipt.anchorMetric ? { anchor: receipt.anchorMetric } : {}),
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
version: 1,
|
|
51
|
+
receipt,
|
|
52
|
+
receiptHash: governanceHash(receipt),
|
|
53
|
+
promote: decision.promote,
|
|
54
|
+
reasons: [...decision.reasons],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function authorizationPayload(auth) {
|
|
58
|
+
return canonical(auth);
|
|
59
|
+
}
|
|
60
|
+
export function authorizePromotion(proposal, signer, expiresAt, privateKey, publicKey) {
|
|
61
|
+
if (!proposal.promote)
|
|
62
|
+
throw new Error('cannot authorize a rejected or inconclusive proposal');
|
|
63
|
+
const unsigned = {
|
|
64
|
+
version: 1,
|
|
65
|
+
receiptHash: proposal.receiptHash,
|
|
66
|
+
candidateCommit: proposal.receipt.candidateCommit,
|
|
67
|
+
expiresAt,
|
|
68
|
+
signer,
|
|
69
|
+
};
|
|
70
|
+
const signature = cryptoSign(null, Buffer.from(authorizationPayload(unsigned)), privateKey).toString('base64');
|
|
71
|
+
return { ...unsigned, signature, publicKey };
|
|
72
|
+
}
|
|
73
|
+
export function verifyPromotionAuthorization(proposal, authorization, now = new Date()) {
|
|
74
|
+
const expiry = Date.parse(authorization.expiresAt);
|
|
75
|
+
if (!proposal.promote || authorization.receiptHash !== proposal.receiptHash ||
|
|
76
|
+
authorization.candidateCommit !== proposal.receipt.candidateCommit ||
|
|
77
|
+
!Number.isFinite(expiry) || expiry <= now.getTime())
|
|
78
|
+
return false;
|
|
79
|
+
const { signature, publicKey, ...unsigned } = authorization;
|
|
80
|
+
try {
|
|
81
|
+
return cryptoVerify(null, Buffer.from(authorizationPayload(unsigned)), publicKey, Buffer.from(signature, 'base64'));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function promoteCandidate(proposal, authorization, store, options) {
|
|
88
|
+
const now = options.now ?? new Date();
|
|
89
|
+
if (!options.trustedPublicKeys.has(authorization.publicKey))
|
|
90
|
+
throw new Error('untrusted promotion signer');
|
|
91
|
+
if (!verifyPromotionAuthorization(proposal, authorization, now))
|
|
92
|
+
throw new Error('invalid promotion authorization');
|
|
93
|
+
if (proposal.receiptHash !== governanceHash(proposal.receipt))
|
|
94
|
+
throw new Error('evaluation receipt hash mismatch');
|
|
95
|
+
const result = await store.compareAndSwap(proposal.receipt.baselineGeneration, proposal.receipt.candidateHash, proposal.receiptHash);
|
|
96
|
+
if (!result.promoted)
|
|
97
|
+
throw new Error('promotion race: baseline generation changed');
|
|
98
|
+
return {
|
|
99
|
+
version: 1,
|
|
100
|
+
receiptHash: proposal.receiptHash,
|
|
101
|
+
candidateCommit: proposal.receipt.candidateCommit,
|
|
102
|
+
candidateHash: proposal.receipt.candidateHash,
|
|
103
|
+
previousGeneration: proposal.receipt.baselineGeneration,
|
|
104
|
+
generation: result.generation,
|
|
105
|
+
signer: authorization.signer,
|
|
106
|
+
promotedAt: now.toISOString(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export function verifyGovernanceReplay(bundle, options) {
|
|
110
|
+
const reasons = [];
|
|
111
|
+
if (bundle.proposal.receiptHash !== governanceHash(bundle.proposal.receipt))
|
|
112
|
+
reasons.push('receipt hash mismatch');
|
|
113
|
+
if (!options.trustedPublicKeys.has(bundle.authorization.publicKey))
|
|
114
|
+
reasons.push('untrusted signer');
|
|
115
|
+
if (!verifyPromotionAuthorization(bundle.proposal, bundle.authorization, options.now))
|
|
116
|
+
reasons.push('invalid authorization');
|
|
117
|
+
if (bundle.promotion && (bundle.promotion.receiptHash !== bundle.proposal.receiptHash ||
|
|
118
|
+
bundle.promotion.candidateHash !== bundle.proposal.receipt.candidateHash)) {
|
|
119
|
+
reasons.push('promotion lineage mismatch');
|
|
120
|
+
}
|
|
121
|
+
return { valid: reasons.length === 0, reasons };
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=flywheel-governance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flywheel-governance.js","sourceRoot":"","sources":["../../src/harness/flywheel-governance.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Trusted evaluation/promotion boundary (ADR-077).\n * Candidate jobs create evidence; only a trusted signer plus an atomic CAS can promote.\n */\nimport { createHash, sign as cryptoSign, verify as cryptoVerify } from 'node:crypto';\nimport { evaluateMetaHarnessPromotion } from './metaharness.js';\n\nexport interface BenchmarkAnchor {\n version: 1;\n project: string;\n corpusHash: string;\n embeddingSpaceHash: string;\n indexTopologyHash: string;\n evaluatorHash: string;\n}\nexport interface PromotionMetrics {\n primary: number;\n noopRate: number;\n costPerWin: number;\n regressed?: boolean;\n}\nexport interface EvaluationReceipt {\n version: 1;\n experimentRevision: string;\n candidateCommit: string;\n candidateHash: string;\n baselineGeneration: string;\n anchor: BenchmarkAnchor;\n anchorHash: string;\n gateFingerprint: string;\n baseline: PromotionMetrics;\n candidate: PromotionMetrics;\n anchorMetric?: { baseline: number; candidate: number };\n createdAt: string;\n}\nexport interface PromotionProposal {\n version: 1;\n receipt: EvaluationReceipt;\n receiptHash: string;\n promote: boolean;\n reasons: string[];\n}\nexport interface PromotionAuthorization {\n version: 1;\n receiptHash: string;\n candidateCommit: string;\n expiresAt: string;\n signer: string;\n signature: string;\n publicKey: string;\n}\nexport interface AtomicPromotionStore {\n compareAndSwap(expectedGeneration: string, candidateHash: string, evidenceHash: string):\n Promise<{ promoted: boolean; generation: string }>;\n}\nexport interface PromotionRecord {\n version: 1;\n receiptHash: string;\n candidateCommit: string;\n candidateHash: string;\n previousGeneration: string;\n generation: string;\n signer: string;\n promotedAt: string;\n}\n\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;\n if (value && typeof value === 'object') {\n const object = value as Record<string, unknown>;\n return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${canonical(object[key])}`).join(',')}}`;\n }\n return JSON.stringify(value);\n}\nexport function governanceHash(value: unknown): string {\n return createHash('sha256').update(canonical(value)).digest('hex');\n}\nfunction assertDigest(name: string, digest: string): void {\n if (!/^[a-f0-9]{64}$/.test(digest)) throw new TypeError(`${name} must be a lowercase sha256 digest`);\n}\nexport function createBenchmarkAnchor(input: Omit<BenchmarkAnchor, 'version'>): BenchmarkAnchor {\n for (const field of ['corpusHash', 'embeddingSpaceHash', 'indexTopologyHash', 'evaluatorHash'] as const) {\n assertDigest(field, input[field]);\n }\n if (!input.project.trim()) throw new TypeError('project is required');\n return { version: 1, ...input };\n}\nexport function createEvaluationReceipt(input: Omit<EvaluationReceipt, 'version' | 'anchorHash'>): EvaluationReceipt {\n assertDigest('candidateHash', input.candidateHash);\n assertDigest('gateFingerprint', input.gateFingerprint);\n if (!input.experimentRevision.trim() || !input.candidateCommit.trim() || !input.baselineGeneration.trim()) {\n throw new TypeError('experimentRevision, candidateCommit, and baselineGeneration are required');\n }\n if (!Number.isFinite(Date.parse(input.createdAt))) throw new TypeError('createdAt must be an ISO timestamp');\n return { version: 1, ...input, anchorHash: governanceHash(input.anchor) };\n}\nexport async function evaluateCandidate(receipt: EvaluationReceipt): Promise<PromotionProposal> {\n if (receipt.anchorHash !== governanceHash(receipt.anchor)) throw new Error('benchmark anchor hash mismatch');\n const decision = await evaluateMetaHarnessPromotion({\n baseline: receipt.baseline,\n candidate: receipt.candidate,\n ...(receipt.anchorMetric ? { anchor: receipt.anchorMetric } : {}),\n });\n return {\n version: 1,\n receipt,\n receiptHash: governanceHash(receipt),\n promote: decision.promote,\n reasons: [...decision.reasons],\n };\n}\nfunction authorizationPayload(auth: Omit<PromotionAuthorization, 'signature' | 'publicKey'>): string {\n return canonical(auth);\n}\nexport function authorizePromotion(\n proposal: PromotionProposal,\n signer: string,\n expiresAt: string,\n privateKey: string,\n publicKey: string,\n): PromotionAuthorization {\n if (!proposal.promote) throw new Error('cannot authorize a rejected or inconclusive proposal');\n const unsigned = {\n version: 1 as const,\n receiptHash: proposal.receiptHash,\n candidateCommit: proposal.receipt.candidateCommit,\n expiresAt,\n signer,\n };\n const signature = cryptoSign(null, Buffer.from(authorizationPayload(unsigned)), privateKey).toString('base64');\n return { ...unsigned, signature, publicKey };\n}\nexport function verifyPromotionAuthorization(\n proposal: PromotionProposal,\n authorization: PromotionAuthorization,\n now = new Date(),\n): boolean {\n const expiry = Date.parse(authorization.expiresAt);\n if (!proposal.promote || authorization.receiptHash !== proposal.receiptHash ||\n authorization.candidateCommit !== proposal.receipt.candidateCommit ||\n !Number.isFinite(expiry) || expiry <= now.getTime()) return false;\n const { signature, publicKey, ...unsigned } = authorization;\n try {\n return cryptoVerify(null, Buffer.from(authorizationPayload(unsigned)), publicKey, Buffer.from(signature, 'base64'));\n } catch {\n return false;\n }\n}\nexport async function promoteCandidate(\n proposal: PromotionProposal,\n authorization: PromotionAuthorization,\n store: AtomicPromotionStore,\n options: { now?: Date; trustedPublicKeys: ReadonlySet<string> },\n): Promise<PromotionRecord> {\n const now = options.now ?? new Date();\n if (!options.trustedPublicKeys.has(authorization.publicKey)) throw new Error('untrusted promotion signer');\n if (!verifyPromotionAuthorization(proposal, authorization, now)) throw new Error('invalid promotion authorization');\n if (proposal.receiptHash !== governanceHash(proposal.receipt)) throw new Error('evaluation receipt hash mismatch');\n const result = await store.compareAndSwap(\n proposal.receipt.baselineGeneration,\n proposal.receipt.candidateHash,\n proposal.receiptHash,\n );\n if (!result.promoted) throw new Error('promotion race: baseline generation changed');\n return {\n version: 1,\n receiptHash: proposal.receiptHash,\n candidateCommit: proposal.receipt.candidateCommit,\n candidateHash: proposal.receipt.candidateHash,\n previousGeneration: proposal.receipt.baselineGeneration,\n generation: result.generation,\n signer: authorization.signer,\n promotedAt: now.toISOString(),\n };\n}\nexport function verifyGovernanceReplay(\n bundle: { proposal: PromotionProposal; authorization: PromotionAuthorization; promotion?: PromotionRecord },\n options: { now?: Date; trustedPublicKeys: ReadonlySet<string> },\n): { valid: boolean; reasons: string[] } {\n const reasons: string[] = [];\n if (bundle.proposal.receiptHash !== governanceHash(bundle.proposal.receipt)) reasons.push('receipt hash mismatch');\n if (!options.trustedPublicKeys.has(bundle.authorization.publicKey)) reasons.push('untrusted signer');\n if (!verifyPromotionAuthorization(bundle.proposal, bundle.authorization, options.now)) reasons.push('invalid authorization');\n if (bundle.promotion && (bundle.promotion.receiptHash !== bundle.proposal.receiptHash ||\n bundle.promotion.candidateHash !== bundle.proposal.receipt.candidateHash)) {\n reasons.push('promotion lineage mismatch');\n }\n return { valid: reasons.length === 0, reasons };\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single lazy integration boundary for ruvector's pinned MetaHarness stack.
|
|
3
|
+
* Ordinary agentic-flow imports do not initialize research infrastructure.
|
|
4
|
+
*/
|
|
5
|
+
export { METAHARNESS_VERSIONS, assertMetaHarnessSafePayload, evaluateMetaHarnessPromotion, getMetaHarnessCapabilities, runMetaHarnessDarwin, runMetaHarnessFlywheel, scanMetaHarnessRewardHacks, verifyMetaHarnessReplay, } from 'ruvector';
|
|
6
|
+
//# sourceMappingURL=metaharness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metaharness.d.ts","sourceRoot":"","sources":["../../src/harness/metaharness.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,EAC5B,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The single lazy integration boundary for ruvector's pinned MetaHarness stack.
|
|
3
|
+
* Ordinary agentic-flow imports do not initialize research infrastructure.
|
|
4
|
+
*/
|
|
5
|
+
export { METAHARNESS_VERSIONS, assertMetaHarnessSafePayload, evaluateMetaHarnessPromotion, getMetaHarnessCapabilities, runMetaHarnessDarwin, runMetaHarnessFlywheel, scanMetaHarnessRewardHacks, verifyMetaHarnessReplay, } from 'ruvector';
|
|
6
|
+
//# sourceMappingURL=metaharness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metaharness.js","sourceRoot":"","sources":["../../src/harness/metaharness.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,oBAAoB,EACpB,4BAA4B,EAC5B,4BAA4B,EAC5B,0BAA0B,EAC1B,oBAAoB,EACpB,sBAAsB,EACtB,0BAA0B,EAC1B,uBAAuB,GACxB,MAAM,UAAU,CAAC","sourcesContent":["/**\n * The single lazy integration boundary for ruvector's pinned MetaHarness stack.\n * Ordinary agentic-flow imports do not initialize research infrastructure.\n */\nexport {\n METAHARNESS_VERSIONS,\n assertMetaHarnessSafePayload,\n evaluateMetaHarnessPromotion,\n getMetaHarnessCapabilities,\n runMetaHarnessDarwin,\n runMetaHarnessFlywheel,\n scanMetaHarnessRewardHacks,\n verifyMetaHarnessReplay,\n} from 'ruvector';\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness provenance — Ed25519 witness manifest over harness/agent config files
|
|
3
|
+
* (ADR-075, Track B).
|
|
4
|
+
*
|
|
5
|
+
* Produces a tamper-evident manifest (sha256 per file) and signs it with Ed25519
|
|
6
|
+
* (Node's built-in `crypto` — no external deps), complementing the CWE-78
|
|
7
|
+
* hardening: a signed manifest lets a consumer verify that the agent/skill/policy
|
|
8
|
+
* files a harness ships are exactly the ones that were reviewed.
|
|
9
|
+
*
|
|
10
|
+
* Deterministic by construction (no wall-clock inside): the caller supplies any
|
|
11
|
+
* timestamp, and entries are sorted, so signing the same files yields the same
|
|
12
|
+
* payload.
|
|
13
|
+
*
|
|
14
|
+
* @see docs/adr/ADR-075-metaharness-harness-evolution-and-provenance.md
|
|
15
|
+
*/
|
|
16
|
+
export interface ManifestEntry {
|
|
17
|
+
/** File path as supplied (used as the stable sort/identity key). */
|
|
18
|
+
path: string;
|
|
19
|
+
/** Lowercase hex sha256 of the file contents. */
|
|
20
|
+
sha256: string;
|
|
21
|
+
}
|
|
22
|
+
export interface HarnessManifest {
|
|
23
|
+
version: 1;
|
|
24
|
+
/** Optional caller-supplied timestamp (ISO string). Part of the signed payload. */
|
|
25
|
+
createdAt?: string;
|
|
26
|
+
/** File digests, sorted by path for a canonical, reproducible payload. */
|
|
27
|
+
entries: ManifestEntry[];
|
|
28
|
+
}
|
|
29
|
+
export interface KeyPairPem {
|
|
30
|
+
publicKey: string;
|
|
31
|
+
privateKey: string;
|
|
32
|
+
}
|
|
33
|
+
/** Generate an Ed25519 keypair as PEM strings (spki/pkcs8). */
|
|
34
|
+
export declare function generateKeyPairPem(): KeyPairPem;
|
|
35
|
+
/** sha256 (hex) of a buffer. */
|
|
36
|
+
export declare function sha256Hex(data: Buffer | string): string;
|
|
37
|
+
/**
|
|
38
|
+
* Build a canonical manifest over the given files. Entries are sorted by path so
|
|
39
|
+
* the signed payload is stable regardless of input order. `createdAt` is included
|
|
40
|
+
* verbatim if provided.
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildManifest(files: string[], opts?: {
|
|
43
|
+
createdAt?: string;
|
|
44
|
+
}): HarnessManifest;
|
|
45
|
+
/** Sign a manifest with an Ed25519 private-key PEM. Returns a base64 signature. */
|
|
46
|
+
export declare function signManifest(manifest: HarnessManifest, privateKeyPem: string): string;
|
|
47
|
+
/** Verify a base64 Ed25519 signature over a manifest with a public-key PEM. */
|
|
48
|
+
export declare function verifyManifest(manifest: HarnessManifest, signatureBase64: string, publicKeyPem: string): boolean;
|
|
49
|
+
export interface SignedManifest {
|
|
50
|
+
manifest: HarnessManifest;
|
|
51
|
+
signature: string;
|
|
52
|
+
publicKey: string;
|
|
53
|
+
}
|
|
54
|
+
/** Convenience: build + sign in one step, returning a portable signed bundle. */
|
|
55
|
+
export declare function signFiles(files: string[], privateKeyPem: string, publicKeyPem: string, opts?: {
|
|
56
|
+
createdAt?: string;
|
|
57
|
+
}): SignedManifest;
|
|
58
|
+
/**
|
|
59
|
+
* Verify a signed bundle AND that the on-disk files still match the manifest
|
|
60
|
+
* digests. Returns a per-file drift report so a caller can see exactly what
|
|
61
|
+
* changed since signing.
|
|
62
|
+
*/
|
|
63
|
+
export declare function verifySignedManifest(signed: SignedManifest): {
|
|
64
|
+
signatureValid: boolean;
|
|
65
|
+
filesIntact: boolean;
|
|
66
|
+
drift: {
|
|
67
|
+
path: string;
|
|
68
|
+
expected: string;
|
|
69
|
+
actual: string | null;
|
|
70
|
+
}[];
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=provenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../src/harness/provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,CAAC,CAAC;IACX,mFAAmF;IACnF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,+DAA+D;AAC/D,wBAAgB,kBAAkB,IAAI,UAAU,CAM/C;AAED,gCAAgC;AAChC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAEvD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,eAAe,CAKjG;AAYD,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAIrF;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAOhH;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,cAAc,CAGzI;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,cAAc,GAAG;IAC5D,cAAc,EAAE,OAAO,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,EAAE,CAAC;CACpE,CAaA"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness provenance — Ed25519 witness manifest over harness/agent config files
|
|
3
|
+
* (ADR-075, Track B).
|
|
4
|
+
*
|
|
5
|
+
* Produces a tamper-evident manifest (sha256 per file) and signs it with Ed25519
|
|
6
|
+
* (Node's built-in `crypto` — no external deps), complementing the CWE-78
|
|
7
|
+
* hardening: a signed manifest lets a consumer verify that the agent/skill/policy
|
|
8
|
+
* files a harness ships are exactly the ones that were reviewed.
|
|
9
|
+
*
|
|
10
|
+
* Deterministic by construction (no wall-clock inside): the caller supplies any
|
|
11
|
+
* timestamp, and entries are sorted, so signing the same files yields the same
|
|
12
|
+
* payload.
|
|
13
|
+
*
|
|
14
|
+
* @see docs/adr/ADR-075-metaharness-harness-evolution-and-provenance.md
|
|
15
|
+
*/
|
|
16
|
+
import { generateKeyPairSync, sign as cryptoSign, verify as cryptoVerify, createHash } from 'node:crypto';
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
/** Generate an Ed25519 keypair as PEM strings (spki/pkcs8). */
|
|
19
|
+
export function generateKeyPairPem() {
|
|
20
|
+
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
|
|
21
|
+
return {
|
|
22
|
+
publicKey: publicKey.export({ type: 'spki', format: 'pem' }).toString(),
|
|
23
|
+
privateKey: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** sha256 (hex) of a buffer. */
|
|
27
|
+
export function sha256Hex(data) {
|
|
28
|
+
return createHash('sha256').update(data).digest('hex');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build a canonical manifest over the given files. Entries are sorted by path so
|
|
32
|
+
* the signed payload is stable regardless of input order. `createdAt` is included
|
|
33
|
+
* verbatim if provided.
|
|
34
|
+
*/
|
|
35
|
+
export function buildManifest(files, opts = {}) {
|
|
36
|
+
const entries = files
|
|
37
|
+
.map((path) => ({ path, sha256: sha256Hex(readFileSync(path)) }))
|
|
38
|
+
.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
39
|
+
return { version: 1, ...(opts.createdAt ? { createdAt: opts.createdAt } : {}), entries };
|
|
40
|
+
}
|
|
41
|
+
/** Canonical bytes that get signed/verified — stable JSON of the manifest. */
|
|
42
|
+
function canonicalPayload(manifest) {
|
|
43
|
+
// Keys emitted in a fixed order; entries already sorted by buildManifest.
|
|
44
|
+
return JSON.stringify({
|
|
45
|
+
version: manifest.version,
|
|
46
|
+
createdAt: manifest.createdAt ?? null,
|
|
47
|
+
entries: manifest.entries.map((e) => ({ path: e.path, sha256: e.sha256 })),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Sign a manifest with an Ed25519 private-key PEM. Returns a base64 signature. */
|
|
51
|
+
export function signManifest(manifest, privateKeyPem) {
|
|
52
|
+
const payload = Buffer.from(canonicalPayload(manifest), 'utf8');
|
|
53
|
+
// Ed25519 takes a null algorithm (the curve fixes the hash).
|
|
54
|
+
return cryptoSign(null, payload, privateKeyPem).toString('base64');
|
|
55
|
+
}
|
|
56
|
+
/** Verify a base64 Ed25519 signature over a manifest with a public-key PEM. */
|
|
57
|
+
export function verifyManifest(manifest, signatureBase64, publicKeyPem) {
|
|
58
|
+
try {
|
|
59
|
+
const payload = Buffer.from(canonicalPayload(manifest), 'utf8');
|
|
60
|
+
return cryptoVerify(null, payload, publicKeyPem, Buffer.from(signatureBase64, 'base64'));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false; // malformed signature/key → not verified
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Convenience: build + sign in one step, returning a portable signed bundle. */
|
|
67
|
+
export function signFiles(files, privateKeyPem, publicKeyPem, opts = {}) {
|
|
68
|
+
const manifest = buildManifest(files, opts);
|
|
69
|
+
return { manifest, signature: signManifest(manifest, privateKeyPem), publicKey: publicKeyPem };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Verify a signed bundle AND that the on-disk files still match the manifest
|
|
73
|
+
* digests. Returns a per-file drift report so a caller can see exactly what
|
|
74
|
+
* changed since signing.
|
|
75
|
+
*/
|
|
76
|
+
export function verifySignedManifest(signed) {
|
|
77
|
+
const signatureValid = verifyManifest(signed.manifest, signed.signature, signed.publicKey);
|
|
78
|
+
const drift = [];
|
|
79
|
+
for (const entry of signed.manifest.entries) {
|
|
80
|
+
let actual = null;
|
|
81
|
+
try {
|
|
82
|
+
actual = sha256Hex(readFileSync(entry.path));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
actual = null; // missing/unreadable
|
|
86
|
+
}
|
|
87
|
+
if (actual !== entry.sha256)
|
|
88
|
+
drift.push({ path: entry.path, expected: entry.sha256, actual });
|
|
89
|
+
}
|
|
90
|
+
return { signatureValid, filesIntact: drift.length === 0, drift };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=provenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../src/harness/provenance.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Harness provenance — Ed25519 witness manifest over harness/agent config files\n * (ADR-075, Track B).\n *\n * Produces a tamper-evident manifest (sha256 per file) and signs it with Ed25519\n * (Node's built-in `crypto` — no external deps), complementing the CWE-78\n * hardening: a signed manifest lets a consumer verify that the agent/skill/policy\n * files a harness ships are exactly the ones that were reviewed.\n *\n * Deterministic by construction (no wall-clock inside): the caller supplies any\n * timestamp, and entries are sorted, so signing the same files yields the same\n * payload.\n *\n * @see docs/adr/ADR-075-metaharness-harness-evolution-and-provenance.md\n */\n\nimport { generateKeyPairSync, sign as cryptoSign, verify as cryptoVerify, createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\n\nexport interface ManifestEntry {\n /** File path as supplied (used as the stable sort/identity key). */\n path: string;\n /** Lowercase hex sha256 of the file contents. */\n sha256: string;\n}\n\nexport interface HarnessManifest {\n version: 1;\n /** Optional caller-supplied timestamp (ISO string). Part of the signed payload. */\n createdAt?: string;\n /** File digests, sorted by path for a canonical, reproducible payload. */\n entries: ManifestEntry[];\n}\n\nexport interface KeyPairPem {\n publicKey: string;\n privateKey: string;\n}\n\n/** Generate an Ed25519 keypair as PEM strings (spki/pkcs8). */\nexport function generateKeyPairPem(): KeyPairPem {\n const { publicKey, privateKey } = generateKeyPairSync('ed25519');\n return {\n publicKey: publicKey.export({ type: 'spki', format: 'pem' }).toString(),\n privateKey: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),\n };\n}\n\n/** sha256 (hex) of a buffer. */\nexport function sha256Hex(data: Buffer | string): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\n/**\n * Build a canonical manifest over the given files. Entries are sorted by path so\n * the signed payload is stable regardless of input order. `createdAt` is included\n * verbatim if provided.\n */\nexport function buildManifest(files: string[], opts: { createdAt?: string } = {}): HarnessManifest {\n const entries: ManifestEntry[] = files\n .map((path) => ({ path, sha256: sha256Hex(readFileSync(path)) }))\n .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n return { version: 1, ...(opts.createdAt ? { createdAt: opts.createdAt } : {}), entries };\n}\n\n/** Canonical bytes that get signed/verified — stable JSON of the manifest. */\nfunction canonicalPayload(manifest: HarnessManifest): string {\n // Keys emitted in a fixed order; entries already sorted by buildManifest.\n return JSON.stringify({\n version: manifest.version,\n createdAt: manifest.createdAt ?? null,\n entries: manifest.entries.map((e) => ({ path: e.path, sha256: e.sha256 })),\n });\n}\n\n/** Sign a manifest with an Ed25519 private-key PEM. Returns a base64 signature. */\nexport function signManifest(manifest: HarnessManifest, privateKeyPem: string): string {\n const payload = Buffer.from(canonicalPayload(manifest), 'utf8');\n // Ed25519 takes a null algorithm (the curve fixes the hash).\n return cryptoSign(null, payload, privateKeyPem).toString('base64');\n}\n\n/** Verify a base64 Ed25519 signature over a manifest with a public-key PEM. */\nexport function verifyManifest(manifest: HarnessManifest, signatureBase64: string, publicKeyPem: string): boolean {\n try {\n const payload = Buffer.from(canonicalPayload(manifest), 'utf8');\n return cryptoVerify(null, payload, publicKeyPem, Buffer.from(signatureBase64, 'base64'));\n } catch {\n return false; // malformed signature/key → not verified\n }\n}\n\nexport interface SignedManifest {\n manifest: HarnessManifest;\n signature: string; // base64\n publicKey: string; // spki PEM (the verification anchor)\n}\n\n/** Convenience: build + sign in one step, returning a portable signed bundle. */\nexport function signFiles(files: string[], privateKeyPem: string, publicKeyPem: string, opts: { createdAt?: string } = {}): SignedManifest {\n const manifest = buildManifest(files, opts);\n return { manifest, signature: signManifest(manifest, privateKeyPem), publicKey: publicKeyPem };\n}\n\n/**\n * Verify a signed bundle AND that the on-disk files still match the manifest\n * digests. Returns a per-file drift report so a caller can see exactly what\n * changed since signing.\n */\nexport function verifySignedManifest(signed: SignedManifest): {\n signatureValid: boolean;\n filesIntact: boolean;\n drift: { path: string; expected: string; actual: string | null }[];\n} {\n const signatureValid = verifyManifest(signed.manifest, signed.signature, signed.publicKey);\n const drift: { path: string; expected: string; actual: string | null }[] = [];\n for (const entry of signed.manifest.entries) {\n let actual: string | null = null;\n try {\n actual = sha256Hex(readFileSync(entry.path));\n } catch {\n actual = null; // missing/unreadable\n }\n if (actual !== entry.sha256) drift.push({ path: entry.path, expected: entry.sha256, actual });\n }\n return { signatureValid, filesIntact: drift.length === 0, drift };\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type McpProfile = 'readonly' | 'retrieval' | 'learning' | 'admin';
|
|
2
|
+
export declare function parseMcpProfile(value: string | undefined): McpProfile;
|
|
3
|
+
export declare function isMcpToolAllowed(name: string, options: {
|
|
4
|
+
profile: McpProfile;
|
|
5
|
+
allowDarwinExecution?: boolean;
|
|
6
|
+
}): boolean;
|
|
7
|
+
export declare function installMcpPolicy<T extends {
|
|
8
|
+
addTool: (tool: {
|
|
9
|
+
name: string;
|
|
10
|
+
}) => unknown;
|
|
11
|
+
}>(server: T, options: {
|
|
12
|
+
profile: McpProfile;
|
|
13
|
+
allowDarwinExecution?: boolean;
|
|
14
|
+
}): T;
|
|
15
|
+
//# sourceMappingURL=mcp-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-policy.d.ts","sourceRoot":"","sources":["../../src/mcp/mcp-policy.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;AAYzE,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CAMrE;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC/D,OAAO,CAQT;AAED,wBAAgB,gBAAgB,CAAC,CAAC,SAAS;IAAE,OAAO,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAA;CAAE,EACzF,MAAM,EAAE,CAAC,EACT,OAAO,EAAE;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;CAAE,GAC/D,CAAC,CAOH"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const READONLY = [
|
|
2
|
+
/^agentic_flow_(list|list_all|agent_info|check_conflicts)/,
|
|
3
|
+
/^agentdb_(stats|pattern_search|pattern_stats)$/,
|
|
4
|
+
/^harness_(capabilities|manifest|verify)$/,
|
|
5
|
+
/parse_markdown$/,
|
|
6
|
+
/_(stats|get_profile|list_profiles|benchmark)$/,
|
|
7
|
+
];
|
|
8
|
+
const RETRIEVAL = [...READONLY, /search$/, /context$/];
|
|
9
|
+
const LEARNING = [...RETRIEVAL, /^agentic_flow_agent$/, /^agentdb_pattern_store$/, /^sona_/];
|
|
10
|
+
export function parseMcpProfile(value) {
|
|
11
|
+
const profile = value ?? 'readonly';
|
|
12
|
+
if (!['readonly', 'retrieval', 'learning', 'admin'].includes(profile)) {
|
|
13
|
+
throw new Error(`invalid MCP profile: ${profile}`);
|
|
14
|
+
}
|
|
15
|
+
return profile;
|
|
16
|
+
}
|
|
17
|
+
export function isMcpToolAllowed(name, options) {
|
|
18
|
+
if (name === 'harness_repair') {
|
|
19
|
+
return options.profile === 'admin' && options.allowDarwinExecution === true;
|
|
20
|
+
}
|
|
21
|
+
if (options.profile === 'admin')
|
|
22
|
+
return true;
|
|
23
|
+
const rules = options.profile === 'learning' ? LEARNING :
|
|
24
|
+
options.profile === 'retrieval' ? RETRIEVAL : READONLY;
|
|
25
|
+
return rules.some((rule) => rule.test(name));
|
|
26
|
+
}
|
|
27
|
+
export function installMcpPolicy(server, options) {
|
|
28
|
+
const addTool = server.addTool.bind(server);
|
|
29
|
+
server.addTool = ((tool) => {
|
|
30
|
+
if (isMcpToolAllowed(tool.name, options))
|
|
31
|
+
return addTool(tool);
|
|
32
|
+
return undefined;
|
|
33
|
+
});
|
|
34
|
+
return server;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=mcp-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-policy.js","sourceRoot":"","sources":["../../src/mcp/mcp-policy.ts"],"names":[],"mappings":"AAEA,MAAM,QAAQ,GAAG;IACf,0DAA0D;IAC1D,gDAAgD;IAChD,0CAA0C;IAC1C,iBAAiB;IACjB,+CAA+C;CAChD,CAAC;AACF,MAAM,SAAS,GAAG,CAAC,GAAG,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;AACvD,MAAM,QAAQ,GAAG,CAAC,GAAG,SAAS,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,QAAQ,CAAC,CAAC;AAE7F,MAAM,UAAU,eAAe,CAAC,KAAyB;IACvD,MAAM,OAAO,GAAG,KAAK,IAAI,UAAU,CAAC;IACpC,IAAI,CAAC,CAAC,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,EAAE,CAAC,CAAC;IACrD,CAAC;IACD,OAAO,OAAqB,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,OAAgE;IAEhE,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;QAC9B,OAAO,OAAO,CAAC,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC,oBAAoB,KAAK,IAAI,CAAC;IAC9E,CAAC;IACD,IAAI,OAAO,CAAC,OAAO,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACvD,OAAO,CAAC,OAAO,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC;IACzD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,MAAS,EACT,OAAgE;IAEhE,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,IAAsB,EAAE,EAAE;QAC3C,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC;YAAE,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/D,OAAO,SAAS,CAAC;IACnB,CAAC,CAAiB,CAAC;IACnB,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["export type McpProfile = 'readonly' | 'retrieval' | 'learning' | 'admin';\n\nconst READONLY = [\n /^agentic_flow_(list|list_all|agent_info|check_conflicts)/,\n /^agentdb_(stats|pattern_search|pattern_stats)$/,\n /^harness_(capabilities|manifest|verify)$/,\n /parse_markdown$/,\n /_(stats|get_profile|list_profiles|benchmark)$/,\n];\nconst RETRIEVAL = [...READONLY, /search$/, /context$/];\nconst LEARNING = [...RETRIEVAL, /^agentic_flow_agent$/, /^agentdb_pattern_store$/, /^sona_/];\n\nexport function parseMcpProfile(value: string | undefined): McpProfile {\n const profile = value ?? 'readonly';\n if (!['readonly', 'retrieval', 'learning', 'admin'].includes(profile)) {\n throw new Error(`invalid MCP profile: ${profile}`);\n }\n return profile as McpProfile;\n}\n\nexport function isMcpToolAllowed(\n name: string,\n options: { profile: McpProfile; allowDarwinExecution?: boolean },\n): boolean {\n if (name === 'harness_repair') {\n return options.profile === 'admin' && options.allowDarwinExecution === true;\n }\n if (options.profile === 'admin') return true;\n const rules = options.profile === 'learning' ? LEARNING :\n options.profile === 'retrieval' ? RETRIEVAL : READONLY;\n return rules.some((rule) => rule.test(name));\n}\n\nexport function installMcpPolicy<T extends { addTool: (tool: { name: string }) => unknown }>(\n server: T,\n options: { profile: McpProfile; allowDarwinExecution?: boolean },\n): T {\n const addTool = server.addTool.bind(server);\n server.addTool = ((tool: { name: string }) => {\n if (isMcpToolAllowed(tool.name, options)) return addTool(tool);\n return undefined;\n }) as T['addTool'];\n return server;\n}\n"]}
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import { FastMCP } from 'fastmcp';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { execFileSync } from 'child_process';
|
|
7
|
+
import { registerHarnessTools } from './tools/harness-tools.js';
|
|
8
|
+
import { installMcpPolicy, parseMcpProfile } from './mcp-policy.js';
|
|
7
9
|
// Security: All shell-outs use execFileSync with argv arrays (shell: false) to
|
|
8
10
|
// prevent OS command injection via tool parameters (CWE-78). Do NOT reintroduce
|
|
9
11
|
// execSync with template-string interpolation here — every prior occurrence in
|
|
@@ -27,6 +29,15 @@ const server = new FastMCP({
|
|
|
27
29
|
name: 'agentic-flow',
|
|
28
30
|
version: '1.0.8'
|
|
29
31
|
});
|
|
32
|
+
const mcpProfile = parseMcpProfile(process.env.AGENTIC_FLOW_MCP_PROFILE);
|
|
33
|
+
installMcpPolicy(server, {
|
|
34
|
+
profile: mcpProfile,
|
|
35
|
+
allowDarwinExecution: process.env.AGENTIC_FLOW_MCP_ENABLE_DARWIN === 'true',
|
|
36
|
+
});
|
|
37
|
+
console.error(`🔐 MCP capability profile: ${mcpProfile}`);
|
|
38
|
+
// ADR-075: harness evolution/repair + provenance tools (harness_repair,
|
|
39
|
+
// harness_manifest, harness_verify).
|
|
40
|
+
registerHarnessTools(server);
|
|
30
41
|
// Tool: Run agentic-flow agent
|
|
31
42
|
server.addTool({
|
|
32
43
|
name: 'agentic_flow_agent',
|