@martinloop/mcp 0.3.8 → 0.5.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/README.md +90 -106
- package/dist/discovery-metadata.d.ts +1 -1
- package/dist/discovery-metadata.js +1 -1
- package/dist/resources.d.ts +1 -2
- package/dist/resources.js +190 -238
- package/dist/server-validation.d.ts +1 -1
- package/dist/server-validation.js +16 -11
- package/dist/server.d.ts +23 -0
- package/dist/server.js +152 -7
- package/dist/tools/run-dossier.d.ts +3 -1
- package/dist/tools/run-dossier.js +40 -2
- package/dist/tools/run-loop.d.ts +1 -4
- package/dist/tools/run-loop.js +6 -15
- package/dist/tools/tool-support.d.ts +2 -1
- package/dist/tools/tool-support.js +1 -12
- package/dist/vendor/adapters/claude-cli.js +38 -35
- package/dist/vendor/adapters/cli-bridge.d.ts +9 -1
- package/dist/vendor/adapters/cli-bridge.js +54 -13
- package/dist/vendor/adapters/codex-launcher.d.ts +61 -1
- package/dist/vendor/adapters/codex-launcher.js +118 -19
- package/dist/vendor/adapters/index.d.ts +1 -3
- package/dist/vendor/adapters/index.js +1 -3
- package/dist/vendor/adapters/openai-compatible.js +57 -18
- package/dist/vendor/contracts/adapter-capabilities.d.ts +1 -1
- package/dist/vendor/contracts/context-handoff.d.ts +100 -0
- package/dist/vendor/contracts/context-handoff.js +11 -0
- package/dist/vendor/contracts/context-manifest.d.ts +195 -0
- package/dist/vendor/contracts/context-manifest.js +17 -0
- package/dist/vendor/contracts/context-shadow.d.ts +75 -0
- package/dist/vendor/contracts/context-shadow.js +8 -0
- package/dist/vendor/contracts/exits.d.ts +126 -0
- package/dist/vendor/contracts/exits.js +32 -0
- package/dist/vendor/contracts/index.d.ts +39 -2
- package/dist/vendor/contracts/index.js +22 -0
- package/dist/vendor/contracts/mission.d.ts +102 -0
- package/dist/vendor/contracts/mission.js +64 -0
- package/dist/vendor/contracts/operator.d.ts +3 -1
- package/dist/vendor/contracts/operator.js +2 -1
- package/dist/vendor/contracts/verified-handoff.d.ts +100 -0
- package/dist/vendor/contracts/verified-handoff.js +35 -0
- package/dist/vendor/core/context-chain-gate.d.ts +89 -0
- package/dist/vendor/core/context-chain-gate.js +173 -0
- package/dist/vendor/core/context-compiler.d.ts +53 -0
- package/dist/vendor/core/context-compiler.js +261 -0
- package/dist/vendor/core/context-handoff.d.ts +42 -0
- package/dist/vendor/core/context-handoff.js +167 -0
- package/dist/vendor/core/context-shadow.d.ts +39 -0
- package/dist/vendor/core/context-shadow.js +200 -0
- package/dist/vendor/core/exit-signal.d.ts +88 -0
- package/dist/vendor/core/exit-signal.js +339 -0
- package/dist/vendor/core/exits.d.ts +41 -0
- package/dist/vendor/core/exits.js +258 -0
- package/dist/vendor/core/index.d.ts +42 -1
- package/dist/vendor/core/index.js +1086 -662
- package/dist/vendor/core/mission/aggregate.d.ts +27 -0
- package/dist/vendor/core/mission/aggregate.js +42 -0
- package/dist/vendor/core/mission/index.d.ts +2 -0
- package/dist/vendor/core/mission/index.js +1 -0
- package/dist/vendor/core/persistence/compiler.d.ts +8 -0
- package/dist/vendor/core/persistence/compiler.js +156 -0
- package/dist/vendor/core/persistence/context-handoff-store.d.ts +68 -0
- package/dist/vendor/core/persistence/context-handoff-store.js +112 -0
- package/dist/vendor/core/persistence/index.d.ts +4 -0
- package/dist/vendor/core/persistence/index.js +2 -0
- package/dist/vendor/core/persistence/ledger.d.ts +1 -1
- package/dist/vendor/core/persistence/mission-store.d.ts +56 -0
- package/dist/vendor/core/persistence/mission-store.js +281 -0
- package/dist/vendor/core/policy.d.ts +2 -0
- package/dist/vendor/core/policy.js +5 -2
- package/dist/vendor/core/rollback.js +15 -1
- package/dist/vendor/core/savings.d.ts +13 -0
- package/dist/vendor/core/savings.js +22 -0
- package/dist/vendor/core/termination-store.d.ts +15 -0
- package/dist/vendor/core/termination-store.js +44 -0
- package/dist/vendor/core/verified-handoff.d.ts +67 -0
- package/dist/vendor/core/verified-handoff.js +210 -0
- package/package.json +76 -83
- package/server.json +21 -21
- package/LICENSE +0 -216
- package/dist/vendor/adapters/adapters/src/claude-cli.d.ts +0 -146
- package/dist/vendor/adapters/adapters/src/claude-cli.js +0 -1100
- package/dist/vendor/adapters/adapters/src/cli-bridge.d.ts +0 -103
- package/dist/vendor/adapters/adapters/src/cli-bridge.js +0 -569
- package/dist/vendor/adapters/adapters/src/codex-launcher.d.ts +0 -78
- package/dist/vendor/adapters/adapters/src/codex-launcher.js +0 -663
- package/dist/vendor/adapters/adapters/src/direct-provider.d.ts +0 -10
- package/dist/vendor/adapters/adapters/src/direct-provider.js +0 -40
- package/dist/vendor/adapters/adapters/src/index.d.ts +0 -8
- package/dist/vendor/adapters/adapters/src/index.js +0 -8
- package/dist/vendor/adapters/adapters/src/openai-compatible.d.ts +0 -62
- package/dist/vendor/adapters/adapters/src/openai-compatible.js +0 -306
- package/dist/vendor/adapters/adapters/src/runtime-support.d.ts +0 -17
- package/dist/vendor/adapters/adapters/src/runtime-support.js +0 -59
- package/dist/vendor/adapters/adapters/src/stub-agent-cli.d.ts +0 -8
- package/dist/vendor/adapters/adapters/src/stub-agent-cli.js +0 -40
- package/dist/vendor/adapters/adapters/src/stub-direct-provider.d.ts +0 -8
- package/dist/vendor/adapters/adapters/src/stub-direct-provider.js +0 -12
- package/dist/vendor/adapters/adapters/src/verifier-only.d.ts +0 -9
- package/dist/vendor/adapters/adapters/src/verifier-only.js +0 -63
- package/dist/vendor/adapters/contracts/src/context-graph.d.ts +0 -48
- package/dist/vendor/adapters/contracts/src/context-graph.js +0 -26
- package/dist/vendor/adapters/contracts/src/execution-policy.d.ts +0 -78
- package/dist/vendor/adapters/contracts/src/execution-policy.js +0 -43
- package/dist/vendor/adapters/contracts/src/governance.d.ts +0 -24
- package/dist/vendor/adapters/contracts/src/governance.js +0 -11
- package/dist/vendor/adapters/contracts/src/identity.d.ts +0 -29
- package/dist/vendor/adapters/contracts/src/identity.js +0 -30
- package/dist/vendor/adapters/contracts/src/index.d.ts +0 -412
- package/dist/vendor/adapters/contracts/src/index.js +0 -225
- package/dist/vendor/adapters/contracts/src/operator.d.ts +0 -19
- package/dist/vendor/adapters/contracts/src/operator.js +0 -11
- package/dist/vendor/adapters/contracts/src/trajectory.d.ts +0 -26
- package/dist/vendor/adapters/contracts/src/trajectory.js +0 -27
- package/dist/vendor/adapters/core/src/compiler.d.ts +0 -52
- package/dist/vendor/adapters/core/src/compiler.js +0 -52
- package/dist/vendor/adapters/core/src/context-graph.d.ts +0 -3
- package/dist/vendor/adapters/core/src/context-graph.js +0 -371
- package/dist/vendor/adapters/core/src/context-integrity.d.ts +0 -26
- package/dist/vendor/adapters/core/src/context-integrity.js +0 -80
- package/dist/vendor/adapters/core/src/grounding.d.ts +0 -38
- package/dist/vendor/adapters/core/src/grounding.js +0 -273
- package/dist/vendor/adapters/core/src/identity.d.ts +0 -20
- package/dist/vendor/adapters/core/src/identity.js +0 -86
- package/dist/vendor/adapters/core/src/index.d.ts +0 -186
- package/dist/vendor/adapters/core/src/index.js +0 -1495
- package/dist/vendor/adapters/core/src/leash.d.ts +0 -48
- package/dist/vendor/adapters/core/src/leash.js +0 -497
- package/dist/vendor/adapters/core/src/persistence/compiler.d.ts +0 -18
- package/dist/vendor/adapters/core/src/persistence/compiler.js +0 -34
- package/dist/vendor/adapters/core/src/persistence/index.d.ts +0 -10
- package/dist/vendor/adapters/core/src/persistence/index.js +0 -5
- package/dist/vendor/adapters/core/src/persistence/integrity.d.ts +0 -39
- package/dist/vendor/adapters/core/src/persistence/integrity.js +0 -252
- package/dist/vendor/adapters/core/src/persistence/ledger.d.ts +0 -23
- package/dist/vendor/adapters/core/src/persistence/ledger.js +0 -9
- package/dist/vendor/adapters/core/src/persistence/memory-store.d.ts +0 -63
- package/dist/vendor/adapters/core/src/persistence/memory-store.js +0 -115
- package/dist/vendor/adapters/core/src/persistence/runs-reader.d.ts +0 -66
- package/dist/vendor/adapters/core/src/persistence/runs-reader.js +0 -140
- package/dist/vendor/adapters/core/src/persistence/store.d.ts +0 -89
- package/dist/vendor/adapters/core/src/persistence/store.js +0 -125
- package/dist/vendor/adapters/core/src/persistence/trace-store.d.ts +0 -85
- package/dist/vendor/adapters/core/src/persistence/trace-store.js +0 -125
- package/dist/vendor/adapters/core/src/policy-compiler.d.ts +0 -2
- package/dist/vendor/adapters/core/src/policy-compiler.js +0 -195
- package/dist/vendor/adapters/core/src/policy.d.ts +0 -156
- package/dist/vendor/adapters/core/src/policy.js +0 -704
- package/dist/vendor/adapters/core/src/repo-analyzer.d.ts +0 -53
- package/dist/vendor/adapters/core/src/repo-analyzer.js +0 -263
- package/dist/vendor/adapters/core/src/rollback.d.ts +0 -15
- package/dist/vendor/adapters/core/src/rollback.js +0 -253
- package/dist/vendor/adapters/core/src/routing.d.ts +0 -82
- package/dist/vendor/adapters/core/src/routing.js +0 -336
- package/dist/vendor/adapters/core/src/test-integrity.d.ts +0 -64
- package/dist/vendor/adapters/core/src/test-integrity.js +0 -197
- package/dist/vendor/adapters/core/src/trajectory.d.ts +0 -18
- package/dist/vendor/adapters/core/src/trajectory.js +0 -295
- package/dist/vendor/adapters/stub-agent-cli.d.ts +0 -8
- package/dist/vendor/adapters/stub-agent-cli.js +0 -40
- package/dist/vendor/adapters/verifier-only.d.ts +0 -9
- package/dist/vendor/adapters/verifier-only.js +0 -63
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contracts for the Martin Loop agentic system.
|
|
3
|
+
*
|
|
4
|
+
* This module defines the core type contracts and data structures for autonomous agent loop management,
|
|
5
|
+
* including loop lifecycle states, task definitions, budget tracking, cost accounting, verification,
|
|
6
|
+
* patch decisions, telemetry, and governance policies. It provides the public API surface for
|
|
7
|
+
* creating and managing loop records, handling events, validating batches, and tracking
|
|
8
|
+
* portfolio snapshots and routing economics.
|
|
9
|
+
*/
|
|
1
10
|
export const FAILURE_CLASSES = [
|
|
2
11
|
"logic_error",
|
|
3
12
|
"hallucination",
|
|
@@ -53,6 +62,7 @@ export function createLoopRecord(draft, options = {}) {
|
|
|
53
62
|
updatedAt: draft.updatedAt ?? now,
|
|
54
63
|
...(draft.receiptScope ? { receiptScope: draft.receiptScope } : {}),
|
|
55
64
|
...(draft.receiptIntegrity ? { receiptIntegrity: draft.receiptIntegrity } : {}),
|
|
65
|
+
...(draft.terminationEnvelope ? { terminationEnvelope: draft.terminationEnvelope } : {}),
|
|
56
66
|
...(draft.teamId ? { teamId: draft.teamId } : {})
|
|
57
67
|
};
|
|
58
68
|
}
|
|
@@ -210,6 +220,8 @@ function nextStatus(current, eventType) {
|
|
|
210
220
|
return "verifying";
|
|
211
221
|
case "run.completed":
|
|
212
222
|
return current === "failed" ? "failed" : "completed";
|
|
223
|
+
case "run.terminated":
|
|
224
|
+
return "exited";
|
|
213
225
|
default:
|
|
214
226
|
return current;
|
|
215
227
|
}
|
|
@@ -223,5 +235,15 @@ export { DEFAULT_ROUTING_POLICY } from "./execution-policy.js";
|
|
|
223
235
|
export { cloneContextGraphSnapshot } from "./context-graph.js";
|
|
224
236
|
export { cloneIdentityAttestation, cloneIdentityClaims, cloneIdentityToken } from "./identity.js";
|
|
225
237
|
export { cloneCircuitBreakDecision, cloneTrajectoryAssessment } from "./trajectory.js";
|
|
238
|
+
export { EXIT_KINDS, EXIT_POLICY_VERSION, EXIT_EVALUATION_VERSION, EXIT_SIGNAL_VERSION, TERMINATION_ENVELOPE_VERSION } from "./exits.js";
|
|
226
239
|
// ─── R4 Delivery — M1 Contract ──────────────────────────────────────────────
|
|
227
240
|
export { ALLOWED_ACTION_TYPES, DELIVERY_MESSAGE_SCHEMA_VERSION, DELIVERY_RECORD_SCHEMA_VERSION, MESSAGE_SELECTION_RESPONSE_SCHEMA_VERSION } from "./delivery.js";
|
|
241
|
+
// ─── Context Shadow — A-CTX-0 ────────────────────────────────────────────────
|
|
242
|
+
export { CONTEXT_SHADOW_MANIFEST_VERSION, CONTEXT_C5_VERSION } from "./context-shadow.js";
|
|
243
|
+
// ─── Context Runtime — A-CTX-1 ───────────────────────────────────────────────
|
|
244
|
+
export { CONTEXT_MANIFEST_VERSION, CONTEXT_LEDGER_VERSION } from "./context-manifest.js";
|
|
245
|
+
// ─── Track A — Verified Handoff ───────────────────────────────────────────────
|
|
246
|
+
export { EVIDENCE_STATUSES, TEST_INTEGRITY_STATUSES, TEST_INTEGRITY_VERDICTS, VERIFIED_HANDOFF_OUTCOMES, } from "./verified-handoff.js";
|
|
247
|
+
// ─── Context Handoff — A-CTX-2 ───────────────────────────────────────────────
|
|
248
|
+
export { HANDOFF_SCHEMA_VERSION } from "./context-handoff.js";
|
|
249
|
+
export { MISSION_SCHEMA_VERSION, MISSION_STATUSES, ALLOWED_MISSION_TRANSITIONS, createMissionRecord, isMissionTransitionAllowed } from './mission.js';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission Governance contracts — C2
|
|
3
|
+
*
|
|
4
|
+
* A MissionRecord governs one software mission: intent → budget → runs →
|
|
5
|
+
* verification → decision → receipt. It never replaces LoopRecord; it
|
|
6
|
+
* aggregates above it.
|
|
7
|
+
*
|
|
8
|
+
* Invariants:
|
|
9
|
+
* - One accountable human owner per mission.
|
|
10
|
+
* - State transitions are append-only in the ledger.
|
|
11
|
+
* - Ledger is the authority; mission.json is a rebuildable cache.
|
|
12
|
+
* - A mission cannot become "verified" without verified run evidence.
|
|
13
|
+
* - A mission cannot become "shipped" without an explicit ship decision.
|
|
14
|
+
* - Unknown schema versions fail closed.
|
|
15
|
+
* - Budgets cannot be raised silently.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MISSION_SCHEMA_VERSION: "martin.mission.v1";
|
|
18
|
+
export declare const MISSION_STATUSES: readonly ["planned", "running", "blocked", "verified", "shipped", "rolled_back", "killed"];
|
|
19
|
+
export type MissionStatus = (typeof MISSION_STATUSES)[number];
|
|
20
|
+
export declare const ALLOWED_MISSION_TRANSITIONS: Record<MissionStatus, readonly MissionStatus[]>;
|
|
21
|
+
export type MissionDecision = "ship" | "retry" | "rollback" | "kill";
|
|
22
|
+
export interface MissionBudget {
|
|
23
|
+
maxUsd: number;
|
|
24
|
+
maxTokens: number;
|
|
25
|
+
maxRuns: number;
|
|
26
|
+
maxConcurrentRuns: number;
|
|
27
|
+
}
|
|
28
|
+
export interface MissionCost {
|
|
29
|
+
/** Sum of actualUsd across all linked runs. */
|
|
30
|
+
totalActualUsd: number;
|
|
31
|
+
/** Number of linked runs with status "completed" (verified outcome). */
|
|
32
|
+
verifiedOutcomeCount: number;
|
|
33
|
+
/** Number of linked runs regardless of outcome. */
|
|
34
|
+
totalRunCount: number;
|
|
35
|
+
}
|
|
36
|
+
export type MissionRunRole = "primary" | "experiment" | "validation";
|
|
37
|
+
export interface MissionRunLink {
|
|
38
|
+
loopId: string;
|
|
39
|
+
role: MissionRunRole;
|
|
40
|
+
attachedAt: string;
|
|
41
|
+
/** Set to true when the linked run completed with verification passed. */
|
|
42
|
+
verifiedOutcome?: boolean;
|
|
43
|
+
/** Actual cost of this run in USD. */
|
|
44
|
+
actualUsd?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface MissionApproval {
|
|
47
|
+
approvalId: string;
|
|
48
|
+
kind: "ship" | "budget_increase" | "scope_change";
|
|
49
|
+
decision: "approved" | "denied";
|
|
50
|
+
approvedBy: string;
|
|
51
|
+
approvedAt: string;
|
|
52
|
+
note?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface MissionOutcome {
|
|
55
|
+
decision: MissionDecision;
|
|
56
|
+
decidedAt: string;
|
|
57
|
+
decidedBy: string;
|
|
58
|
+
note?: string;
|
|
59
|
+
}
|
|
60
|
+
export type MissionEventKind = "mission.created" | "mission.status_changed" | "mission.run_attached" | "mission.run_verified" | "mission.approved" | "mission.closed" | "mission.collision_blocked";
|
|
61
|
+
export interface MissionEvent {
|
|
62
|
+
eventId: string;
|
|
63
|
+
kind: MissionEventKind;
|
|
64
|
+
missionId: string;
|
|
65
|
+
timestamp: string;
|
|
66
|
+
payload: Record<string, unknown>;
|
|
67
|
+
}
|
|
68
|
+
export interface MissionRecord {
|
|
69
|
+
schemaVersion: typeof MISSION_SCHEMA_VERSION;
|
|
70
|
+
missionId: string;
|
|
71
|
+
/** Monotonically increasing write counter. Used for CAS enforcement. */
|
|
72
|
+
revision: number;
|
|
73
|
+
title: string;
|
|
74
|
+
objective: string;
|
|
75
|
+
ownerId: string;
|
|
76
|
+
workspaceId: string;
|
|
77
|
+
projectId: string;
|
|
78
|
+
status: MissionStatus;
|
|
79
|
+
budget: MissionBudget;
|
|
80
|
+
cost: MissionCost;
|
|
81
|
+
runLinks: MissionRunLink[];
|
|
82
|
+
approvals: MissionApproval[];
|
|
83
|
+
outcome?: MissionOutcome;
|
|
84
|
+
acceptanceCriteria: string[];
|
|
85
|
+
createdAt: string;
|
|
86
|
+
updatedAt: string;
|
|
87
|
+
}
|
|
88
|
+
export interface MissionDraft {
|
|
89
|
+
missionId?: string;
|
|
90
|
+
title: string;
|
|
91
|
+
objective: string;
|
|
92
|
+
ownerId: string;
|
|
93
|
+
workspaceId: string;
|
|
94
|
+
projectId: string;
|
|
95
|
+
budget: MissionBudget;
|
|
96
|
+
acceptanceCriteria?: string[];
|
|
97
|
+
}
|
|
98
|
+
export declare function createMissionRecord(draft: MissionDraft, options?: {
|
|
99
|
+
now?: () => string;
|
|
100
|
+
idFactory?: (prefix: string) => string;
|
|
101
|
+
}): MissionRecord;
|
|
102
|
+
export declare function isMissionTransitionAllowed(from: MissionStatus, to: MissionStatus): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mission Governance contracts — C2
|
|
3
|
+
*
|
|
4
|
+
* A MissionRecord governs one software mission: intent → budget → runs →
|
|
5
|
+
* verification → decision → receipt. It never replaces LoopRecord; it
|
|
6
|
+
* aggregates above it.
|
|
7
|
+
*
|
|
8
|
+
* Invariants:
|
|
9
|
+
* - One accountable human owner per mission.
|
|
10
|
+
* - State transitions are append-only in the ledger.
|
|
11
|
+
* - Ledger is the authority; mission.json is a rebuildable cache.
|
|
12
|
+
* - A mission cannot become "verified" without verified run evidence.
|
|
13
|
+
* - A mission cannot become "shipped" without an explicit ship decision.
|
|
14
|
+
* - Unknown schema versions fail closed.
|
|
15
|
+
* - Budgets cannot be raised silently.
|
|
16
|
+
*/
|
|
17
|
+
export const MISSION_SCHEMA_VERSION = "martin.mission.v1";
|
|
18
|
+
// ─── Status and transitions ───────────────────────────────────────────────────
|
|
19
|
+
export const MISSION_STATUSES = [
|
|
20
|
+
"planned",
|
|
21
|
+
"running",
|
|
22
|
+
"blocked",
|
|
23
|
+
"verified",
|
|
24
|
+
"shipped",
|
|
25
|
+
"rolled_back",
|
|
26
|
+
"killed"
|
|
27
|
+
];
|
|
28
|
+
export const ALLOWED_MISSION_TRANSITIONS = {
|
|
29
|
+
planned: ["running", "killed"],
|
|
30
|
+
running: ["blocked", "verified", "rolled_back", "killed"],
|
|
31
|
+
blocked: ["running", "rolled_back", "killed"],
|
|
32
|
+
verified: ["shipped", "running", "rolled_back", "killed"],
|
|
33
|
+
shipped: [],
|
|
34
|
+
rolled_back: ["running", "killed"],
|
|
35
|
+
killed: []
|
|
36
|
+
};
|
|
37
|
+
// ─── Factory ──────────────────────────────────────────────────────────────────
|
|
38
|
+
export function createMissionRecord(draft, options = {}) {
|
|
39
|
+
const now = options.now ?? (() => new Date().toISOString());
|
|
40
|
+
const makeId = options.idFactory ?? ((prefix) => `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`);
|
|
41
|
+
const ts = now();
|
|
42
|
+
return {
|
|
43
|
+
schemaVersion: MISSION_SCHEMA_VERSION,
|
|
44
|
+
missionId: draft.missionId ?? makeId("mission"),
|
|
45
|
+
revision: 0,
|
|
46
|
+
title: draft.title,
|
|
47
|
+
objective: draft.objective,
|
|
48
|
+
ownerId: draft.ownerId,
|
|
49
|
+
workspaceId: draft.workspaceId,
|
|
50
|
+
projectId: draft.projectId,
|
|
51
|
+
status: "planned",
|
|
52
|
+
budget: { ...draft.budget },
|
|
53
|
+
cost: { totalActualUsd: 0, verifiedOutcomeCount: 0, totalRunCount: 0 },
|
|
54
|
+
runLinks: [],
|
|
55
|
+
approvals: [],
|
|
56
|
+
acceptanceCriteria: draft.acceptanceCriteria ?? [],
|
|
57
|
+
createdAt: ts,
|
|
58
|
+
updatedAt: ts
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// ─── Transition guard ─────────────────────────────────────────────────────────
|
|
62
|
+
export function isMissionTransitionAllowed(from, to) {
|
|
63
|
+
return ALLOWED_MISSION_TRANSITIONS[from].includes(to);
|
|
64
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
export declare const MARTIN_ERROR_CATEGORIES: readonly ["invalid_input", "environment", "auth", "not_found", "store_unreadable", "verification_failed", "policy_blocked", "budget_exit", "transient"];
|
|
1
|
+
export declare const MARTIN_ERROR_CATEGORIES: readonly ["invalid_input", "environment", "auth", "not_found", "store_unreadable", "verification_failed", "policy_blocked", "budget_exit", "transient", "install_failed"];
|
|
2
2
|
export type MartinErrorCategory = (typeof MARTIN_ERROR_CATEGORIES)[number];
|
|
3
3
|
export type MartinOutputMode = "human" | "json" | "quiet";
|
|
4
4
|
export interface MartinRunSelector {
|
|
5
5
|
runsDir?: string;
|
|
6
|
+
workspaceId?: string;
|
|
6
7
|
file?: string;
|
|
7
8
|
loopId?: string;
|
|
8
9
|
latest?: boolean;
|
|
@@ -10,6 +11,7 @@ export interface MartinRunSelector {
|
|
|
10
11
|
}
|
|
11
12
|
export interface MartinRunListFilters {
|
|
12
13
|
runsDir?: string;
|
|
14
|
+
workspaceId?: string;
|
|
13
15
|
limit?: number;
|
|
14
16
|
status?: string;
|
|
15
17
|
lifecycleState?: string;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { CostProvenance, LoopLifecycleState, LoopStatus, ReceiptIntegritySummary } from "./index.js";
|
|
2
|
+
export declare const VERIFIED_HANDOFF_OUTCOMES: readonly ["VERIFIED", "STOPPED", "NEEDS_REVIEW"];
|
|
3
|
+
export type VerifiedHandoffOutcome = (typeof VERIFIED_HANDOFF_OUTCOMES)[number];
|
|
4
|
+
export declare const EVIDENCE_STATUSES: readonly ["PASSED", "FAILED", "CONTRADICTED", "NOT_RUN", "NOT_EVALUATED"];
|
|
5
|
+
export type EvidenceStatus = (typeof EVIDENCE_STATUSES)[number];
|
|
6
|
+
/**
|
|
7
|
+
* Granular internal/runtime state. Retained for diagnosis, policy,
|
|
8
|
+
* receipts and engineering review.
|
|
9
|
+
*/
|
|
10
|
+
export declare const TEST_INTEGRITY_STATUSES: readonly ["UNCHANGED", "AUTHORIZED_CHANGE", "PREVENTED", "DETECTED_AND_ROLLED_BACK", "DETECTED_NEEDS_REVIEW", "NOT_EVALUATED"];
|
|
11
|
+
export type TestIntegrityStatus = (typeof TEST_INTEGRITY_STATUSES)[number];
|
|
12
|
+
/**
|
|
13
|
+
* Stable public-facing verdict used by CLI, MCP, JSON/Markdown handoffs
|
|
14
|
+
* and the website. Do not make each renderer collapse states independently.
|
|
15
|
+
*/
|
|
16
|
+
export declare const TEST_INTEGRITY_VERDICTS: readonly ["VERIFIED", "TAMPERING_DETECTED", "NOT_EVALUATED"];
|
|
17
|
+
export type TestIntegrityVerdict = (typeof TEST_INTEGRITY_VERDICTS)[number];
|
|
18
|
+
export interface VerifiedHandoffCheckV1 {
|
|
19
|
+
command: string;
|
|
20
|
+
status: EvidenceStatus;
|
|
21
|
+
exitCode?: number;
|
|
22
|
+
timedOut?: boolean;
|
|
23
|
+
detail?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface VerifiedHandoffRequirementV1 {
|
|
26
|
+
requirement: string;
|
|
27
|
+
status: "PROVEN" | "FAILED" | "UNRESOLVED" | "NOT_EVALUATED";
|
|
28
|
+
evidence?: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface VerifiedHandoffScopeV1 {
|
|
31
|
+
status: "WITHIN_SCOPE" | "VIOLATION_REJECTED" | "NEEDS_REVIEW" | "NOT_EVALUATED";
|
|
32
|
+
allowedPaths: string[];
|
|
33
|
+
deniedPaths: string[];
|
|
34
|
+
changedFiles: string[];
|
|
35
|
+
violations: string[];
|
|
36
|
+
}
|
|
37
|
+
export interface VerifiedHandoffTestIntegrityV1 {
|
|
38
|
+
/** Public rendering contract. */
|
|
39
|
+
verdict: TestIntegrityVerdict;
|
|
40
|
+
/** Granular runtime state for diagnosis and evidence. */
|
|
41
|
+
status: TestIntegrityStatus;
|
|
42
|
+
protectedPaths: string[];
|
|
43
|
+
changedProtectedPaths: string[];
|
|
44
|
+
findings: Array<{
|
|
45
|
+
filePath: string;
|
|
46
|
+
issue: string;
|
|
47
|
+
severity: "high" | "medium" | "low";
|
|
48
|
+
detail: string;
|
|
49
|
+
}>;
|
|
50
|
+
summary: string;
|
|
51
|
+
}
|
|
52
|
+
export interface VerifiedHandoffRecoveryV1 {
|
|
53
|
+
rollbackBoundaryAvailable: boolean;
|
|
54
|
+
rollbackAttempted: boolean;
|
|
55
|
+
rollbackSucceeded?: boolean;
|
|
56
|
+
isolatedRef?: string;
|
|
57
|
+
nextCommand?: string;
|
|
58
|
+
summary: string;
|
|
59
|
+
}
|
|
60
|
+
export interface VerifiedHandoffV1 {
|
|
61
|
+
schemaVersion: "1.0.0";
|
|
62
|
+
handoffId: string;
|
|
63
|
+
loopId: string;
|
|
64
|
+
generatedAt: string;
|
|
65
|
+
task: {
|
|
66
|
+
title: string;
|
|
67
|
+
objective: string;
|
|
68
|
+
};
|
|
69
|
+
definitionOfDone: {
|
|
70
|
+
acceptanceCriteria: string[];
|
|
71
|
+
verificationPlan: string[];
|
|
72
|
+
};
|
|
73
|
+
outcome: VerifiedHandoffOutcome;
|
|
74
|
+
sourceStatus: {
|
|
75
|
+
status: LoopStatus;
|
|
76
|
+
lifecycleState: LoopLifecycleState;
|
|
77
|
+
};
|
|
78
|
+
verification: {
|
|
79
|
+
status: EvidenceStatus;
|
|
80
|
+
summary: string;
|
|
81
|
+
checks: VerifiedHandoffCheckV1[];
|
|
82
|
+
warnings: string[];
|
|
83
|
+
};
|
|
84
|
+
requirements: VerifiedHandoffRequirementV1[];
|
|
85
|
+
scope: VerifiedHandoffScopeV1;
|
|
86
|
+
testIntegrity: VerifiedHandoffTestIntegrityV1;
|
|
87
|
+
unresolvedWork: string[];
|
|
88
|
+
stopReason?: string;
|
|
89
|
+
recovery: VerifiedHandoffRecoveryV1;
|
|
90
|
+
usage: {
|
|
91
|
+
attempts: number;
|
|
92
|
+
actualUsd: number;
|
|
93
|
+
estimatedUsd?: number;
|
|
94
|
+
tokensIn: number;
|
|
95
|
+
tokensOut: number;
|
|
96
|
+
costProvenance: CostProvenance;
|
|
97
|
+
};
|
|
98
|
+
receiptIntegrity: ReceiptIntegritySummary;
|
|
99
|
+
nextAction: string;
|
|
100
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: MartinLoop contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
export const VERIFIED_HANDOFF_OUTCOMES = [
|
|
4
|
+
"VERIFIED",
|
|
5
|
+
"STOPPED",
|
|
6
|
+
"NEEDS_REVIEW",
|
|
7
|
+
];
|
|
8
|
+
export const EVIDENCE_STATUSES = [
|
|
9
|
+
"PASSED",
|
|
10
|
+
"FAILED",
|
|
11
|
+
"CONTRADICTED",
|
|
12
|
+
"NOT_RUN",
|
|
13
|
+
"NOT_EVALUATED",
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Granular internal/runtime state. Retained for diagnosis, policy,
|
|
17
|
+
* receipts and engineering review.
|
|
18
|
+
*/
|
|
19
|
+
export const TEST_INTEGRITY_STATUSES = [
|
|
20
|
+
"UNCHANGED",
|
|
21
|
+
"AUTHORIZED_CHANGE",
|
|
22
|
+
"PREVENTED",
|
|
23
|
+
"DETECTED_AND_ROLLED_BACK",
|
|
24
|
+
"DETECTED_NEEDS_REVIEW",
|
|
25
|
+
"NOT_EVALUATED",
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Stable public-facing verdict used by CLI, MCP, JSON/Markdown handoffs
|
|
29
|
+
* and the website. Do not make each renderer collapse states independently.
|
|
30
|
+
*/
|
|
31
|
+
export const TEST_INTEGRITY_VERDICTS = [
|
|
32
|
+
"VERIFIED",
|
|
33
|
+
"TAMPERING_DETECTED",
|
|
34
|
+
"NOT_EVALUATED",
|
|
35
|
+
];
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Chain Gate — C1
|
|
3
|
+
*
|
|
4
|
+
* Converts chain verification evidence into a merge-gate decision and
|
|
5
|
+
* a PR comment (Infracost-style). Two things hard-fail. Everything else
|
|
6
|
+
* is configurable or comment-only.
|
|
7
|
+
*
|
|
8
|
+
* Hard failures (always block merge):
|
|
9
|
+
* 1. no_governance_receipt — no receipt at all
|
|
10
|
+
* 2. tamper_detected — receipt hash does not match producer run
|
|
11
|
+
*
|
|
12
|
+
* Configurable gate (team decides; default: warn only):
|
|
13
|
+
* 3. verifier_failed — unverified claims, incomplete integrity,
|
|
14
|
+
* missing artifacts, unresolved assumptions
|
|
15
|
+
*
|
|
16
|
+
* Comment-only (never block, always visible):
|
|
17
|
+
* - Cost: actual vs budget — money is already spent, blocking adds friction
|
|
18
|
+
* - Chain lineage: chainId, producerRunId, consumerRunId
|
|
19
|
+
* - Integrity state
|
|
20
|
+
*
|
|
21
|
+
* Standing rules:
|
|
22
|
+
* - Pure functions, no side effects.
|
|
23
|
+
* - evaluateChainGate never reads environment variables or config files.
|
|
24
|
+
* - renderGatePrComment produces valid GitHub-flavored markdown.
|
|
25
|
+
* - No fake data, no placeholder behaviour.
|
|
26
|
+
*/
|
|
27
|
+
import type { ContextHandoffReceipt } from "../contracts/index.js";
|
|
28
|
+
import type { ChainIntegrityState, ContextHandoffVerification } from "../contracts/index.js";
|
|
29
|
+
export interface ChainGateConfig {
|
|
30
|
+
/**
|
|
31
|
+
* When true, a failed verifier (unverified claims, incomplete integrity,
|
|
32
|
+
* missing artifacts, unresolved assumptions) blocks the merge.
|
|
33
|
+
* Default: false — warn in PR comment, do not block.
|
|
34
|
+
*/
|
|
35
|
+
blockOnVerifierFailure: boolean;
|
|
36
|
+
}
|
|
37
|
+
export interface ChainGateCost {
|
|
38
|
+
actualUsd: number;
|
|
39
|
+
budgetUsd: number;
|
|
40
|
+
}
|
|
41
|
+
export interface ChainGateInput {
|
|
42
|
+
/** null means no receipt was provided — triggers the hard no_governance_receipt failure. */
|
|
43
|
+
receipt: ContextHandoffReceipt | null;
|
|
44
|
+
/** null when receipt is null. Must be provided when receipt is non-null. */
|
|
45
|
+
verification: ContextHandoffVerification | null;
|
|
46
|
+
cost?: ChainGateCost;
|
|
47
|
+
config?: Partial<ChainGateConfig>;
|
|
48
|
+
}
|
|
49
|
+
export type ChainGateConclusion = "failure" | "neutral" | "success";
|
|
50
|
+
export interface ChainGateResult {
|
|
51
|
+
noGovernance: boolean;
|
|
52
|
+
tamperDetected: boolean;
|
|
53
|
+
verifierFailed: boolean;
|
|
54
|
+
shouldBlock: boolean;
|
|
55
|
+
conclusion: ChainGateConclusion;
|
|
56
|
+
failureReasonCode?: string;
|
|
57
|
+
failureMessage?: string;
|
|
58
|
+
cost?: ChainGateCost & {
|
|
59
|
+
exceeded: boolean;
|
|
60
|
+
};
|
|
61
|
+
integrity: ChainIntegrityState | "absent";
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Evaluates whether the chain gate should block merge, and assembles
|
|
65
|
+
* the data needed for the PR comment.
|
|
66
|
+
*
|
|
67
|
+
* Call order:
|
|
68
|
+
* 1. Check for missing receipt (hard fail).
|
|
69
|
+
* 2. Check for tamper_detected integrity (hard fail).
|
|
70
|
+
* 3. Check verifier result against config (configurable gate).
|
|
71
|
+
* 4. Return success / neutral with full comment data.
|
|
72
|
+
*/
|
|
73
|
+
export declare function evaluateChainGate(input: ChainGateInput): ChainGateResult;
|
|
74
|
+
export interface GatePrCommentOptions {
|
|
75
|
+
runId?: string;
|
|
76
|
+
chainId?: string;
|
|
77
|
+
handoffId?: string;
|
|
78
|
+
producerRunId?: string;
|
|
79
|
+
prNumber?: number;
|
|
80
|
+
headSha?: string;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Renders an Infracost-style GitHub PR comment summarising the gate result.
|
|
84
|
+
*
|
|
85
|
+
* Hard failures and cost overruns are shown prominently.
|
|
86
|
+
* Cost is always present when provided — never omitted, never a blocking signal.
|
|
87
|
+
* Lineage fields (chainId, producerRunId, runId) provide audit trail links.
|
|
88
|
+
*/
|
|
89
|
+
export declare function renderGatePrComment(result: ChainGateResult, options?: GatePrCommentOptions): string;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Chain Gate — C1
|
|
3
|
+
*
|
|
4
|
+
* Converts chain verification evidence into a merge-gate decision and
|
|
5
|
+
* a PR comment (Infracost-style). Two things hard-fail. Everything else
|
|
6
|
+
* is configurable or comment-only.
|
|
7
|
+
*
|
|
8
|
+
* Hard failures (always block merge):
|
|
9
|
+
* 1. no_governance_receipt — no receipt at all
|
|
10
|
+
* 2. tamper_detected — receipt hash does not match producer run
|
|
11
|
+
*
|
|
12
|
+
* Configurable gate (team decides; default: warn only):
|
|
13
|
+
* 3. verifier_failed — unverified claims, incomplete integrity,
|
|
14
|
+
* missing artifacts, unresolved assumptions
|
|
15
|
+
*
|
|
16
|
+
* Comment-only (never block, always visible):
|
|
17
|
+
* - Cost: actual vs budget — money is already spent, blocking adds friction
|
|
18
|
+
* - Chain lineage: chainId, producerRunId, consumerRunId
|
|
19
|
+
* - Integrity state
|
|
20
|
+
*
|
|
21
|
+
* Standing rules:
|
|
22
|
+
* - Pure functions, no side effects.
|
|
23
|
+
* - evaluateChainGate never reads environment variables or config files.
|
|
24
|
+
* - renderGatePrComment produces valid GitHub-flavored markdown.
|
|
25
|
+
* - No fake data, no placeholder behaviour.
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_CONFIG = {
|
|
28
|
+
blockOnVerifierFailure: false
|
|
29
|
+
};
|
|
30
|
+
// ─── Gate evaluation ─────────────────────────────────────────────────────────
|
|
31
|
+
/**
|
|
32
|
+
* Evaluates whether the chain gate should block merge, and assembles
|
|
33
|
+
* the data needed for the PR comment.
|
|
34
|
+
*
|
|
35
|
+
* Call order:
|
|
36
|
+
* 1. Check for missing receipt (hard fail).
|
|
37
|
+
* 2. Check for tamper_detected integrity (hard fail).
|
|
38
|
+
* 3. Check verifier result against config (configurable gate).
|
|
39
|
+
* 4. Return success / neutral with full comment data.
|
|
40
|
+
*/
|
|
41
|
+
export function evaluateChainGate(input) {
|
|
42
|
+
const config = { ...DEFAULT_CONFIG, ...input.config };
|
|
43
|
+
const costResult = input.cost !== undefined
|
|
44
|
+
? { ...input.cost, exceeded: input.cost.actualUsd > input.cost.budgetUsd }
|
|
45
|
+
: undefined;
|
|
46
|
+
// ── Hard fail 1: no governance receipt ───────────────────────────────────
|
|
47
|
+
if (input.receipt === null || input.verification === null) {
|
|
48
|
+
return {
|
|
49
|
+
noGovernance: true,
|
|
50
|
+
tamperDetected: false,
|
|
51
|
+
verifierFailed: false,
|
|
52
|
+
shouldBlock: true,
|
|
53
|
+
conclusion: "failure",
|
|
54
|
+
failureReasonCode: "no_governance_receipt",
|
|
55
|
+
failureMessage: "No MartinLoop governance receipt was found for this run. " +
|
|
56
|
+
"Every AI-authored PR must carry a verified governance receipt.",
|
|
57
|
+
integrity: "absent",
|
|
58
|
+
...(costResult !== undefined ? { cost: costResult } : {})
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const integrity = input.verification.integrity;
|
|
62
|
+
// ── Hard fail 2: tampered receipt ─────────────────────────────────────────
|
|
63
|
+
if (integrity === "tamper_detected") {
|
|
64
|
+
return {
|
|
65
|
+
noGovernance: false,
|
|
66
|
+
tamperDetected: true,
|
|
67
|
+
verifierFailed: false,
|
|
68
|
+
shouldBlock: true,
|
|
69
|
+
conclusion: "failure",
|
|
70
|
+
failureReasonCode: "tamper_detected",
|
|
71
|
+
failureMessage: "Governance receipt integrity check failed: tamper detected. " +
|
|
72
|
+
"The receipt hash does not match the producer run record.",
|
|
73
|
+
integrity,
|
|
74
|
+
...(costResult !== undefined ? { cost: costResult } : {})
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// ── Configurable gate: verifier failed ───────────────────────────────────
|
|
78
|
+
const verifierFailed = !input.verification.ok;
|
|
79
|
+
const blockOnVerifier = verifierFailed && config.blockOnVerifierFailure;
|
|
80
|
+
if (blockOnVerifier) {
|
|
81
|
+
const first = input.verification.reasons[0];
|
|
82
|
+
return {
|
|
83
|
+
noGovernance: false,
|
|
84
|
+
tamperDetected: false,
|
|
85
|
+
verifierFailed: true,
|
|
86
|
+
shouldBlock: true,
|
|
87
|
+
conclusion: "failure",
|
|
88
|
+
failureReasonCode: first?.code ?? "verifier_failed",
|
|
89
|
+
failureMessage: first?.message ?? "Context chain verification failed.",
|
|
90
|
+
integrity,
|
|
91
|
+
...(costResult !== undefined ? { cost: costResult } : {})
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// ── Pass (or warn-only verifier failure) ──────────────────────────────────
|
|
95
|
+
return {
|
|
96
|
+
noGovernance: false,
|
|
97
|
+
tamperDetected: false,
|
|
98
|
+
verifierFailed,
|
|
99
|
+
shouldBlock: false,
|
|
100
|
+
conclusion: verifierFailed ? "neutral" : "success",
|
|
101
|
+
integrity,
|
|
102
|
+
...(costResult !== undefined ? { cost: costResult } : {})
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Renders an Infracost-style GitHub PR comment summarising the gate result.
|
|
107
|
+
*
|
|
108
|
+
* Hard failures and cost overruns are shown prominently.
|
|
109
|
+
* Cost is always present when provided — never omitted, never a blocking signal.
|
|
110
|
+
* Lineage fields (chainId, producerRunId, runId) provide audit trail links.
|
|
111
|
+
*/
|
|
112
|
+
export function renderGatePrComment(result, options = {}) {
|
|
113
|
+
const lines = [];
|
|
114
|
+
lines.push("## MartinLoop Governance");
|
|
115
|
+
lines.push("");
|
|
116
|
+
// Status headline
|
|
117
|
+
if (result.noGovernance) {
|
|
118
|
+
lines.push("🚫 **No governance receipt** — merge is blocked.");
|
|
119
|
+
}
|
|
120
|
+
else if (result.tamperDetected) {
|
|
121
|
+
lines.push("🚫 **Tampered receipt** — merge is blocked.");
|
|
122
|
+
}
|
|
123
|
+
else if (result.verifierFailed && result.shouldBlock) {
|
|
124
|
+
lines.push("⚠️ **Verifier failed** — merge is blocked (configured as required).");
|
|
125
|
+
}
|
|
126
|
+
else if (result.verifierFailed) {
|
|
127
|
+
lines.push("⚠️ **Verifier warnings** — merge is permitted, review recommended.");
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
lines.push("✅ **Governance verified** — chain integrity confirmed.");
|
|
131
|
+
}
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push("| Signal | Value | Status |");
|
|
134
|
+
lines.push("|--------|-------|--------|");
|
|
135
|
+
// Chain integrity
|
|
136
|
+
const integrityIcon = result.integrity === "verified"
|
|
137
|
+
? "✅"
|
|
138
|
+
: result.integrity === "absent" || result.integrity === "tamper_detected"
|
|
139
|
+
? "🚫"
|
|
140
|
+
: "⚠️";
|
|
141
|
+
lines.push(`| Chain integrity | \`${result.integrity}\` | ${integrityIcon} |`);
|
|
142
|
+
// Cost — always comment-only, never a block signal
|
|
143
|
+
if (result.cost !== undefined) {
|
|
144
|
+
const { actualUsd, budgetUsd, exceeded } = result.cost;
|
|
145
|
+
const costIcon = exceeded ? "⚠️" : "✅";
|
|
146
|
+
const delta = actualUsd - budgetUsd;
|
|
147
|
+
const costNote = exceeded
|
|
148
|
+
? `over budget (+$${delta.toFixed(4)})`
|
|
149
|
+
: "within budget";
|
|
150
|
+
lines.push(`| Cost | \`$${actualUsd.toFixed(4)}\` / \`$${budgetUsd.toFixed(4)}\` | ${costIcon} ${costNote} |`);
|
|
151
|
+
}
|
|
152
|
+
// Lineage (audit trail, never a block signal)
|
|
153
|
+
if (options.chainId) {
|
|
154
|
+
lines.push(`| Chain ID | \`${options.chainId}\` | ℹ️ |`);
|
|
155
|
+
}
|
|
156
|
+
if (options.handoffId) {
|
|
157
|
+
lines.push(`| Handoff ID | \`${options.handoffId}\` | ℹ️ |`);
|
|
158
|
+
}
|
|
159
|
+
if (options.producerRunId) {
|
|
160
|
+
lines.push(`| Producer run | \`${options.producerRunId}\` | ℹ️ |`);
|
|
161
|
+
}
|
|
162
|
+
if (options.runId) {
|
|
163
|
+
lines.push(`| Consumer run | \`${options.runId}\` | ℹ️ |`);
|
|
164
|
+
}
|
|
165
|
+
// Failure detail
|
|
166
|
+
if (result.failureMessage) {
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push(`> **Failure:** ${result.failureMessage}`);
|
|
169
|
+
}
|
|
170
|
+
lines.push("");
|
|
171
|
+
lines.push("_Powered by [MartinLoop](https://github.com/Keesan12/martin-loop) governance gate_");
|
|
172
|
+
return lines.join("\n");
|
|
173
|
+
}
|