specrails-core 6.0.1 → 6.2.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 +3 -3
- package/dist/agent-runtime/capabilities.js +3 -1
- package/dist/agent-runtime/change-scope.js +2 -0
- package/dist/agent-runtime/cli-executor.d.ts +3 -0
- package/dist/agent-runtime/cli-executor.js +52 -14
- package/dist/agent-runtime/cli-process.js +6 -2
- package/dist/agent-runtime/cli.d.ts +15 -0
- package/dist/agent-runtime/cli.js +43 -9
- package/dist/agent-runtime/compact/chat-client.js +2 -1
- package/dist/agent-runtime/config.d.ts +4 -1
- package/dist/agent-runtime/config.js +41 -3
- package/dist/agent-runtime/core-host.d.ts +9 -3
- package/dist/agent-runtime/core-host.js +21 -18
- package/dist/agent-runtime/efficiency-summary.d.ts +4 -1
- package/dist/agent-runtime/efficiency-summary.js +30 -27
- package/dist/agent-runtime/efficiency-types.d.ts +4 -4
- package/dist/agent-runtime/efficiency.d.ts +1 -1
- package/dist/agent-runtime/efficiency.js +7 -6
- package/dist/agent-runtime/engine/__fixtures__/implementation-component.json +90 -0
- package/dist/agent-runtime/engine/__fixtures__/implementation-fixture.d.ts +11 -0
- package/dist/agent-runtime/engine/__fixtures__/implementation-fixture.js +60 -0
- package/dist/agent-runtime/engine/__fixtures__/implementation.json +43 -0
- package/dist/agent-runtime/engine/budget.d.ts +9 -0
- package/dist/agent-runtime/engine/budget.js +33 -0
- package/dist/agent-runtime/engine/canonical-json.d.ts +9 -0
- package/dist/agent-runtime/engine/canonical-json.js +162 -0
- package/dist/agent-runtime/engine/checkpoint/database.d.ts +67 -0
- package/dist/agent-runtime/engine/checkpoint/database.js +304 -0
- package/dist/agent-runtime/engine/checkpoint/lease.d.ts +17 -0
- package/dist/agent-runtime/engine/checkpoint/lease.js +82 -0
- package/dist/agent-runtime/engine/checkpoint/ledger.d.ts +133 -0
- package/dist/agent-runtime/engine/checkpoint/ledger.js +510 -0
- package/dist/agent-runtime/engine/checkpoint/saver.d.ts +25 -0
- package/dist/agent-runtime/engine/checkpoint/saver.js +146 -0
- package/dist/agent-runtime/engine/cli.d.ts +10 -0
- package/dist/agent-runtime/engine/cli.js +172 -0
- package/dist/agent-runtime/engine/compiler.d.ts +23 -0
- package/dist/agent-runtime/engine/compiler.js +262 -0
- package/dist/agent-runtime/engine/composition.d.ts +16 -0
- package/dist/agent-runtime/engine/composition.js +28 -0
- package/dist/agent-runtime/engine/concurrency.d.ts +14 -0
- package/dist/agent-runtime/engine/concurrency.js +55 -0
- package/dist/agent-runtime/engine/contracts.d.ts +273 -0
- package/dist/agent-runtime/engine/contracts.js +11 -0
- package/dist/agent-runtime/engine/definition-schema.d.ts +250 -0
- package/dist/agent-runtime/engine/definition-schema.js +315 -0
- package/dist/agent-runtime/engine/definition-types.d.ts +84 -0
- package/dist/agent-runtime/engine/definition-types.js +3 -0
- package/dist/agent-runtime/engine/definition-validator.d.ts +7 -0
- package/dist/agent-runtime/engine/definition-validator.js +191 -0
- package/dist/agent-runtime/engine/efficiency-summary.d.ts +62 -0
- package/dist/agent-runtime/engine/efficiency-summary.js +31 -0
- package/dist/agent-runtime/engine/events.d.ts +19 -0
- package/dist/agent-runtime/engine/events.js +88 -0
- package/dist/agent-runtime/engine/execution.d.ts +29 -0
- package/dist/agent-runtime/engine/execution.js +121 -0
- package/dist/agent-runtime/engine/expressions.d.ts +11 -0
- package/dist/agent-runtime/engine/expressions.js +185 -0
- package/dist/agent-runtime/engine/fork.d.ts +28 -0
- package/dist/agent-runtime/engine/fork.js +253 -0
- package/dist/agent-runtime/engine/graph-description.d.ts +48 -0
- package/dist/agent-runtime/engine/graph-description.js +16 -0
- package/dist/agent-runtime/engine/implementation-compiler.d.ts +214 -0
- package/dist/agent-runtime/engine/implementation-compiler.js +91 -0
- package/dist/agent-runtime/engine/index.d.ts +14 -0
- package/dist/agent-runtime/engine/index.js +14 -0
- package/dist/agent-runtime/engine/invocation-context.d.ts +11 -0
- package/dist/agent-runtime/engine/invocation-context.js +130 -0
- package/dist/agent-runtime/engine/otel.d.ts +15 -0
- package/dist/agent-runtime/engine/otel.js +132 -0
- package/dist/agent-runtime/engine/output.d.ts +4 -0
- package/dist/agent-runtime/engine/output.js +73 -0
- package/dist/agent-runtime/engine/piece-dependencies.d.ts +54 -0
- package/dist/agent-runtime/engine/piece-dependencies.js +240 -0
- package/dist/agent-runtime/engine/piece-registry.d.ts +19 -0
- package/dist/agent-runtime/engine/piece-registry.js +62 -0
- package/dist/agent-runtime/engine/pieces/artifact-contract.d.ts +4 -0
- package/dist/agent-runtime/engine/pieces/artifact-contract.js +70 -0
- package/dist/agent-runtime/engine/pieces/assign.d.ts +4 -0
- package/dist/agent-runtime/engine/pieces/assign.js +35 -0
- package/dist/agent-runtime/engine/pieces/control.d.ts +5 -0
- package/dist/agent-runtime/engine/pieces/control.js +53 -0
- package/dist/agent-runtime/engine/pieces/decider.d.ts +5 -0
- package/dist/agent-runtime/engine/pieces/decider.js +70 -0
- package/dist/agent-runtime/engine/pieces/implementation-binding.d.ts +13 -0
- package/dist/agent-runtime/engine/pieces/implementation-binding.js +40 -0
- package/dist/agent-runtime/engine/pieces/implementation-journal.d.ts +25 -0
- package/dist/agent-runtime/engine/pieces/implementation-journal.js +192 -0
- package/dist/agent-runtime/engine/pieces/implementation-step.d.ts +5 -0
- package/dist/agent-runtime/engine/pieces/implementation-step.js +47 -0
- package/dist/agent-runtime/engine/pieces/implementation.d.ts +27 -0
- package/dist/agent-runtime/engine/pieces/implementation.js +177 -0
- package/dist/agent-runtime/engine/pieces/index.d.ts +7 -0
- package/dist/agent-runtime/engine/pieces/index.js +24 -0
- package/dist/agent-runtime/engine/pieces/openspec.d.ts +3 -0
- package/dist/agent-runtime/engine/pieces/openspec.js +75 -0
- package/dist/agent-runtime/engine/pieces/ports.d.ts +53 -0
- package/dist/agent-runtime/engine/pieces/ports.js +1 -0
- package/dist/agent-runtime/engine/pieces/project-memory.d.ts +3 -0
- package/dist/agent-runtime/engine/pieces/project-memory.js +13 -0
- package/dist/agent-runtime/engine/pieces/prompt.d.ts +5 -0
- package/dist/agent-runtime/engine/pieces/prompt.js +130 -0
- package/dist/agent-runtime/engine/pieces/role-turn.d.ts +7 -0
- package/dist/agent-runtime/engine/pieces/role-turn.js +73 -0
- package/dist/agent-runtime/engine/pieces/shared.d.ts +67 -0
- package/dist/agent-runtime/engine/pieces/shared.js +40 -0
- package/dist/agent-runtime/engine/pieces/shell.d.ts +6 -0
- package/dist/agent-runtime/engine/pieces/shell.js +47 -0
- package/dist/agent-runtime/engine/pieces/verify.d.ts +7 -0
- package/dist/agent-runtime/engine/pieces/verify.js +102 -0
- package/dist/agent-runtime/engine/preflight.d.ts +27 -0
- package/dist/agent-runtime/engine/preflight.js +54 -0
- package/dist/agent-runtime/engine/regex.d.ts +3 -0
- package/dist/agent-runtime/engine/regex.js +19 -0
- package/dist/agent-runtime/engine/run-status.d.ts +563 -0
- package/dist/agent-runtime/engine/run-status.js +61 -0
- package/dist/agent-runtime/engine/runs.d.ts +605 -0
- package/dist/agent-runtime/engine/runs.js +247 -0
- package/dist/agent-runtime/engine/state.d.ts +64 -0
- package/dist/agent-runtime/engine/state.js +98 -0
- package/dist/agent-runtime/engine/steering/inbox.d.ts +54 -0
- package/dist/agent-runtime/engine/steering/inbox.js +113 -0
- package/dist/agent-runtime/engine/storage/fork-allocation.d.ts +21 -0
- package/dist/agent-runtime/engine/storage/fork-allocation.js +143 -0
- package/dist/agent-runtime/engine/storage/private-path.d.ts +11 -0
- package/dist/agent-runtime/engine/storage/private-path.js +74 -0
- package/dist/agent-runtime/engine/store/sqlite-store.d.ts +26 -0
- package/dist/agent-runtime/engine/store/sqlite-store.js +227 -0
- package/dist/agent-runtime/evaluation-corpus.d.ts +125 -0
- package/dist/agent-runtime/evaluation-corpus.js +4 -0
- package/dist/agent-runtime/evaluation.d.ts +9 -3
- package/dist/agent-runtime/evaluation.js +50 -8
- package/dist/agent-runtime/executor-types.d.ts +53 -3
- package/dist/agent-runtime/executor-types.js +35 -2
- package/dist/agent-runtime/executors.js +2 -2
- package/dist/agent-runtime/graph/artifacts.d.ts +7 -0
- package/dist/agent-runtime/graph/artifacts.js +82 -9
- package/dist/agent-runtime/graph/nodes.d.ts +2 -0
- package/dist/agent-runtime/graph/nodes.js +2 -2
- package/dist/agent-runtime/graph/roles.d.ts +8 -0
- package/dist/agent-runtime/graph/roles.js +25 -17
- package/dist/agent-runtime/kimi-acp.js +6 -6
- package/dist/agent-runtime/openai-executor.js +17 -12
- package/dist/agent-runtime/openspec.d.ts +17 -2
- package/dist/agent-runtime/openspec.js +54 -20
- package/dist/agent-runtime/prompts.d.ts +4 -3
- package/dist/agent-runtime/prompts.js +27 -6
- package/dist/agent-runtime/recovery.d.ts +1 -1
- package/dist/agent-runtime/repository-context.js +1 -1
- package/dist/agent-runtime/role-routing.js +4 -2
- package/dist/agent-runtime/role-state.d.ts +5 -0
- package/dist/agent-runtime/verification-plan.d.ts +2 -0
- package/dist/agent-runtime/verification-plan.js +10 -0
- package/dist/agent-runtime/workflow-types.d.ts +2 -0
- package/dist/agent-runtime/workflow.d.ts +3 -1
- package/dist/agent-runtime/workflow.js +7 -3
- package/dist/agent-runtime/workspace-tools.d.ts +2 -2
- package/dist/agent-runtime/workspace-tools.js +6 -6
- package/dist/installer/cli.js +5 -0
- package/dist/installer/phases/scaffold.d.ts +2 -2
- package/dist/installer/phases/scaffold.js +3 -7
- package/dist/pipeline/pipeline-state.d.ts +38 -4
- package/dist/pipeline/pipeline-state.js +96 -21
- package/integration-contract.json +111 -19
- package/package.json +13 -6
- package/schemas/agent-runtime.schema.json +93 -1
- package/schemas/workflow-definition.schema.json +314 -0
- package/templates/kimi/specrails/run-skill.mjs +15 -1
- package/templates/commands/specrails/batch-implement.md +0 -32
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBuiltinRole } from './executor-types.js';
|
|
1
2
|
export function failedCandidateCount(checkpoint) {
|
|
2
3
|
let candidate;
|
|
3
4
|
const failures = new Set();
|
|
@@ -16,8 +17,9 @@ export function selectRoleRoute(role, selected, kind, checkpoint, previous) {
|
|
|
16
17
|
const reason = previous?.tier === 'escalation' ? previous.reason
|
|
17
18
|
: role === 'architect' && kind === 'deepen' ? 'Architect requested its single deeper investigation'
|
|
18
19
|
: role === 'reviewer' && kind === 'repair' ? 'Reviewer required its single protocol repair'
|
|
19
|
-
: role
|
|
20
|
-
:
|
|
20
|
+
: !isBuiltinRole(role) && kind === 'repair' ? 'Configured role required its single protocol repair'
|
|
21
|
+
: role === 'developer' && ['initial', 'correction'].includes(kind) && failedCandidateCount(checkpoint) >= 2 ? 'Two completed candidate attempts failed verification or review'
|
|
22
|
+
: undefined;
|
|
21
23
|
if (!selected.escalation || !reason)
|
|
22
24
|
return { tier: 'base', reason: 'Configured base role selection', selection: selected };
|
|
23
25
|
return { tier: 'escalation', reason, selection: {
|
|
@@ -13,6 +13,11 @@ export interface RoleExecutionState {
|
|
|
13
13
|
attemptId: string;
|
|
14
14
|
}>>;
|
|
15
15
|
}
|
|
16
|
+
/** Bound by the engine to one run/scope/node; the legacy adapter remains file-backed. */
|
|
17
|
+
export interface RoleStatePort {
|
|
18
|
+
read(): RoleExecutionState;
|
|
19
|
+
write(state: RoleExecutionState): void;
|
|
20
|
+
}
|
|
16
21
|
/** Host-owned auxiliary state lives under the workflow's existing run lease.
|
|
17
22
|
* A corrupted packet fails explicitly; it is never used as missing context. */
|
|
18
23
|
export declare function readRoleState(directory: string): RoleExecutionState;
|
|
@@ -53,4 +53,6 @@ export declare function addDeveloperChecks(context: PipelineContext, proposals:
|
|
|
53
53
|
export declare function expandedPlanCommands(context: PipelineContext, plan: VerificationPlan): VerificationCommand[];
|
|
54
54
|
/** Bind the exact manifest and every source to lifecycle gates. */
|
|
55
55
|
export declare function bindPlan(context: PipelineContext, plan: VerificationPlan): void;
|
|
56
|
+
/** Rebind a checkpoint-owned plan to a new run, retaining the exact admitted checks. */
|
|
57
|
+
export declare function forkVerificationPlan(context: PipelineContext, source: VerificationPlan, sourceContext: PipelineContext): VerificationPlan;
|
|
56
58
|
export {};
|
|
@@ -208,3 +208,13 @@ export function bindPlan(context, plan) {
|
|
|
208
208
|
}
|
|
209
209
|
bindVerificationPlan(context, plan.planHash, files);
|
|
210
210
|
}
|
|
211
|
+
/** Rebind a checkpoint-owned plan to a new run, retaining the exact admitted checks. */
|
|
212
|
+
export function forkVerificationPlan(context, source, sourceContext) {
|
|
213
|
+
const { integrity, ...payload } = source;
|
|
214
|
+
if (integrity !== fingerprint(payload) || source.scopeHash !== fingerprint(sourceContext) || source.runId !== sourceContext.runId
|
|
215
|
+
|| source.planHash !== fingerprint({ policyVersion: 1, scopeHash: source.scopeHash, executionPolicy: source.executionPolicy, entries: source.entries }))
|
|
216
|
+
throw new Error('Fork verification plan failed its source integrity check');
|
|
217
|
+
const plan = savePlan(context, source.baseline, source.developer, null, source.executionPolicy);
|
|
218
|
+
bindPlan(context, plan);
|
|
219
|
+
return plan;
|
|
220
|
+
}
|
|
@@ -52,6 +52,8 @@ export interface WorkflowStepContext {
|
|
|
52
52
|
attemptId: string;
|
|
53
53
|
attempt: number;
|
|
54
54
|
input: JsonValue;
|
|
55
|
+
/** Engine-owned, durably claimed operator text; cannot replace the frozen scope. */
|
|
56
|
+
operatorSteering?: string;
|
|
55
57
|
signal: AbortSignal;
|
|
56
58
|
/** A detached snapshot; modifying it cannot modify the persisted run. */
|
|
57
59
|
checkpoint: WorkflowState;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import type { RunWorkflowOptions, WorkflowState } from './workflow-types.js';
|
|
1
|
+
import type { RunWorkflowOptions, WorkflowDefinition, WorkflowState } from './workflow-types.js';
|
|
2
2
|
export * from './workflow-types.js';
|
|
3
3
|
export { readWorkflowState, readWorkflowEnvelope, writeWorkflowEnvelope, WorkflowStoreError, type WorkflowEnvelope } from './durable-store.js';
|
|
4
4
|
export declare class WorkflowError extends Error {
|
|
5
5
|
readonly code: 'INVALID_WORKFLOW' | 'ALREADY_EXISTS' | 'NOT_FOUND' | 'INCOMPATIBLE_RESUME';
|
|
6
6
|
constructor(code: 'INVALID_WORKFLOW' | 'ALREADY_EXISTS' | 'NOT_FOUND' | 'INCOMPATIBLE_RESUME', message: string);
|
|
7
7
|
}
|
|
8
|
+
/** Pure legacy resume identity; preserve node order, declared exits and default metadata. */
|
|
9
|
+
export declare function definitionFingerprint<S extends Record<string, unknown>>(workflow: WorkflowDefinition<S>): string;
|
|
8
10
|
/**
|
|
9
11
|
* Execute a LangGraph state graph with a durable host ledger at every effect
|
|
10
12
|
* boundary. LangGraph owns traversal, state reducers, checkpoints, interrupts
|
|
@@ -63,9 +63,13 @@ function validateWorkflow(workflow) {
|
|
|
63
63
|
if (!Number.isInteger(maxTransitions) || maxTransitions < 1 || maxTransitions > 10_000) {
|
|
64
64
|
throw new WorkflowError('INVALID_WORKFLOW', 'maxTransitions must be an integer from 1 to 10000');
|
|
65
65
|
}
|
|
66
|
+
return definitionFingerprint(workflow);
|
|
67
|
+
}
|
|
68
|
+
/** Pure legacy resume identity; preserve node order, declared exits and default metadata. */
|
|
69
|
+
export function definitionFingerprint(workflow) {
|
|
66
70
|
return fingerprint({
|
|
67
|
-
id: workflow.id, version: workflow.version, maxTransitions, entry: workflow.entry,
|
|
68
|
-
nodes:
|
|
71
|
+
id: workflow.id, version: workflow.version, maxTransitions: workflow.maxTransitions ?? 100, entry: workflow.entry,
|
|
72
|
+
nodes: Object.keys(workflow.nodes).map(id => ({ id, effect: workflow.nodes[id].effect ?? 'read', maxAttempts: workflow.nodes[id].maxAttempts ?? 1, retrySafe: workflow.nodes[id].retrySafe ?? false, ends: workflow.nodes[id].ends })),
|
|
69
73
|
});
|
|
70
74
|
}
|
|
71
75
|
function validateUsage(usage) {
|
|
@@ -406,7 +410,7 @@ export async function runWorkflow(options) {
|
|
|
406
410
|
},
|
|
407
411
|
reportInvocation: async (invocation) => {
|
|
408
412
|
validateUsage(invocation.usage);
|
|
409
|
-
if (!Number.isFinite(invocation.durationMs) || invocation.durationMs < 0 || !Number.isSafeInteger(invocation.toolCalls) || invocation.toolCalls < 0)
|
|
413
|
+
if (typeof invocation.durationMs !== 'number' || !Number.isFinite(invocation.durationMs) || invocation.durationMs < 0 || typeof invocation.toolCalls !== 'number' || !Number.isSafeInteger(invocation.toolCalls) || invocation.toolCalls < 0)
|
|
410
414
|
throw new TypeError('Invalid invocation measurement');
|
|
411
415
|
if (invocation.invocationId && history.invocations?.some(item => item.invocationId === invocation.invocationId))
|
|
412
416
|
return;
|
|
@@ -13,11 +13,11 @@ export declare function canonicalWorkspace(cwd: string, allowedRoots: string[]):
|
|
|
13
13
|
};
|
|
14
14
|
/** Application tool boundary, not an OS sandbox for concurrent external processes. */
|
|
15
15
|
export declare class WorkspaceTools {
|
|
16
|
-
private readonly
|
|
16
|
+
private readonly access;
|
|
17
17
|
readonly cwd: string;
|
|
18
18
|
readonly roots: string[];
|
|
19
19
|
private readonly rootAliases;
|
|
20
|
-
constructor(cwd: string, roots: string[],
|
|
20
|
+
constructor(cwd: string, roots: string[], access: 'read' | 'write' | AgentRole);
|
|
21
21
|
definitions(): WorkspaceToolDefinition[];
|
|
22
22
|
private resolve;
|
|
23
23
|
execute(name: string, input: unknown): string;
|
|
@@ -33,12 +33,12 @@ export function canonicalWorkspace(cwd, allowedRoots) {
|
|
|
33
33
|
}
|
|
34
34
|
/** Application tool boundary, not an OS sandbox for concurrent external processes. */
|
|
35
35
|
export class WorkspaceTools {
|
|
36
|
-
|
|
36
|
+
access;
|
|
37
37
|
cwd;
|
|
38
38
|
roots;
|
|
39
39
|
rootAliases;
|
|
40
|
-
constructor(cwd, roots,
|
|
41
|
-
this.
|
|
40
|
+
constructor(cwd, roots, access) {
|
|
41
|
+
this.access = access;
|
|
42
42
|
const scope = canonicalWorkspace(cwd, roots);
|
|
43
43
|
this.cwd = scope.cwd;
|
|
44
44
|
this.roots = scope.roots;
|
|
@@ -52,8 +52,8 @@ export class WorkspaceTools {
|
|
|
52
52
|
definition('read_lines', 'Read numbered lines, up to 500 lines / 48 KiB, from a UTF-8 file up to 1 MiB. Returns a full-file SHA-256 for guarded edits.', { ...file, startLine: { type: 'integer', minimum: 1 }, endLine: { type: 'integer', minimum: 1 } }, ['path']),
|
|
53
53
|
definition('search_text', 'Search literal text recursively in source files. Skips dependencies, build output, metadata, binaries and symlinks. Returns bounded numbered matches and a truncation flag.', { ...file, query: { type: 'string' }, maxResults: { type: 'integer', minimum: 1, maximum: 100 } }, ['path', 'query']),
|
|
54
54
|
definition('get_diff', 'Read the staged and unstaged Git diff against HEAD for one file, up to 48 KiB. Untracked files are identified; use read_lines for their content.', file, ['path']),
|
|
55
|
-
...(this.
|
|
56
|
-
...(this.
|
|
55
|
+
...((this.access === 'write' || this.access === 'developer') ? [definition('write_file', 'Create or replace a UTF-8 source file (max 256 KiB). Parent directories are created. Runtime metadata cannot be changed.', { ...file, content: { type: 'string' } }, ['path', 'content'])] : []),
|
|
56
|
+
...((this.access === 'write' || this.access === 'developer') ? [definition('apply_patch', 'Atomically replace one exact, unique fragment in a UTF-8 file. Rejects missing or ambiguous matches. Optionally guard against stale reads with expectedHash from read_lines.', { ...file, oldText: { type: 'string', minLength: 1 }, newText: { type: 'string' }, expectedHash: { type: 'string' } }, ['path', 'oldText', 'newText'])] : []),
|
|
57
57
|
];
|
|
58
58
|
}
|
|
59
59
|
resolve(raw, write) {
|
|
@@ -92,7 +92,7 @@ export class WorkspaceTools {
|
|
|
92
92
|
throw new Error('Tool input must be a JSON object');
|
|
93
93
|
const args = input;
|
|
94
94
|
if (!this.definitions().some(tool => tool.function.name === name))
|
|
95
|
-
throw new Error(`Tool '${name}' is unavailable for the ${this.
|
|
95
|
+
throw new Error(`Tool '${name}' is unavailable for the ${this.access} role`);
|
|
96
96
|
const allowed = name === 'write_file' ? ['path', 'content'] : name === 'apply_patch' ? ['path', 'oldText', 'newText', 'expectedHash'] : name === 'read_lines' ? ['path', 'startLine', 'endLine'] : name === 'search_text' ? ['path', 'query', 'maxResults'] : ['path'];
|
|
97
97
|
if (Object.keys(args).some(key => !allowed.includes(key)) || typeof args.path !== 'string')
|
|
98
98
|
throw new Error('Invalid tool arguments');
|
package/dist/installer/cli.js
CHANGED
|
@@ -99,6 +99,11 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
99
99
|
return await dispatch(subcommand, flags, positionals);
|
|
100
100
|
}
|
|
101
101
|
catch (err) {
|
|
102
|
+
if (subcommand === 'runtime') {
|
|
103
|
+
const { runtimeFailure } = await import('../agent-runtime/cli.js');
|
|
104
|
+
process.stdout.write(JSON.stringify(runtimeFailure(err)) + '\n');
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
102
107
|
if (isInstallerError(err)) {
|
|
103
108
|
fatal(err.message);
|
|
104
109
|
return err.exitCode;
|
|
@@ -4,8 +4,8 @@ import type { Provider } from './provider-detect.js';
|
|
|
4
4
|
* ships. The implement pipeline depends on all three. Any additional agent
|
|
5
5
|
* comes from a user-authored profile (`custom-*`), never the installer.
|
|
6
6
|
*
|
|
7
|
-
* Mirrors the
|
|
8
|
-
*
|
|
7
|
+
* Mirrors the required built-in roles in schemas/agent-runtime.schema.json
|
|
8
|
+
* (`agents`, with the sr- prefix in installed files); keep both in sync.
|
|
9
9
|
*/
|
|
10
10
|
export declare const CORE_AGENTS: Set<string>;
|
|
11
11
|
/**
|
|
@@ -11,8 +11,8 @@ import { buildManifest, writeManifestFiles } from './manifest.js';
|
|
|
11
11
|
* ships. The implement pipeline depends on all three. Any additional agent
|
|
12
12
|
* comes from a user-authored profile (`custom-*`), never the installer.
|
|
13
13
|
*
|
|
14
|
-
* Mirrors the
|
|
15
|
-
*
|
|
14
|
+
* Mirrors the required built-in roles in schemas/agent-runtime.schema.json
|
|
15
|
+
* (`agents`, with the sr- prefix in installed files); keep both in sync.
|
|
16
16
|
*/
|
|
17
17
|
export const CORE_AGENTS = new Set([
|
|
18
18
|
'sr-architect',
|
|
@@ -151,10 +151,6 @@ const SKILL_FROM_COMMAND = {
|
|
|
151
151
|
command: 'implement',
|
|
152
152
|
description: 'sr:implement — Full OpenSpec lifecycle with specialized agents: architect designs, developer implements, reviewer validates. Use for implementing frozen specs or feature descriptions.',
|
|
153
153
|
},
|
|
154
|
-
'sr-batch-implement': {
|
|
155
|
-
command: 'batch-implement',
|
|
156
|
-
description: 'sr:batch-implement — Run multiple frozen specs as one programmatic agent workflow.',
|
|
157
|
-
},
|
|
158
154
|
};
|
|
159
155
|
/**
|
|
160
156
|
* Provider-static subtrees inside a providerDir that are SHARED via symlink from
|
|
@@ -1557,7 +1553,7 @@ function renderInitialAgentsMd(repoRoot) {
|
|
|
1557
1553
|
'',
|
|
1558
1554
|
'Implementation is coordinated by the Specrails programmatic agent runtime.',
|
|
1559
1555
|
'Use the frozen scope and official OpenSpec workflow supplied for your role.',
|
|
1560
|
-
'Do not start another implement
|
|
1556
|
+
'Do not start another implement workflow inside a role.',
|
|
1561
1557
|
'',
|
|
1562
1558
|
'## Repository context',
|
|
1563
1559
|
'',
|
|
@@ -187,6 +187,9 @@ export interface PipelineState {
|
|
|
187
187
|
acceptanceHash: string;
|
|
188
188
|
};
|
|
189
189
|
artifactExclusions: string[];
|
|
190
|
+
/** Explicit engine-owned metadata; absent for all legacy journals. */
|
|
191
|
+
runtimeExclusions?: readonly string[];
|
|
192
|
+
repositoryExclusions?: Readonly<Record<string, readonly string[]>>;
|
|
190
193
|
preview?: {
|
|
191
194
|
baseHash: string;
|
|
192
195
|
files: PreviewFile[];
|
|
@@ -207,6 +210,18 @@ export declare function withinRepositoryScope(repository: Pick<PipelineRepositor
|
|
|
207
210
|
export declare function repositoryWorkingDirectory(repository: Pick<PipelineRepository, 'path' | 'scope'>): string;
|
|
208
211
|
export declare function validatePipelineContext(input: unknown): PipelineContext;
|
|
209
212
|
export declare function pipelineStateDirectory(context: PipelineContext): string;
|
|
213
|
+
export type CandidateScope = Pick<PipelineState, 'context' | 'scopeHash' | 'artifactExclusions'> & {
|
|
214
|
+
/** Engine-owned child journals and artifact paths, never user-defined exclusion patterns. */
|
|
215
|
+
runtimeExclusions?: readonly string[];
|
|
216
|
+
repositoryExclusions?: Readonly<Record<string, readonly string[]>>;
|
|
217
|
+
};
|
|
218
|
+
/** Bind only engine-owned metadata paths; callers supply declared run/branch ownership. */
|
|
219
|
+
export declare function bindImplementationExclusions(contextInput: PipelineContext, exclusions: {
|
|
220
|
+
runtimeExclusions: readonly string[];
|
|
221
|
+
repositoryExclusions: Readonly<Record<string, readonly string[]>>;
|
|
222
|
+
}): void;
|
|
223
|
+
/** Read the validated implementation journal's dynamic candidate exclusions. */
|
|
224
|
+
export declare function readCandidateScope(contextInput: unknown): CandidateScope;
|
|
210
225
|
export interface CandidateManifest {
|
|
211
226
|
schemaVersion: 1;
|
|
212
227
|
scopeHash: string;
|
|
@@ -216,8 +231,8 @@ export interface CandidateManifest {
|
|
|
216
231
|
files: Array<[string, string]>;
|
|
217
232
|
}>;
|
|
218
233
|
}
|
|
219
|
-
export declare function candidateManifest(state:
|
|
220
|
-
export declare function fingerprintCandidate(state:
|
|
234
|
+
export declare function candidateManifest(state: CandidateScope): CandidateManifest;
|
|
235
|
+
export declare function fingerprintCandidate(state: CandidateScope): string;
|
|
221
236
|
export declare function initializePipeline(contextInput: unknown, change: string): PipelineState;
|
|
222
237
|
/** Host-owned binding, including immutable harness bytes outside the candidate. */
|
|
223
238
|
export declare function bindVerificationPlan(contextInput: PipelineContext, hash: string, files: Array<{
|
|
@@ -270,6 +285,11 @@ export declare function verificationInvocation(command: string, args: string[],
|
|
|
270
285
|
};
|
|
271
286
|
/** Shared persisted-output/provider diagnostic redaction; preserves line structure. */
|
|
272
287
|
export declare function redactRuntimeText(text: string, env?: NodeJS.ProcessEnv): string;
|
|
288
|
+
/** Structured shell execution with the same scope, environment and tree-kill rules, without a receipt or journal. */
|
|
289
|
+
export declare function executeVerificationCommand(contextInput: unknown, raw: unknown, log?: (text: string) => void, signal?: AbortSignal, options?: {
|
|
290
|
+
deadline?: number;
|
|
291
|
+
idleTimeoutMs?: number;
|
|
292
|
+
}): Promise<CommandReceipt>;
|
|
273
293
|
export interface VerificationSnapshot {
|
|
274
294
|
eligible: boolean;
|
|
275
295
|
reason: string;
|
|
@@ -280,12 +300,25 @@ export interface VerificationSnapshot {
|
|
|
280
300
|
export declare function verificationSnapshot(context: PipelineContext, command: VerificationCommand): VerificationSnapshot;
|
|
281
301
|
/** Contiguous waves preserve baseline order; an uncertain check is a barrier. */
|
|
282
302
|
export declare function verificationWaves<T extends VerificationCommand>(commands: T[], maxConcurrency?: number): T[][];
|
|
283
|
-
export
|
|
303
|
+
export interface VerificationRunOptions {
|
|
284
304
|
deadline?: number;
|
|
285
305
|
maxConcurrency?: number;
|
|
286
306
|
idleTimeoutMs?: number;
|
|
287
307
|
onEvidence?: (kind: 'check-started' | 'check-finished' | 'check-reused' | 'check-invalidated', payload: Record<string, string | number | null>) => Promise<void>;
|
|
288
|
-
}
|
|
308
|
+
}
|
|
309
|
+
/** Persistence and candidate ownership are bound by the host, never inferred from a journal path. */
|
|
310
|
+
export interface VerificationEvidencePort {
|
|
311
|
+
candidateHash: string;
|
|
312
|
+
scopeHash: string;
|
|
313
|
+
planHash?: string;
|
|
314
|
+
previous?: VerificationReceipt;
|
|
315
|
+
isCurrent(): boolean;
|
|
316
|
+
persistCheck(result: CommandReceipt, planHash: string, candidateHash: string): void | Promise<void>;
|
|
317
|
+
commitReceipt(receipt: VerificationReceipt): VerificationReceipt | Promise<VerificationReceipt>;
|
|
318
|
+
}
|
|
319
|
+
export declare function verifyPipeline(contextInput: unknown, raw: unknown, log?: (text: string) => void, signal?: AbortSignal, options?: VerificationRunOptions): Promise<VerificationReceipt>;
|
|
320
|
+
/** Same deterministic command/evidence runner for legacy journals and ledger-only engine runs. */
|
|
321
|
+
export declare function executeVerification(contextInput: unknown, raw: unknown, evidence: VerificationEvidencePort, log?: (text: string) => void, signal?: AbortSignal, options?: VerificationRunOptions): Promise<VerificationReceipt>;
|
|
289
322
|
/** Validate a check plan without creating state or executing commands. */
|
|
290
323
|
export declare function validateVerificationRequest(contextInput: unknown, raw: unknown): void;
|
|
291
324
|
export declare function preparePreview(contextInput: unknown, raw: unknown): PipelineState;
|
|
@@ -299,6 +332,7 @@ export interface VerificationEvidenceQuery {
|
|
|
299
332
|
cursor?: string;
|
|
300
333
|
limit?: number;
|
|
301
334
|
}
|
|
335
|
+
export declare function persistCheckEvidence(context: PipelineContext, result: CommandReceipt, planHash: string, candidateHash: string): void;
|
|
302
336
|
/** Historical read: no live repository validation, source execution or lifecycle mutation. */
|
|
303
337
|
export declare function readVerificationEvidence(context: Pick<PipelineContext, 'backlogRoot' | 'runId'>, query?: VerificationEvidenceQuery): {
|
|
304
338
|
nextCursor?: string | undefined;
|
|
@@ -273,10 +273,56 @@ function saveState(state) {
|
|
|
273
273
|
atomicJson(stateFile(state.context), state);
|
|
274
274
|
}
|
|
275
275
|
function relativeUnix(value) { return value.split(path.sep).join('/'); }
|
|
276
|
+
/** Bind only engine-owned metadata paths; callers supply declared run/branch ownership. */
|
|
277
|
+
export function bindImplementationExclusions(contextInput, exclusions) {
|
|
278
|
+
const context = validatePipelineContext(contextInput);
|
|
279
|
+
const root = path.join(context.backlogRoot, '.specrails/pipeline');
|
|
280
|
+
const runtimeExclusions = [...new Set(exclusions.runtimeExclusions)].sort();
|
|
281
|
+
for (const item of runtimeExclusions) {
|
|
282
|
+
const relative = path.relative(root, item);
|
|
283
|
+
const memory = ['', '-wal', '-shm'].some(suffix => item === path.join(context.backlogRoot, '.specrails/engine-store.sqlite' + suffix));
|
|
284
|
+
if (memory) {
|
|
285
|
+
safeChild(context.backlogRoot, path.relative(context.backlogRoot, item));
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!path.isAbsolute(item) || path.resolve(item) !== item || !ID.test(relative) || relative.includes(path.sep))
|
|
289
|
+
fail('Implementation runtime exclusion is not an admitted pipeline journal');
|
|
290
|
+
safeChild(root, relative);
|
|
291
|
+
}
|
|
292
|
+
const repositoryExclusions = {};
|
|
293
|
+
for (const [id, paths] of Object.entries(exclusions.repositoryExclusions)) {
|
|
294
|
+
const repository = context.repositories.find(repository => repository.id === id);
|
|
295
|
+
if (!repository)
|
|
296
|
+
continue; // A scoped child receives the parent union, filtered to its frozen repositories.
|
|
297
|
+
repositoryExclusions[id] = [...new Set(paths)].sort();
|
|
298
|
+
for (const item of repositoryExclusions[id]) {
|
|
299
|
+
if (!/^(?:[a-zA-Z0-9][a-zA-Z0-9._-]*\/)*openspec\/(?:changes|specs)\/[a-z0-9][a-z0-9./-]*$/.test(item))
|
|
300
|
+
fail('Implementation artifact exclusion is outside declared OpenSpec artifacts');
|
|
301
|
+
safeChild(repository.path, item);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
locked(context, () => {
|
|
305
|
+
const state = readState(context);
|
|
306
|
+
if (canonical(state.runtimeExclusions ?? []) === canonical(runtimeExclusions) && canonical(state.repositoryExclusions ?? {}) === canonical(repositoryExclusions))
|
|
307
|
+
return;
|
|
308
|
+
state.runtimeExclusions = runtimeExclusions;
|
|
309
|
+
state.repositoryExclusions = repositoryExclusions;
|
|
310
|
+
saveState(state);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
/** Read the validated implementation journal's dynamic candidate exclusions. */
|
|
314
|
+
export function readCandidateScope(contextInput) {
|
|
315
|
+
const state = readState(validatePipelineContext(contextInput));
|
|
316
|
+
return { context: state.context, scopeHash: state.scopeHash, artifactExclusions: [...state.artifactExclusions], ...(state.runtimeExclusions ? { runtimeExclusions: [...state.runtimeExclusions] } : {}), ...(state.repositoryExclusions ? { repositoryExclusions: structuredClone(state.repositoryExclusions) } : {}) };
|
|
317
|
+
}
|
|
276
318
|
function excluded(state, repo, relative) {
|
|
277
319
|
const absolute = path.join(repo.path, relative);
|
|
278
320
|
if (within(pipelineStateDirectory(state.context), absolute))
|
|
279
321
|
return true;
|
|
322
|
+
if (state.runtimeExclusions?.some(directory => within(directory, absolute)))
|
|
323
|
+
return true;
|
|
324
|
+
if (state.repositoryExclusions?.[repo.id]?.some(item => relative === item || relative.startsWith(item + '/')))
|
|
325
|
+
return true;
|
|
280
326
|
if (relative === '.specrails/runtime' || relative.startsWith('.specrails/runtime/'))
|
|
281
327
|
return true;
|
|
282
328
|
if (repo.id !== state.context.artifactRepositoryId)
|
|
@@ -768,6 +814,8 @@ function validateCommand(context, raw) {
|
|
|
768
814
|
const cwd = command.cwd === undefined ? directory(repositoryWorkingDirectory(repo)) : directory(path.resolve(repo.path, String(command.cwd)));
|
|
769
815
|
if (!within(repo.path, cwd))
|
|
770
816
|
fail('Verification cwd escapes selected repository');
|
|
817
|
+
if (!withinRepositoryScope(repo, path.relative(repo.path, cwd).split(path.sep).join('/')))
|
|
818
|
+
fail('Verification cwd escapes selected code workspace');
|
|
771
819
|
const env = command.env === undefined ? undefined : object(command.env);
|
|
772
820
|
if (env && Object.values(env).some((value) => typeof value !== 'string' || value.includes('\0')))
|
|
773
821
|
fail('Invalid verification environment');
|
|
@@ -805,6 +853,12 @@ function validateCommand(context, raw) {
|
|
|
805
853
|
export function verificationInvocation(command, args, cwd, platform = process.platform, env = process.env) {
|
|
806
854
|
if (platform !== 'win32')
|
|
807
855
|
return { command, args };
|
|
856
|
+
// An explicit shell line (`cmd /d /s /c <line>`) is already cmd syntax. Node's
|
|
857
|
+
// default quoting would escape its inner quotes with backslashes, which cmd does
|
|
858
|
+
// not understand. Pass it verbatim: /s strips exactly the outer quotes we add.
|
|
859
|
+
if (/(^|[\\/])cmd(\.exe)?$/i.test(command) && args.length === 4 && args[0].toLowerCase() === '/d' && args[1].toLowerCase() === '/s' && args[2].toLowerCase() === '/c') {
|
|
860
|
+
return { command, args: ['/d', '/s', '/c', '"' + args[3] + '"'], windowsVerbatimArguments: true };
|
|
861
|
+
}
|
|
808
862
|
let resolved = command;
|
|
809
863
|
if (!/\.(cmd|bat|exe|com)$/i.test(command)) {
|
|
810
864
|
const pathValue = Object.entries(env).find(([key]) => key.toLowerCase() === 'path')?.[1] ?? '';
|
|
@@ -845,6 +899,10 @@ export function redactRuntimeText(text, env = process.env) {
|
|
|
845
899
|
}
|
|
846
900
|
/** Silence a verification command may keep before it counts as hung (env `SPECRAILS_VERIFY_IDLE_TIMEOUT_MS`, default 5 min). */
|
|
847
901
|
function verificationIdleTimeoutMs() { const raw = Number(process.env.SPECRAILS_VERIFY_IDLE_TIMEOUT_MS); return Number.isFinite(raw) && raw >= 100 ? raw : 5 * 60_000; }
|
|
902
|
+
/** Structured shell execution with the same scope, environment and tree-kill rules, without a receipt or journal. */
|
|
903
|
+
export async function executeVerificationCommand(contextInput, raw, log = () => { }, signal, options = {}) {
|
|
904
|
+
return executeCheck(validateCommand(validatePipelineContext(contextInput), raw), log, signal, options.deadline, undefined, options.idleTimeoutMs);
|
|
905
|
+
}
|
|
848
906
|
async function executeCheck(command, log, signal, deadline, evidenceId = digest(randomUUID()), idleTimeoutOverrideMs) {
|
|
849
907
|
const started = Date.now();
|
|
850
908
|
const overrides = normalizeVerificationEnvironment(command.env ?? {});
|
|
@@ -1078,7 +1136,7 @@ export function verificationWaves(commands, maxConcurrency = 1) {
|
|
|
1078
1136
|
}
|
|
1079
1137
|
export async function verifyPipeline(contextInput, raw, log = () => { }, signal, options = {}) {
|
|
1080
1138
|
const context = validatePipelineContext(contextInput);
|
|
1081
|
-
const { request
|
|
1139
|
+
const { request } = verificationPlan(context, raw);
|
|
1082
1140
|
let previous;
|
|
1083
1141
|
const state = locked(context, () => {
|
|
1084
1142
|
const current = readState(context);
|
|
@@ -1097,6 +1155,33 @@ export async function verifyPipeline(contextInput, raw, log = () => { }, signal,
|
|
|
1097
1155
|
if (identityProblems.length)
|
|
1098
1156
|
fail(identityProblems.join('; '));
|
|
1099
1157
|
const candidateHash = fingerprintCandidate(state);
|
|
1158
|
+
const isCurrent = () => {
|
|
1159
|
+
const current = readState(context);
|
|
1160
|
+
return fingerprintCandidate(current) === candidateHash && current.revision === state.revision && planReasons(current, request.planHash).length === 0;
|
|
1161
|
+
};
|
|
1162
|
+
return executeVerification(context, raw, {
|
|
1163
|
+
candidateHash, scopeHash: state.scopeHash, planHash: state.verificationPlan?.hash, previous,
|
|
1164
|
+
isCurrent: () => locked(context, isCurrent),
|
|
1165
|
+
persistCheck: (result, planHash, candidate) => persistCheckEvidence(context, result, planHash, candidate),
|
|
1166
|
+
commitReceipt: receipt => locked(context, () => {
|
|
1167
|
+
const current = readState(context);
|
|
1168
|
+
if (!isCurrent())
|
|
1169
|
+
receipt = { ...receipt, valid: false, reason: 'Candidate changed during verification' };
|
|
1170
|
+
atomicJson(safeChild(pipelineStateDirectory(context), 'receipts/' + receipt.id + '.json'), receipt);
|
|
1171
|
+
if (receipt.kind === 'full' || !receipt.valid || !current.verification)
|
|
1172
|
+
current.verification = receipt;
|
|
1173
|
+
else if (previous?.valid && previous.candidateHash === candidateHash)
|
|
1174
|
+
current.verification = previous;
|
|
1175
|
+
saveState(current);
|
|
1176
|
+
return receipt;
|
|
1177
|
+
}),
|
|
1178
|
+
}, log, signal, options);
|
|
1179
|
+
}
|
|
1180
|
+
/** Same deterministic command/evidence runner for legacy journals and ledger-only engine runs. */
|
|
1181
|
+
export async function executeVerification(contextInput, raw, evidence, log = () => { }, signal, options = {}) {
|
|
1182
|
+
const context = validatePipelineContext(contextInput);
|
|
1183
|
+
const { request, commands, unverifiedRepositories } = verificationPlan(context, raw);
|
|
1184
|
+
const { candidateHash, previous } = evidence;
|
|
1100
1185
|
const results = [];
|
|
1101
1186
|
const controller = new AbortController();
|
|
1102
1187
|
const abort = () => controller.abort();
|
|
@@ -1116,7 +1201,7 @@ export async function verifyPipeline(contextInput, raw, log = () => { }, signal,
|
|
|
1116
1201
|
const evidenceId = digest(randomUUID());
|
|
1117
1202
|
if (!reuse) {
|
|
1118
1203
|
const pending = { repositoryId: command.repositoryId, key: command.key, label: command.label, command: command.command, args: command.args, cwd: command.cwd, environmentHash: '', environmentKeys: [], environmentOverrideKeys: [], environmentOverridesHash: '', evidenceId, pending: true, disposition: 'executed', outcome: 'interrupted', exitCode: -1, durationMs: 0, output: '' };
|
|
1119
|
-
|
|
1204
|
+
await evidence.persistCheck(pending, request.planHash ?? digest(canonical(commands)), candidateHash);
|
|
1120
1205
|
await options.onEvidence?.('check-started', { executionId: evidenceId, repositoryId: command.repositoryId, checkId: command.key ?? '', label: command.label ?? command.command });
|
|
1121
1206
|
}
|
|
1122
1207
|
const result = reuse ? { ...old, evidenceId, disposition: 'reused', reusedFrom: old.evidenceId, durationMs: 0, snapshot, reuseReason: 'snapshot-local-identities-match' } : { ...await executeCheck(command, text => log(prefix + text), controller.signal, options.deadline, evidenceId, options.idleTimeoutMs), disposition: 'executed', snapshot, reuseReason: snapshot.eligible ? 'snapshot-local-no-current-match' : snapshot.reason };
|
|
@@ -1131,7 +1216,7 @@ export async function verifyPipeline(contextInput, raw, log = () => { }, signal,
|
|
|
1131
1216
|
}
|
|
1132
1217
|
if (result.exitCode !== 0)
|
|
1133
1218
|
controller.abort();
|
|
1134
|
-
|
|
1219
|
+
await evidence.persistCheck(result, request.planHash ?? digest(canonical(commands)), candidateHash);
|
|
1135
1220
|
await options.onEvidence?.(reuse ? 'check-reused' : 'check-finished', { executionId: evidenceId, repositoryId: command.repositoryId, checkId: command.key ?? '', label: command.label ?? command.command, exitCode: result.exitCode, durationMs: result.durationMs, reason: result.reuseReason ?? null });
|
|
1136
1221
|
return result;
|
|
1137
1222
|
}
|
|
@@ -1160,25 +1245,15 @@ export async function verifyPipeline(contextInput, raw, log = () => { }, signal,
|
|
|
1160
1245
|
for (const command of commands.slice(results.length)) {
|
|
1161
1246
|
const id = digest(randomUUID());
|
|
1162
1247
|
const skipped = { repositoryId: command.repositoryId, key: command.key, label: command.label, command: command.command, args: command.args, cwd: command.cwd, environmentHash: '', environmentKeys: [], environmentOverrideKeys: [], environmentOverridesHash: '', evidenceId: id, disposition: 'not-run', outcome: 'cancelled', exitCode: -1, durationMs: 0, output: 'Not run after cancellation or an earlier failed check' };
|
|
1163
|
-
|
|
1248
|
+
await evidence.persistCheck(skipped, request.planHash ?? digest(canonical(commands)), candidateHash);
|
|
1164
1249
|
notRunEvidenceIds.push(id);
|
|
1165
1250
|
}
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
...(changed ? { reason: 'Candidate changed during verification' } : results.some((result) => result.exitCode !== 0) ? { reason: 'A verification command failed' } : {}),
|
|
1173
|
-
...(unverifiedRepositories.length ? { unverifiedRepositories } : {}),
|
|
1174
|
-
};
|
|
1175
|
-
atomicJson(safeChild(pipelineStateDirectory(context), 'receipts/' + receipt.id + '.json'), receipt);
|
|
1176
|
-
if (receipt.kind === 'full' || !receipt.valid || !current.verification)
|
|
1177
|
-
current.verification = receipt;
|
|
1178
|
-
else if (previous?.valid && previous.candidateHash === candidateHash)
|
|
1179
|
-
current.verification = previous;
|
|
1180
|
-
saveState(current);
|
|
1181
|
-
return receipt;
|
|
1251
|
+
const changed = !evidence.isCurrent();
|
|
1252
|
+
const receipt = await evidence.commitReceipt({
|
|
1253
|
+
id: randomUUID(), ...(evidence.planHash ? { planHash: evidence.planHash } : {}), kind: request.kind, scopeHash: evidence.scopeHash, candidateHash, commands: results, ...(notRunEvidenceIds.length ? { notRunEvidenceIds } : {}),
|
|
1254
|
+
completedAt: new Date().toISOString(), valid: !changed && !signal?.aborted && (options.deadline === undefined || Date.now() <= options.deadline) && results.length === commands.length && results.every((result) => result.exitCode === 0),
|
|
1255
|
+
...(changed ? { reason: 'Candidate changed during verification' } : results.some((result) => result.exitCode !== 0) ? { reason: 'A verification command failed' } : {}),
|
|
1256
|
+
...(unverifiedRepositories.length ? { unverifiedRepositories } : {}),
|
|
1182
1257
|
});
|
|
1183
1258
|
if (!receipt.valid)
|
|
1184
1259
|
for (const result of results) {
|
|
@@ -1406,7 +1481,7 @@ function evidenceSummary(document) {
|
|
|
1406
1481
|
const { stdout: _out, stderr: _err, sources, ...summary } = document;
|
|
1407
1482
|
return { ...summary, sources: sources.map(({ text: _text, ...source }) => source) };
|
|
1408
1483
|
}
|
|
1409
|
-
function persistCheckEvidence(context, result, planHash, candidateHash) {
|
|
1484
|
+
export function persistCheckEvidence(context, result, planHash, candidateHash) {
|
|
1410
1485
|
const id = result.evidenceId;
|
|
1411
1486
|
const planPath = safeChild(pipelineStateDirectory(context), 'verification/plan.json');
|
|
1412
1487
|
const plan = existsSync(planPath) ? object(readJson(planPath)) : undefined;
|