@cleocode/contracts 2026.9.20 → 2026.9.22
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 +1 -1
- package/dist/__tests__/decide.test.d.ts +7 -0
- package/dist/__tests__/decide.test.d.ts.map +1 -0
- package/dist/__tests__/decide.test.js +91 -0
- package/dist/__tests__/decide.test.js.map +1 -0
- package/dist/__tests__/evidence-atom-schema.test.js +19 -1
- package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
- package/dist/__tests__/memory-wire-shapes.test.js.map +1 -1
- package/dist/acceptance-gate-schema.d.ts +5 -0
- package/dist/acceptance-gate-schema.d.ts.map +1 -1
- package/dist/acceptance-gate-schema.js +26 -0
- package/dist/acceptance-gate-schema.js.map +1 -1
- package/dist/acceptance-gate.d.ts +70 -3
- package/dist/acceptance-gate.d.ts.map +1 -1
- package/dist/backup-manifest.d.ts +13 -2
- package/dist/backup-manifest.d.ts.map +1 -1
- package/dist/branch-lock.d.ts +11 -0
- package/dist/branch-lock.d.ts.map +1 -1
- package/dist/branch-lock.js +31 -4
- package/dist/branch-lock.js.map +1 -1
- package/dist/cloud/api.d.ts +607 -0
- package/dist/cloud/api.d.ts.map +1 -0
- package/dist/cloud/api.js +417 -0
- package/dist/cloud/api.js.map +1 -0
- package/dist/cloud/envelope.d.ts +84 -0
- package/dist/cloud/envelope.d.ts.map +1 -0
- package/dist/cloud/envelope.js +54 -0
- package/dist/cloud/envelope.js.map +1 -0
- package/dist/cloud/ids.d.ts +30 -0
- package/dist/cloud/ids.d.ts.map +1 -0
- package/dist/cloud/ids.js +32 -0
- package/dist/cloud/ids.js.map +1 -0
- package/dist/cloud/index.d.ts +17 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +17 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/data-accessor.d.ts +23 -3
- package/dist/data-accessor.d.ts.map +1 -1
- package/dist/data-accessor.js.map +1 -1
- package/dist/decide.d.ts +333 -0
- package/dist/decide.d.ts.map +1 -0
- package/dist/decide.js +184 -0
- package/dist/decide.js.map +1 -0
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +56 -0
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/done-plan.d.ts +330 -0
- package/dist/done-plan.d.ts.map +1 -0
- package/dist/done-plan.js +17 -0
- package/dist/done-plan.js.map +1 -0
- package/dist/evidence-atom-schema.d.ts +29 -44
- package/dist/evidence-atom-schema.d.ts.map +1 -1
- package/dist/evidence-atom-schema.js +48 -8
- package/dist/evidence-atom-schema.js.map +1 -1
- package/dist/exit-codes.d.ts +40 -0
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +41 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/graph.d.ts +40 -0
- package/dist/graph.d.ts.map +1 -1
- package/dist/index.d.ts +13 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/observe.d.ts +16 -0
- package/dist/memory/observe.d.ts.map +1 -1
- package/dist/operations/__tests__/release-shared-surface.test.d.ts +10 -0
- package/dist/operations/__tests__/release-shared-surface.test.d.ts.map +1 -0
- package/dist/operations/__tests__/release-shared-surface.test.js +84 -0
- package/dist/operations/__tests__/release-shared-surface.test.js.map +1 -0
- package/dist/operations/dialectic.d.ts +16 -0
- package/dist/operations/dialectic.d.ts.map +1 -1
- package/dist/operations/docs.d.ts +5 -2
- package/dist/operations/docs.d.ts.map +1 -1
- package/dist/operations/docs.js.map +1 -1
- package/dist/operations/memory.d.ts +48 -0
- package/dist/operations/memory.d.ts.map +1 -1
- package/dist/operations/nexus-scope-map.d.ts +8 -0
- package/dist/operations/nexus-scope-map.d.ts.map +1 -1
- package/dist/operations/nexus-scope-map.js +8 -0
- package/dist/operations/nexus-scope-map.js.map +1 -1
- package/dist/operations/nexus-user-profile.d.ts +27 -1
- package/dist/operations/nexus-user-profile.d.ts.map +1 -1
- package/dist/operations/nexus-user-profile.js +11 -1
- package/dist/operations/nexus-user-profile.js.map +1 -1
- package/dist/operations/nexus.d.ts +394 -3
- package/dist/operations/nexus.d.ts.map +1 -1
- package/dist/operations/release.d.ts +259 -0
- package/dist/operations/release.d.ts.map +1 -1
- package/dist/operations/release.js +130 -1
- package/dist/operations/release.js.map +1 -1
- package/dist/operations/session.d.ts +6 -4
- package/dist/operations/session.d.ts.map +1 -1
- package/dist/operations/tasks.d.ts +6 -0
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js +1 -0
- package/dist/operations/tasks.js.map +1 -1
- package/dist/operations/validate.d.ts +5 -0
- package/dist/operations/validate.d.ts.map +1 -1
- package/dist/operations/worktree.d.ts +125 -1
- package/dist/operations/worktree.d.ts.map +1 -1
- package/dist/process-probe.d.ts +17 -0
- package/dist/process-probe.d.ts.map +1 -0
- package/dist/process-probe.js +21 -0
- package/dist/process-probe.js.map +1 -0
- package/dist/project-context.d.ts +44 -2
- package/dist/project-context.d.ts.map +1 -1
- package/dist/project-lifecycle.d.ts +143 -0
- package/dist/project-lifecycle.d.ts.map +1 -0
- package/dist/project-lifecycle.js +21 -0
- package/dist/project-lifecycle.js.map +1 -0
- package/dist/release/plan.d.ts +6 -2
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +8 -2
- package/dist/release/plan.js.map +1 -1
- package/dist/release/plan.test.js +6 -1
- package/dist/release/plan.test.js.map +1 -1
- package/dist/table-classification.d.ts +208 -0
- package/dist/table-classification.d.ts.map +1 -0
- package/dist/table-classification.js +23 -0
- package/dist/table-classification.js.map +1 -0
- package/dist/task.d.ts +97 -1
- package/dist/task.d.ts.map +1 -1
- package/package.json +6 -2
- package/schemas/gate-result.schema.json +43 -0
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/decide.test.ts +139 -0
- package/src/__tests__/evidence-atom-schema.test.ts +24 -1
- package/src/__tests__/memory-wire-shapes.test.ts +3 -0
- package/src/acceptance-gate-schema.ts +29 -0
- package/src/acceptance-gate.ts +68 -3
- package/src/backup-manifest.ts +13 -2
- package/src/branch-lock.ts +36 -4
- package/src/cloud/api.ts +512 -0
- package/src/cloud/envelope.ts +63 -0
- package/src/cloud/ids.ts +51 -0
- package/src/cloud/index.ts +16 -0
- package/src/data-accessor.ts +24 -3
- package/src/decide.ts +355 -0
- package/src/dispatch/operations-registry.ts +59 -0
- package/src/done-plan.ts +370 -0
- package/src/evidence-atom-schema.ts +51 -9
- package/src/exit-codes.ts +42 -0
- package/src/graph.ts +44 -0
- package/src/index.ts +118 -0
- package/src/memory/observe.ts +16 -0
- package/src/operations/__tests__/release-shared-surface.test.ts +112 -0
- package/src/operations/dialectic.ts +17 -0
- package/src/operations/docs.ts +5 -2
- package/src/operations/memory.ts +48 -0
- package/src/operations/nexus-scope-map.ts +9 -0
- package/src/operations/nexus-user-profile.ts +29 -1
- package/src/operations/nexus.ts +416 -3
- package/src/operations/release.ts +197 -0
- package/src/operations/session.ts +6 -4
- package/src/operations/tasks.ts +7 -0
- package/src/operations/validate.ts +5 -0
- package/src/operations/worktree.ts +133 -1
- package/src/process-probe.ts +21 -0
- package/src/project-context.ts +42 -2
- package/src/project-lifecycle.ts +149 -0
- package/src/release/plan.test.ts +6 -1
- package/src/release/plan.ts +8 -2
- package/src/table-classification.ts +232 -0
- package/src/task.ts +102 -2
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the typed-decision contracts (T12489).
|
|
3
|
+
*
|
|
4
|
+
* @task T12489
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
8
|
+
import type { z } from 'zod';
|
|
9
|
+
import {
|
|
10
|
+
DECISION_REQUEST_LIMITS,
|
|
11
|
+
type DecisionAnswer,
|
|
12
|
+
type DecisionOutcome,
|
|
13
|
+
type DecisionQuestion,
|
|
14
|
+
decisionAnswerSchema,
|
|
15
|
+
decisionOutcomeSchema,
|
|
16
|
+
decisionProviderConfigSchema,
|
|
17
|
+
decisionQuestionSchema,
|
|
18
|
+
decisionRequestSchema,
|
|
19
|
+
} from '../decide.js';
|
|
20
|
+
|
|
21
|
+
const noul = { type: 'noul', criteria: 'The change is safe to merge' } as const;
|
|
22
|
+
|
|
23
|
+
describe('decisionRequestSchema', () => {
|
|
24
|
+
it('accepts a request with each question shape and structured state', () => {
|
|
25
|
+
const result = decisionRequestSchema.safeParse({
|
|
26
|
+
model: 'default',
|
|
27
|
+
state: { diff: ['a', 'b'], files: 2 },
|
|
28
|
+
questions: {
|
|
29
|
+
safe: { ...noul, instructions: { strict: true } },
|
|
30
|
+
area: { type: 'choice', criteria: { api: 'Touches the API', ui: 'Touches the UI' } },
|
|
31
|
+
risk: { type: 'score', criteria: ['low', 'medium', 'high'] },
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
expect(result.success).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('enforces 1-32 questions', () => {
|
|
38
|
+
expect(decisionRequestSchema.safeParse({ state: 's', questions: {} }).success).toBe(false);
|
|
39
|
+
const questions = (n: number) =>
|
|
40
|
+
Object.fromEntries(Array.from({ length: n }, (_, i) => [`q${i}`, noul]));
|
|
41
|
+
expect(
|
|
42
|
+
decisionRequestSchema.safeParse({
|
|
43
|
+
state: 's',
|
|
44
|
+
questions: questions(DECISION_REQUEST_LIMITS.maxQuestions),
|
|
45
|
+
}).success,
|
|
46
|
+
).toBe(true);
|
|
47
|
+
expect(
|
|
48
|
+
decisionRequestSchema.safeParse({
|
|
49
|
+
state: 's',
|
|
50
|
+
questions: questions(DECISION_REQUEST_LIMITS.maxQuestions + 1),
|
|
51
|
+
}).success,
|
|
52
|
+
).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('enforces the state size limit for string and structured state', () => {
|
|
56
|
+
const max = DECISION_REQUEST_LIMITS.maxStateChars;
|
|
57
|
+
const q = { q: noul };
|
|
58
|
+
expect(decisionRequestSchema.safeParse({ state: 'x'.repeat(max), questions: q }).success).toBe(
|
|
59
|
+
true,
|
|
60
|
+
);
|
|
61
|
+
expect(
|
|
62
|
+
decisionRequestSchema.safeParse({ state: 'x'.repeat(max + 1), questions: q }).success,
|
|
63
|
+
).toBe(false);
|
|
64
|
+
expect(
|
|
65
|
+
decisionRequestSchema.safeParse({ state: ['x'.repeat(max)], questions: q }).success,
|
|
66
|
+
).toBe(false);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('decisionQuestionSchema', () => {
|
|
71
|
+
it('requires at least two choice options', () => {
|
|
72
|
+
expect(decisionQuestionSchema.safeParse({ type: 'choice', criteria: { a: 'A' } }).success).toBe(
|
|
73
|
+
false,
|
|
74
|
+
);
|
|
75
|
+
expect(
|
|
76
|
+
decisionQuestionSchema.safeParse({ type: 'choice', criteria: { a: 'A', b: 'B' } }).success,
|
|
77
|
+
).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('requires 2-10 score levels', () => {
|
|
81
|
+
const levels = (n: number) => Array.from({ length: n }, (_, i) => `level ${i}`);
|
|
82
|
+
expect(decisionQuestionSchema.safeParse({ type: 'score', criteria: levels(1) }).success).toBe(
|
|
83
|
+
false,
|
|
84
|
+
);
|
|
85
|
+
expect(decisionQuestionSchema.safeParse({ type: 'score', criteria: levels(2) }).success).toBe(
|
|
86
|
+
true,
|
|
87
|
+
);
|
|
88
|
+
expect(decisionQuestionSchema.safeParse({ type: 'score', criteria: levels(10) }).success).toBe(
|
|
89
|
+
true,
|
|
90
|
+
);
|
|
91
|
+
expect(decisionQuestionSchema.safeParse({ type: 'score', criteria: levels(11) }).success).toBe(
|
|
92
|
+
false,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('answer, outcome and provider config schemas', () => {
|
|
98
|
+
it('rejects probabilities outside [0, 1]', () => {
|
|
99
|
+
expect(
|
|
100
|
+
decisionAnswerSchema.safeParse({
|
|
101
|
+
type: 'noul',
|
|
102
|
+
value: true,
|
|
103
|
+
probability: 1.2,
|
|
104
|
+
confidence: 0.9,
|
|
105
|
+
}).success,
|
|
106
|
+
).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('accepts a cached outcome', () => {
|
|
110
|
+
const outcome: DecisionOutcome = {
|
|
111
|
+
answers: {
|
|
112
|
+
safe: { type: 'noul', value: true, probability: 0.8, confidence: 0.7 },
|
|
113
|
+
area: {
|
|
114
|
+
type: 'choice',
|
|
115
|
+
value: 'api',
|
|
116
|
+
probabilities: { api: 0.9, ui: 0.1 },
|
|
117
|
+
confidence: 0.6,
|
|
118
|
+
},
|
|
119
|
+
risk: { type: 'score', value: 0, probabilities: [0.7, 0.2, 0.1], confidence: 0.5 },
|
|
120
|
+
},
|
|
121
|
+
source: 'cache',
|
|
122
|
+
latencyMs: 3,
|
|
123
|
+
};
|
|
124
|
+
expect(decisionOutcomeSchema.parse(outcome)).toEqual(outcome);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('requires an absolute baseUrl', () => {
|
|
128
|
+
expect(
|
|
129
|
+
decisionProviderConfigSchema.safeParse({ baseUrl: 'https://example.test' }).success,
|
|
130
|
+
).toBe(true);
|
|
131
|
+
expect(decisionProviderConfigSchema.safeParse({ baseUrl: 'not a url' }).success).toBe(false);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('schema outputs are assignable to the domain types', () => {
|
|
135
|
+
expectTypeOf<z.output<typeof decisionQuestionSchema>>().toExtend<DecisionQuestion>();
|
|
136
|
+
expectTypeOf<z.output<typeof decisionAnswerSchema>>().toExtend<DecisionAnswer>();
|
|
137
|
+
expectTypeOf<z.output<typeof decisionOutcomeSchema>>().toExtend<DecisionOutcome>();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -31,7 +31,7 @@ describe('EvidenceAtomSchema (T10337)', () => {
|
|
|
31
31
|
it('EVIDENCE_ATOM_KINDS covers every discriminated union member (T12030)', () => {
|
|
32
32
|
// Verify the shared constant is in sync with the zod discriminated union.
|
|
33
33
|
// The discriminated union keys are 'commit','files','test-run','tool','url',
|
|
34
|
-
// 'note','decision','pr','loc-drop','callsite-coverage','satisfies'.
|
|
34
|
+
// 'note','decision','pr','ci','loc-drop','callsite-coverage','satisfies'.
|
|
35
35
|
const expected = [
|
|
36
36
|
'commit',
|
|
37
37
|
'files',
|
|
@@ -41,6 +41,7 @@ describe('EvidenceAtomSchema (T10337)', () => {
|
|
|
41
41
|
'note',
|
|
42
42
|
'decision',
|
|
43
43
|
'pr',
|
|
44
|
+
'ci',
|
|
44
45
|
'loc-drop',
|
|
45
46
|
'callsite-coverage',
|
|
46
47
|
'satisfies',
|
|
@@ -498,6 +499,28 @@ describe('parseEvidenceString (T10337)', () => {
|
|
|
498
499
|
|
|
499
500
|
// ─── GATE_EVIDENCE_REQUIREMENTS — gate-to-atom mapping ─────────────────────
|
|
500
501
|
|
|
502
|
+
describe('pr:/ci: component references (T12671)', () => {
|
|
503
|
+
it('pr:<component>@<integration> names the integration PR and its component', () => {
|
|
504
|
+
expect(parseEvidenceString('pr:42@41')).toEqual([
|
|
505
|
+
{ kind: 'pr', prNumber: 41, componentPrNumber: 42 },
|
|
506
|
+
]);
|
|
507
|
+
expect(parseEvidenceString('ci:42@41')).toEqual([
|
|
508
|
+
{ kind: 'ci', prNumber: 41, componentPrNumber: 42 },
|
|
509
|
+
]);
|
|
510
|
+
expect(parseEvidenceString('pr:41')).toEqual([{ kind: 'pr', prNumber: 41 }]);
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it.each(['pr:42@42', 'pr:42@', 'pr:@41', 'ci:0@41', 'pr:42@4x'])('rejects %s', (raw) => {
|
|
514
|
+
expect(() => parseEvidenceString(raw)).toThrow(EvidenceParseError);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('the schema accepts the component field', () => {
|
|
518
|
+
expect(
|
|
519
|
+
EvidenceAtomSchema.safeParse({ kind: 'ci', prNumber: 41, componentPrNumber: 42 }).success,
|
|
520
|
+
).toBe(true);
|
|
521
|
+
});
|
|
522
|
+
});
|
|
523
|
+
|
|
501
524
|
describe('GATE_EVIDENCE_REQUIREMENTS (T10337)', () => {
|
|
502
525
|
it('covers every VerificationGate', () => {
|
|
503
526
|
const expected: VerificationGate[] = [
|
|
@@ -205,6 +205,7 @@ type _ObserveBrainParamsShape = {
|
|
|
205
205
|
attachmentRefs?: string[];
|
|
206
206
|
origin?: string | null;
|
|
207
207
|
provenanceChain?: string[] | null;
|
|
208
|
+
askTypeDecision?: boolean;
|
|
208
209
|
_skipGate?: boolean;
|
|
209
210
|
_skipQueue?: boolean;
|
|
210
211
|
};
|
|
@@ -219,6 +220,8 @@ type _ObserveBrainResultShape = {
|
|
|
219
220
|
id: string;
|
|
220
221
|
type: string;
|
|
221
222
|
createdAt: string;
|
|
223
|
+
typeSource?: 'caller' | 'keyword' | 'system-one';
|
|
224
|
+
typeConfidence?: number;
|
|
222
225
|
};
|
|
223
226
|
|
|
224
227
|
type _AssertObserveResultPinned = AssertEquals1<
|
|
@@ -443,6 +443,25 @@ const acceptanceGateBindingSchema: z.ZodType<AcceptanceGateBinding> = z
|
|
|
443
443
|
});
|
|
444
444
|
}),
|
|
445
445
|
),
|
|
446
|
+
tree: z
|
|
447
|
+
.object({
|
|
448
|
+
headSha: z.string().regex(/^[0-9a-f]{40}$/),
|
|
449
|
+
clean: z.boolean(),
|
|
450
|
+
cwd: z
|
|
451
|
+
.string()
|
|
452
|
+
.min(1)
|
|
453
|
+
.refine(
|
|
454
|
+
(v) => !/^(?:\/|[A-Za-z]:[\\/]|\\\\)/.test(v) && !v.split(/[\\/]/).includes('..'),
|
|
455
|
+
'Tree-relative cwd must be relative and stay inside the tree',
|
|
456
|
+
),
|
|
457
|
+
inputsHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
458
|
+
baseSha: z
|
|
459
|
+
.string()
|
|
460
|
+
.regex(/^[0-9a-f]{40}$/)
|
|
461
|
+
.optional(),
|
|
462
|
+
})
|
|
463
|
+
.strict()
|
|
464
|
+
.optional(),
|
|
446
465
|
})
|
|
447
466
|
.strict()
|
|
448
467
|
.superRefine((binding, context) => {
|
|
@@ -490,8 +509,18 @@ export const acceptanceGateResultSchema = z
|
|
|
490
509
|
/** ISO 8601 timestamp. */
|
|
491
510
|
checkedAt: z.string().datetime(),
|
|
492
511
|
checkedBy: z.string().min(1),
|
|
512
|
+
/** T12621: `executed` in this run, or an authenticated pass reused from the cache. */
|
|
513
|
+
source: z.enum(['executed', 'cache']).optional(),
|
|
514
|
+
/** T12621: creation time of the reused cache entry. */
|
|
515
|
+
cachedAt: z.string().datetime().optional(),
|
|
493
516
|
})
|
|
494
517
|
.superRefine((result, context) => {
|
|
518
|
+
if ((result.source === 'cache') !== (result.cachedAt !== undefined))
|
|
519
|
+
context.addIssue({
|
|
520
|
+
code: 'custom',
|
|
521
|
+
path: ['cachedAt'],
|
|
522
|
+
message: 'A cached result carries its cache time, and only a cached result does',
|
|
523
|
+
});
|
|
495
524
|
const binding = result.binding;
|
|
496
525
|
if (binding) {
|
|
497
526
|
if (binding.identity.actor !== result.checkedBy)
|
package/src/acceptance-gate.ts
CHANGED
|
@@ -28,8 +28,8 @@ import type {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Captured options for evaluating acceptance gates without renewing operation authority.
|
|
31
|
-
* @remarks An absent execution context
|
|
32
|
-
*
|
|
31
|
+
* @remarks An absent execution context is admitted for every gate's tool deadline plus
|
|
32
|
+
* the two-second bookkeeping budget (T12516); per-gate timeouts bound each gate.
|
|
33
33
|
* @example
|
|
34
34
|
* ```typescript
|
|
35
35
|
* const options: AcceptanceGateRunOptions = { projectRoot: '/project', execution };
|
|
@@ -49,6 +49,23 @@ export interface AcceptanceGateRunOptions
|
|
|
49
49
|
maxOutputBytes?: number;
|
|
50
50
|
/** Existing manager context, separate from the child environment. */
|
|
51
51
|
systemdControl?: SystemdControlContext;
|
|
52
|
+
/**
|
|
53
|
+
* Typed-gate result cache policy (T12621). Keyed by gate definition hash, git
|
|
54
|
+
* HEAD, dirty-tree fingerprint and cwd; only passes are stored.
|
|
55
|
+
* - `off`: never read or write the cache.
|
|
56
|
+
* - `refresh`: always execute, then store a pass (`cleo verify --run`).
|
|
57
|
+
* - `use`: serve a fresh cached pass, otherwise execute and store a pass.
|
|
58
|
+
* - `only`: serve a fresh cached pass; a cacheable gate without one is an
|
|
59
|
+
* `error` result and is never executed (`cleo verify --no-run`).
|
|
60
|
+
*
|
|
61
|
+
* @defaultValue 'off'
|
|
62
|
+
*/
|
|
63
|
+
cache?: 'off' | 'refresh' | 'use' | 'only';
|
|
64
|
+
/**
|
|
65
|
+
* Digest of the single gate's captured invocation and input artifacts; part of
|
|
66
|
+
* its cache key. Required, with a one-gate batch, whenever `cache` is not `off`.
|
|
67
|
+
*/
|
|
68
|
+
cacheInputsHash?: string;
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
// ─── Base ────────────────────────────────────────────────────────────────────
|
|
@@ -80,7 +97,10 @@ export interface GateBase {
|
|
|
80
97
|
/**
|
|
81
98
|
* Gate timeout in milliseconds.
|
|
82
99
|
*
|
|
83
|
-
* @defaultValue
|
|
100
|
+
* @defaultValue The ADR-061 tool deadline for the gate kind: `CLEO_GATE_TIMEOUT_MS`,
|
|
101
|
+
* else `CLEO_TOOL_TIMEOUT_<KIND>`, else 1_800_000 for `test` and 300_000 otherwise
|
|
102
|
+
* (T12516). It bounds the gate's own execution; the two-second shared budget
|
|
103
|
+
* covers only the surrounding bookkeeping, never a gate (T12621).
|
|
84
104
|
*/
|
|
85
105
|
timeoutMs?: number;
|
|
86
106
|
}
|
|
@@ -434,6 +454,37 @@ export interface AcceptanceGateBinding {
|
|
|
434
454
|
invocation?: AcceptanceGateInvocation;
|
|
435
455
|
/** Explicit bounded input inventory, including untracked harness/task files. */
|
|
436
456
|
artifacts: AcceptanceGateArtifact[];
|
|
457
|
+
/**
|
|
458
|
+
* Content binding of the tree the gate ran in (T12625). Lets a result
|
|
459
|
+
* verified in a worker's worktree be revalidated from another checkout of
|
|
460
|
+
* the same project — the orchestrator completing from main after merge —
|
|
461
|
+
* without re-entering the (often auto-cleaned) worktree. Absent on results
|
|
462
|
+
* recorded before T12625, which keep exact path-bound revalidation.
|
|
463
|
+
*/
|
|
464
|
+
tree?: AcceptanceGateTreeBinding;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Tree-independent binding of a typed gate's inputs (T12625).
|
|
469
|
+
* @remarks Revalidation from another tree of the same project accepts only when
|
|
470
|
+
* the verified tree was clean, `headSha` is an ancestor of the completing HEAD,
|
|
471
|
+
* and `inputsHash` recomputes identically there.
|
|
472
|
+
*/
|
|
473
|
+
export interface AcceptanceGateTreeBinding {
|
|
474
|
+
/** HEAD commit of the tree the gate ran in. */
|
|
475
|
+
headSha: string;
|
|
476
|
+
/** True when that tree had no uncommitted tracked changes. */
|
|
477
|
+
clean: boolean;
|
|
478
|
+
/** Gate working directory, relative to the tree root (`.` for the root). */
|
|
479
|
+
cwd: string;
|
|
480
|
+
/** SHA-256 over command, args, relative cwd, environment hash and repo-relative input bytes. */
|
|
481
|
+
inputsHash: string;
|
|
482
|
+
/**
|
|
483
|
+
* Fork point of the verified commit from origin's default branch at
|
|
484
|
+
* verification time. `baseSha..headSha` is the change the result attests;
|
|
485
|
+
* every path it touched must be unchanged in the completing tree.
|
|
486
|
+
*/
|
|
487
|
+
baseSha?: string;
|
|
437
488
|
}
|
|
438
489
|
|
|
439
490
|
/**
|
|
@@ -504,6 +555,14 @@ export interface AcceptanceGateResult {
|
|
|
504
555
|
checkedAt: string;
|
|
505
556
|
/** Agent identifier or `"human"` that ran or attested the gate. */
|
|
506
557
|
checkedBy: string;
|
|
558
|
+
/**
|
|
559
|
+
* Where the verdict came from (T12621): `executed` in this run, or `cache` —
|
|
560
|
+
* an authenticated pass reused from the ADR-061 evidence cache. Absent on
|
|
561
|
+
* results recorded before T12621.
|
|
562
|
+
*/
|
|
563
|
+
source?: 'executed' | 'cache';
|
|
564
|
+
/** When the reused cache entry was created (the original execution); set iff `source` is `cache`. */
|
|
565
|
+
cachedAt?: string;
|
|
507
566
|
}
|
|
508
567
|
|
|
509
568
|
/** Canonical typed-verification audit details stored with results in the task transaction. */
|
|
@@ -516,4 +575,10 @@ export interface AcceptanceGateVerificationReceipt {
|
|
|
516
575
|
operation: 'check.gate.verify';
|
|
517
576
|
/** Overall generic-plus-typed verification outcome at this observation. */
|
|
518
577
|
passed: boolean;
|
|
578
|
+
/**
|
|
579
|
+
* Results reused from the evidence cache instead of executed (T12621), by
|
|
580
|
+
* acceptance index, with each cache entry's creation time. Omitted when every
|
|
581
|
+
* result was executed, so receipts recorded before T12621 stay byte-identical.
|
|
582
|
+
*/
|
|
583
|
+
cached?: Array<{ index: number; cachedAt: string }>;
|
|
519
584
|
}
|
package/src/backup-manifest.ts
CHANGED
|
@@ -254,8 +254,19 @@ export interface BackupObservationInspection {
|
|
|
254
254
|
atimeNs: string;
|
|
255
255
|
/** Observed access time at the final identity/content-metadata recheck. */
|
|
256
256
|
atimeAfterNs: string;
|
|
257
|
-
/**
|
|
258
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Whether access time changed; this does not identify the responsible reader.
|
|
259
|
+
* `null` when {@link atimeIsolation} is `'unavailable'`: the inspection's own
|
|
260
|
+
* read may have advanced atime, so a change is not evidence of another reader.
|
|
261
|
+
*/
|
|
262
|
+
atimeChanged: boolean | null;
|
|
263
|
+
/**
|
|
264
|
+
* Whether the inspection's own read was kept from updating access time.
|
|
265
|
+
* `'noatime'`: the source was opened with O_NOATIME (Linux).
|
|
266
|
+
* `'unavailable'`: the platform has no O_NOATIME (macOS), so atime may
|
|
267
|
+
* reflect this inspection.
|
|
268
|
+
*/
|
|
269
|
+
atimeIsolation: 'noatime' | 'unavailable';
|
|
259
270
|
};
|
|
260
271
|
/** Actual SQLite schema marker; not an inferred product/schema version. */
|
|
261
272
|
userVersion: number;
|
package/src/branch-lock.ts
CHANGED
|
@@ -320,6 +320,13 @@ export const BRANCH_LOCK_ERROR_CODES = {
|
|
|
320
320
|
* reset the branch automatically.
|
|
321
321
|
*/
|
|
322
322
|
E_DIRTY_BRANCH: 'E_DIRTY_BRANCH',
|
|
323
|
+
/**
|
|
324
|
+
* T12506: the per-task worktree lock is held by a live holder (pid alive with
|
|
325
|
+
* the recorded start time, heartbeat within the TTL). A second spawn would
|
|
326
|
+
* otherwise force-remove the holder's live worktree. Error details name the
|
|
327
|
+
* holder. Exit code `ExitCode.WORKTREE_LOCKED` (25).
|
|
328
|
+
*/
|
|
329
|
+
E_WORKTREE_LOCKED: 'E_WORKTREE_LOCKED',
|
|
323
330
|
} as const;
|
|
324
331
|
|
|
325
332
|
/** Union of all branch-lock error code strings. */
|
|
@@ -501,6 +508,10 @@ export interface IsolationResult {
|
|
|
501
508
|
* against symlink traversal and shell quirks).
|
|
502
509
|
* 3. Exports the isolation env vars so they are visible to child processes.
|
|
503
510
|
*
|
|
511
|
+
* Every path and value is POSIX single-quoted, so the snippet stays a valid
|
|
512
|
+
* shell program when the worktree path contains spaces (the macOS default
|
|
513
|
+
* `~/Library/Application Support/...`), `$`, backticks or quotes (T12520).
|
|
514
|
+
*
|
|
504
515
|
* The trailing newline is included so callers can concatenate directly.
|
|
505
516
|
*/
|
|
506
517
|
preamble: string;
|
|
@@ -512,6 +523,24 @@ export interface IsolationResult {
|
|
|
512
523
|
boundaryContract: BoundaryContract;
|
|
513
524
|
}
|
|
514
525
|
|
|
526
|
+
/**
|
|
527
|
+
* POSIX single-quote one shell word: wrap it in `'...'` and rewrite each
|
|
528
|
+
* embedded `'` as `'\''`. The result is one literal word for any input — no
|
|
529
|
+
* parameter, command or glob expansion — which is what a copy-paste snippet
|
|
530
|
+
* naming a path under `Application Support` needs (T12520).
|
|
531
|
+
*
|
|
532
|
+
* Module-private on purpose: contracts is types-only for its exported surface
|
|
533
|
+
* (gate 10), and {@link provisionIsolatedShell} is the one place that renders
|
|
534
|
+
* isolation paths into shell text, so callers consume the quoted preamble
|
|
535
|
+
* rather than re-quoting.
|
|
536
|
+
*
|
|
537
|
+
* @param value - Raw path or value.
|
|
538
|
+
* @returns The single-quoted shell word.
|
|
539
|
+
*/
|
|
540
|
+
function shellQuote(value: string): string {
|
|
541
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
542
|
+
}
|
|
543
|
+
|
|
515
544
|
/**
|
|
516
545
|
* Provision an isolated shell context for an agent worktree.
|
|
517
546
|
*
|
|
@@ -561,19 +590,22 @@ export function provisionIsolatedShell(options: IsolationOptions): IsolationResu
|
|
|
561
590
|
// does not emit a clobbering `export KEY=""` that would clear an inherited
|
|
562
591
|
// value. Worktree-binding keys are always non-empty so they always export.
|
|
563
592
|
const exportBlock = ISOLATION_ENV_KEYS.filter((k) => env[k] !== '')
|
|
564
|
-
.map((k) => `export ${k}
|
|
593
|
+
.map((k) => `export ${k}=${shellQuote(env[k])}`)
|
|
565
594
|
.join('\n');
|
|
566
595
|
|
|
596
|
+
// T12520 — every path is emitted through `shellQuote`, never interpolated
|
|
597
|
+
// bare or inside double quotes, so the snippet parses for any worktree path.
|
|
598
|
+
const quotedPath = shellQuote(worktreePath);
|
|
567
599
|
const preamble = [
|
|
568
600
|
'## Worktree Isolation (REQUIRED — do not skip)',
|
|
569
601
|
'',
|
|
570
602
|
'# Step 1: Enter the worktree (exits immediately if path is missing)',
|
|
571
|
-
`cd
|
|
603
|
+
`cd ${quotedPath} || exit 1`,
|
|
572
604
|
'',
|
|
573
605
|
'# Step 2: Verify working directory (guards against shell/symlink quirks)',
|
|
574
606
|
'case "$PWD" in',
|
|
575
|
-
`
|
|
576
|
-
|
|
607
|
+
` ${quotedPath}*) ;;`,
|
|
608
|
+
` *) echo "ISOLATION ERROR: pwd=$PWD expected prefix="${quotedPath} >&2; exit 1 ;;`,
|
|
577
609
|
'esac',
|
|
578
610
|
'',
|
|
579
611
|
'# Step 3: Export isolation env vars',
|