@cleocode/contracts 2026.10.8 → 2026.10.9
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/dist/branch-lock.d.ts +5 -2
- package/dist/branch-lock.d.ts.map +1 -1
- package/dist/branch-lock.js.map +1 -1
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +7 -1
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/invariants/sync-write-invariants.d.ts.map +1 -1
- package/dist/invariants/sync-write-invariants.js +10 -1
- package/dist/invariants/sync-write-invariants.js.map +1 -1
- package/dist/nexus-cloud.d.ts +19 -1
- package/dist/nexus-cloud.d.ts.map +1 -1
- package/dist/nexus-cloud.js +4 -0
- package/dist/nexus-cloud.js.map +1 -1
- package/dist/nexus-vault.d.ts +155 -1
- package/dist/nexus-vault.d.ts.map +1 -1
- package/dist/operations/nexus.d.ts +4 -0
- package/dist/operations/nexus.d.ts.map +1 -1
- package/dist/operations/nexus.js.map +1 -1
- package/dist/operations/orchestrate.d.ts +7 -0
- package/dist/operations/orchestrate.d.ts.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 +61 -0
- package/dist/operations/worktree.d.ts.map +1 -1
- package/dist/project-context.d.ts +9 -0
- package/dist/project-context.d.ts.map +1 -1
- package/dist/project-hook-delivery.d.ts +137 -0
- package/dist/project-hook-delivery.d.ts.map +1 -0
- package/dist/project-hook-delivery.js +21 -0
- package/dist/project-hook-delivery.js.map +1 -0
- package/dist/project-hooks.d.ts +276 -0
- package/dist/project-hooks.d.ts.map +1 -0
- package/dist/project-hooks.js +113 -0
- package/dist/project-hooks.js.map +1 -0
- package/dist/release/channel.d.ts +1 -1
- package/dist/release/channel.d.ts.map +1 -1
- package/dist/release/hook-pilot.d.ts +179 -0
- package/dist/release/hook-pilot.d.ts.map +1 -0
- package/dist/release/hook-pilot.js +112 -0
- package/dist/release/hook-pilot.js.map +1 -0
- package/dist/release/plan.d.ts +12 -1
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +25 -2
- package/dist/release/plan.js.map +1 -1
- package/dist/release/plan.test.js +14 -3
- package/dist/release/plan.test.js.map +1 -1
- package/dist/row-identity.d.ts +7 -0
- package/dist/row-identity.d.ts.map +1 -1
- package/dist/task.d.ts +7 -0
- package/dist/task.d.ts.map +1 -1
- package/dist/tools/atomic.d.ts +6 -0
- package/dist/tools/atomic.d.ts.map +1 -1
- package/dist/tools/atomic.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/branch-lock.ts +5 -2
- package/src/dispatch/operations-registry.ts +7 -1
- package/src/index.ts +7 -0
- package/src/invariants/sync-write-invariants.ts +10 -1
- package/src/nexus-cloud.ts +13 -1
- package/src/nexus-vault.ts +138 -1
- package/src/operations/nexus.ts +4 -0
- package/src/operations/orchestrate.ts +7 -0
- package/src/operations/validate.ts +5 -0
- package/src/operations/worktree.ts +59 -0
- package/src/project-context.ts +9 -0
- package/src/project-hook-delivery.ts +144 -0
- package/src/project-hooks.ts +256 -0
- package/src/release/channel.ts +1 -1
- package/src/release/hook-pilot.ts +163 -0
- package/src/release/plan.test.ts +15 -3
- package/src/release/plan.ts +81 -56
- package/src/row-identity.ts +7 -0
- package/src/task.ts +7 -0
- package/src/tools/atomic.ts +8 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/** Project-owned checks shared by Git, harness and worktree adapters (T13343). */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { HookJsonValue } from './heavy-command-hook.js';
|
|
4
|
+
|
|
5
|
+
/** Invocation origin; CI has stricter failure handling than local adapters. */
|
|
6
|
+
export const HookSourceSchema = z.enum(['agent', 'git', 'worktree', 'direct', 'ci']);
|
|
7
|
+
/** Shared invocation origin. */
|
|
8
|
+
export type HookSource = z.infer<typeof HookSourceSchema>;
|
|
9
|
+
/** A checkout-relative path; runtime validation also resolves symlinks. */
|
|
10
|
+
export const ProjectHookPathSchema = z
|
|
11
|
+
.string()
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(4096)
|
|
14
|
+
.refine(
|
|
15
|
+
(value) =>
|
|
16
|
+
!value.startsWith('/') &&
|
|
17
|
+
!value.includes('\\') &&
|
|
18
|
+
!value.split('/').includes('..') &&
|
|
19
|
+
!/^[A-Za-z]:/.test(value),
|
|
20
|
+
'Expected a project-relative path without traversal',
|
|
21
|
+
);
|
|
22
|
+
/** One project definition; CLEO IDs are reserved for shipped infrastructure. */
|
|
23
|
+
export const ProjectHookDefinitionSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
id: z
|
|
26
|
+
.string()
|
|
27
|
+
.regex(/^[a-z][a-z0-9.-]{0,127}$/)
|
|
28
|
+
.refine((value) => !value.startsWith('cleo.'), 'CLEO IDs are reserved'),
|
|
29
|
+
owner: z.literal('project'),
|
|
30
|
+
bindings: z
|
|
31
|
+
.array(z.object({ source: HookSourceSchema, event: z.string().min(1).max(128) }).strict())
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(16),
|
|
34
|
+
executable: z.string().min(1).max(4096),
|
|
35
|
+
handler: ProjectHookPathSchema,
|
|
36
|
+
args: z.array(z.string().max(8192)).max(64).default([]),
|
|
37
|
+
dependencies: z.array(ProjectHookPathSchema).max(128).default([]),
|
|
38
|
+
timeoutMs: z.number().int().min(1).max(120000).default(30000),
|
|
39
|
+
checkerErrorPolicy: z.enum(['warn', 'block']).default('warn'),
|
|
40
|
+
})
|
|
41
|
+
.strict();
|
|
42
|
+
/** Validated project checker configuration. Handler is executable's first argument. */
|
|
43
|
+
export type ProjectHookDefinition = z.infer<typeof ProjectHookDefinitionSchema>;
|
|
44
|
+
/** Tracked single source of truth; array order defines execution order. */
|
|
45
|
+
export const ProjectHooksManifestSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
schemaVersion: z.literal(1),
|
|
48
|
+
hooks: z.array(ProjectHookDefinitionSchema).max(64),
|
|
49
|
+
})
|
|
50
|
+
.strict()
|
|
51
|
+
.superRefine((manifest, context) => {
|
|
52
|
+
const ids = new Set<string>();
|
|
53
|
+
for (const hook of manifest.hooks) {
|
|
54
|
+
if (ids.has(hook.id))
|
|
55
|
+
context.addIssue({ code: 'custom', message: `Duplicate hook ID: ${hook.id}` });
|
|
56
|
+
ids.add(hook.id);
|
|
57
|
+
const bindings = new Set<string>();
|
|
58
|
+
for (const binding of hook.bindings) {
|
|
59
|
+
const key = `${binding.source}:${binding.event}`;
|
|
60
|
+
if (bindings.has(key))
|
|
61
|
+
context.addIssue({ code: 'custom', message: `Duplicate binding: ${hook.id}:${key}` });
|
|
62
|
+
bindings.add(key);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
/** Validated tracked manifest. */
|
|
67
|
+
export type ProjectHooksManifest = z.infer<typeof ProjectHooksManifestSchema>;
|
|
68
|
+
/** Exact ref update supplied by Git pre-push, including zero object IDs. */
|
|
69
|
+
export interface HookRefUpdate {
|
|
70
|
+
/** Local ref name or deletion marker. */
|
|
71
|
+
localRef: string;
|
|
72
|
+
/** Exact local object ID. */
|
|
73
|
+
localOid: string;
|
|
74
|
+
/** Remote ref name. */
|
|
75
|
+
remoteRef: string;
|
|
76
|
+
/** Previous remote object ID. */
|
|
77
|
+
remoteOid: string;
|
|
78
|
+
}
|
|
79
|
+
/** Native input normalized by the adapter; no harness environment is required. */
|
|
80
|
+
export interface HookInvocation {
|
|
81
|
+
/** Protocol version. */
|
|
82
|
+
schemaVersion: 1;
|
|
83
|
+
/** Active checkout root, independently resolved through Git. */
|
|
84
|
+
projectRoot: string;
|
|
85
|
+
/** Git common directory, for attribution across linked checkouts. */
|
|
86
|
+
gitCommonDir: string;
|
|
87
|
+
/** Triggering adapter. */
|
|
88
|
+
source: HookSource;
|
|
89
|
+
/** Canonical event name. */
|
|
90
|
+
event: string;
|
|
91
|
+
/** Optional remote name and URL/path as Git supplies them. */
|
|
92
|
+
remote?: { name: string; location: string };
|
|
93
|
+
/** All pushed ref updates in original order. */
|
|
94
|
+
refs?: HookRefUpdate[];
|
|
95
|
+
/** Agent tool input, passed only to the checker and never persisted. */
|
|
96
|
+
toolInput?: HookJsonValue;
|
|
97
|
+
/** Optional worktree lifecycle identity. */
|
|
98
|
+
worktree?: { taskId: string; path: string };
|
|
99
|
+
}
|
|
100
|
+
/** Structured checker verdict written as one JSON object on stdout. */
|
|
101
|
+
export const ProjectHookVerdictSchema = z
|
|
102
|
+
.object({
|
|
103
|
+
status: z.enum(['pass', 'block', 'warn', 'skip', 'checker-error']),
|
|
104
|
+
message: z.string().max(4096).optional(),
|
|
105
|
+
})
|
|
106
|
+
.strict();
|
|
107
|
+
/** Project checker's portable stdout contract. */
|
|
108
|
+
export type ProjectHookVerdict = z.infer<typeof ProjectHookVerdictSchema>;
|
|
109
|
+
/** Distinct executor result classes. */
|
|
110
|
+
export type HookOutcomeStatus = ProjectHookVerdict['status'] | 'timeout' | 'infrastructure-error';
|
|
111
|
+
/** Sanitized result: arbitrary child output and inputs are deliberately absent. */
|
|
112
|
+
export interface HookOutcome {
|
|
113
|
+
/** Project check ID, or CLEO infrastructure attribution. */
|
|
114
|
+
id: string;
|
|
115
|
+
/** Observed classification. */
|
|
116
|
+
status: HookOutcomeStatus;
|
|
117
|
+
/** Whether this adapter must prevent the operation. */
|
|
118
|
+
blocks: boolean;
|
|
119
|
+
/** Safe executor diagnostic code; child messages are not retained. */
|
|
120
|
+
code: string;
|
|
121
|
+
/** Project diagnostic for the current caller only; excluded from stored receipts. */
|
|
122
|
+
message?: string;
|
|
123
|
+
/** Child exit code; null when no normal exit occurred. */
|
|
124
|
+
exitCode: number | null;
|
|
125
|
+
/** Terminating signal, when present. */
|
|
126
|
+
signal: string | null;
|
|
127
|
+
/** Elapsed execution time in milliseconds. */
|
|
128
|
+
durationMs: number;
|
|
129
|
+
}
|
|
130
|
+
/** Dependency-injection port; worktree consumes this without importing core. */
|
|
131
|
+
export interface HookExecutor {
|
|
132
|
+
/** Execute all matching activated definitions in manifest order. */
|
|
133
|
+
execute(invocation: HookInvocation): Promise<HookOutcome[]>;
|
|
134
|
+
}
|
|
135
|
+
/** Content hashes approved explicitly on this machine. */
|
|
136
|
+
export const HookActivationSchema = z
|
|
137
|
+
.object({
|
|
138
|
+
schemaVersion: z.literal(1),
|
|
139
|
+
projectRoot: z.string(),
|
|
140
|
+
projectIdentity: z.string(),
|
|
141
|
+
manifestHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
142
|
+
files: z.record(z.string(), z.string().regex(/^[a-f0-9]{64}$/)),
|
|
143
|
+
executables: z.record(z.string(), z.string()),
|
|
144
|
+
activatedAt: z.string(),
|
|
145
|
+
})
|
|
146
|
+
.strict();
|
|
147
|
+
/** Activation record is local, excluded from Git and never replicated. */
|
|
148
|
+
export type HookActivation = z.infer<typeof HookActivationSchema>;
|
|
149
|
+
/** Local feature state; activation never confers native harness trust. */
|
|
150
|
+
export const ProjectHooksLocalStateSchema = z
|
|
151
|
+
.object({
|
|
152
|
+
hooks: z.object({ project: z.object({ enabled: z.boolean() }).strict() }).strict(),
|
|
153
|
+
activation: HookActivationSchema.optional(),
|
|
154
|
+
})
|
|
155
|
+
.strict();
|
|
156
|
+
/** Local activation settings stored independently of tracked project configuration. */
|
|
157
|
+
export type ProjectHooksLocalState = z.infer<typeof ProjectHooksLocalStateSchema>;
|
|
158
|
+
/** Bounded last execution metadata; contains no input or arbitrary output. */
|
|
159
|
+
export interface HookExecutionReceipt {
|
|
160
|
+
/** Receipt format version. */
|
|
161
|
+
schemaVersion: 1;
|
|
162
|
+
/** Activated source digest. */
|
|
163
|
+
manifestHash: string;
|
|
164
|
+
/** Invocation origin. */
|
|
165
|
+
source: HookSource;
|
|
166
|
+
/** Event without payload. */
|
|
167
|
+
event: string;
|
|
168
|
+
/** Completion timestamp. */
|
|
169
|
+
recordedAt: string;
|
|
170
|
+
/** Sanitized outcomes. */
|
|
171
|
+
outcomes: HookOutcome[];
|
|
172
|
+
}
|
|
173
|
+
/** Validator for sanitized execution records, rejecting unexpected payload fields. */
|
|
174
|
+
export const HookExecutionReceiptSchema = z
|
|
175
|
+
.object({
|
|
176
|
+
schemaVersion: z.literal(1),
|
|
177
|
+
manifestHash: z.string().regex(/^[a-f0-9]{64}$/),
|
|
178
|
+
source: HookSourceSchema,
|
|
179
|
+
event: z.string().max(128),
|
|
180
|
+
recordedAt: z.string(),
|
|
181
|
+
outcomes: z
|
|
182
|
+
.array(
|
|
183
|
+
z
|
|
184
|
+
.object({
|
|
185
|
+
id: z.string().max(128),
|
|
186
|
+
status: z.enum([
|
|
187
|
+
'pass',
|
|
188
|
+
'block',
|
|
189
|
+
'warn',
|
|
190
|
+
'skip',
|
|
191
|
+
'checker-error',
|
|
192
|
+
'timeout',
|
|
193
|
+
'infrastructure-error',
|
|
194
|
+
]),
|
|
195
|
+
blocks: z.boolean(),
|
|
196
|
+
code: z.string().regex(/^HOOK_[A-Z_]+$/),
|
|
197
|
+
exitCode: z.number().int().nullable(),
|
|
198
|
+
signal: z.string().max(32).nullable(),
|
|
199
|
+
durationMs: z.number().nonnegative(),
|
|
200
|
+
})
|
|
201
|
+
.strict(),
|
|
202
|
+
)
|
|
203
|
+
.max(65),
|
|
204
|
+
})
|
|
205
|
+
.strict();
|
|
206
|
+
/** Checkout identity and machine-local receipt paths resolved by Git. */
|
|
207
|
+
export interface ProjectHookContext {
|
|
208
|
+
/** Active checkout root. */
|
|
209
|
+
projectRoot: string;
|
|
210
|
+
/** Common Git directory. */
|
|
211
|
+
gitCommonDir: string;
|
|
212
|
+
/** Effective Git hook directory. */
|
|
213
|
+
hooksDir: string;
|
|
214
|
+
/** Per-checkout Git-private CLEO state directory. */
|
|
215
|
+
stateDir: string;
|
|
216
|
+
}
|
|
217
|
+
/** Resolved handler attribution without executing project code. */
|
|
218
|
+
export interface ProjectHookResolution {
|
|
219
|
+
/** Project-owned definition identity. */
|
|
220
|
+
id: string;
|
|
221
|
+
/** Declared policy owner, separate from installer provenance. */
|
|
222
|
+
owner: 'project';
|
|
223
|
+
/** Manifest source path. */
|
|
224
|
+
sourceDefinition: string;
|
|
225
|
+
/** Whether Git currently tracks the manifest. */
|
|
226
|
+
sourceTracked: boolean;
|
|
227
|
+
/** Canonical handler path when it can be resolved safely. */
|
|
228
|
+
handlerPath?: string;
|
|
229
|
+
/** Canonical executable path when available. */
|
|
230
|
+
executablePath?: string;
|
|
231
|
+
/** Bounded diagnostic codes; contains no environment values. */
|
|
232
|
+
diagnostics: string[];
|
|
233
|
+
}
|
|
234
|
+
/** Read-only activation and ownership diagnostic. */
|
|
235
|
+
export interface ProjectHookInspection {
|
|
236
|
+
/** Git-resolved paths. */
|
|
237
|
+
context: ProjectHookContext;
|
|
238
|
+
/** Validated source definitions. */
|
|
239
|
+
manifest: ProjectHooksManifest;
|
|
240
|
+
/** Definition attribution and resolved executable paths. */
|
|
241
|
+
resolvedHooks: ProjectHookResolution[];
|
|
242
|
+
/** Machine-local opt-in flag. */
|
|
243
|
+
enabled: boolean;
|
|
244
|
+
/** Activation is absent, fresh or drifted; `unreleased` while this build has not released hooks-v1 (T13557), whatever the receipt says. */
|
|
245
|
+
activation: 'disabled' | 'missing' | 'active' | 'drifted' | 'unreleased';
|
|
246
|
+
/** Safe assessment codes. */
|
|
247
|
+
diagnostics: string[];
|
|
248
|
+
/** Native harness trust cannot be inferred from files on disk. */
|
|
249
|
+
nativeTrust: 'unverified';
|
|
250
|
+
/** Native project trust is independent of CLEO activation. */
|
|
251
|
+
nativeProjectTrust: 'unverified';
|
|
252
|
+
/** Exact native hook-definition trust is independent of project trust. */
|
|
253
|
+
nativeHookTrust: 'unverified';
|
|
254
|
+
/** Last bounded execution record when valid. */
|
|
255
|
+
lastExecution?: HookExecutionReceipt;
|
|
256
|
+
}
|
package/src/release/channel.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
/** npm dist-tag channel for a release. */
|
|
13
|
-
export type ReleaseChannel = 'latest' | 'beta' | 'alpha';
|
|
13
|
+
export type ReleaseChannel = 'latest' | 'beta' | 'alpha' | 'canary';
|
|
14
14
|
|
|
15
15
|
/** Result of validating a version string against a channel's expectations. */
|
|
16
16
|
export interface ChannelValidationResult {
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/** Bound, retained evidence required before promoting project hooks to stable (T13350). */
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/** Required pilot checks; every named check must pass on the same implementation. */
|
|
5
|
+
export const HOOK_PILOT_CHECKS = [
|
|
6
|
+
'packedArtifact',
|
|
7
|
+
'publishedCanary',
|
|
8
|
+
'readOnlyDev',
|
|
9
|
+
'readOnlyProd',
|
|
10
|
+
'liveClaude',
|
|
11
|
+
'liveCodex',
|
|
12
|
+
'hostedCi',
|
|
13
|
+
] as const;
|
|
14
|
+
/** A required hook pilot check. */
|
|
15
|
+
export type HookPilotCheck = (typeof HOOK_PILOT_CHECKS)[number];
|
|
16
|
+
/** SHA-256 evidence or implementation digest. */
|
|
17
|
+
export const HookPilotDigestSchema = z.string().regex(/^[a-f0-9]{64}$/);
|
|
18
|
+
/** An independent, numbered canary release; RC/beta does not satisfy this pilot. */
|
|
19
|
+
export const HookPilotCanarySchema = z.string().regex(/^\d{4}\.\d+\.\d+-canary\.\d+$/);
|
|
20
|
+
/** Exact live harness versions, kept separate from source-version normalization. */
|
|
21
|
+
export const HookPilotHarnessVersionsSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
claude: z.string().min(1).max(128),
|
|
24
|
+
codex: z.string().min(1).max(128),
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
/** Retained proof path remains inside the evidence artifact directory. */
|
|
28
|
+
export const HookPilotProofReferenceSchema = z
|
|
29
|
+
.object({
|
|
30
|
+
path: z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(4096)
|
|
34
|
+
.refine(
|
|
35
|
+
(value) =>
|
|
36
|
+
!value.startsWith('/') &&
|
|
37
|
+
!value.includes('\\') &&
|
|
38
|
+
!value.split('/').includes('..') &&
|
|
39
|
+
!/^[A-Za-z]:/.test(value),
|
|
40
|
+
'Expected a relative proof path without traversal',
|
|
41
|
+
),
|
|
42
|
+
sha256: HookPilotDigestSchema,
|
|
43
|
+
})
|
|
44
|
+
.strict();
|
|
45
|
+
/** Common identity attached to every retained proof, never inferred from similar releases. */
|
|
46
|
+
export const HookPilotIdentitySchema = z
|
|
47
|
+
.object({
|
|
48
|
+
canaryVersion: HookPilotCanarySchema,
|
|
49
|
+
sourceDigest: HookPilotDigestSchema,
|
|
50
|
+
vidaCommit: z.string().regex(/^[a-f0-9]{40}$/),
|
|
51
|
+
harnessVersions: HookPilotHarnessVersionsSchema,
|
|
52
|
+
})
|
|
53
|
+
.strict();
|
|
54
|
+
/** A bounded collection of actual pilot receipts. Missing or partial checks hold promotion. */
|
|
55
|
+
export const HookPilotEvidenceSchema = HookPilotIdentitySchema.extend({
|
|
56
|
+
schemaVersion: z.literal(1),
|
|
57
|
+
vidaRepository: z
|
|
58
|
+
.string()
|
|
59
|
+
.regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/)
|
|
60
|
+
.max(256),
|
|
61
|
+
hostedRunId: z.number().int().positive().safe(),
|
|
62
|
+
checks: z
|
|
63
|
+
.object({
|
|
64
|
+
packedArtifact: HookPilotProofReferenceSchema,
|
|
65
|
+
publishedCanary: HookPilotProofReferenceSchema,
|
|
66
|
+
readOnlyDev: HookPilotProofReferenceSchema,
|
|
67
|
+
readOnlyProd: HookPilotProofReferenceSchema,
|
|
68
|
+
liveClaude: HookPilotProofReferenceSchema,
|
|
69
|
+
liveCodex: HookPilotProofReferenceSchema,
|
|
70
|
+
hostedCi: HookPilotProofReferenceSchema,
|
|
71
|
+
})
|
|
72
|
+
.strict(),
|
|
73
|
+
}).strict();
|
|
74
|
+
/** Validated evidence bundle index. */
|
|
75
|
+
export type HookPilotEvidence = z.infer<typeof HookPilotEvidenceSchema>;
|
|
76
|
+
/** Structured, redacted proof produced by a real check and retained with its hash. */
|
|
77
|
+
export const HookPilotProofSchema = HookPilotIdentitySchema.extend({
|
|
78
|
+
schemaVersion: z.literal(1),
|
|
79
|
+
check: z.enum(HOOK_PILOT_CHECKS),
|
|
80
|
+
status: z.literal('pass'),
|
|
81
|
+
observedAt: z.string().datetime(),
|
|
82
|
+
checkedCount: z.number().int().min(1).max(100000),
|
|
83
|
+
exitCode: z.literal(0),
|
|
84
|
+
mode: z.enum(['packed-artifact', 'published-canary', 'read-only', 'live-harness', 'hosted-ci']),
|
|
85
|
+
packageVersions: z.record(z.string().max(256), z.string().max(128)).optional(),
|
|
86
|
+
ordinaryCommands: z.number().int().min(1).max(100000).optional(),
|
|
87
|
+
disposablePushes: z.number().int().min(1).max(100000).optional(),
|
|
88
|
+
hostedRunId: z.number().int().positive().safe().optional(),
|
|
89
|
+
url: z.string().url().max(2048).optional(),
|
|
90
|
+
}).strict();
|
|
91
|
+
/** A validated retained proof. */
|
|
92
|
+
export type HookPilotProof = z.infer<typeof HookPilotProofSchema>;
|
|
93
|
+
/** Minimal authoritative GitHub response required for an actual successful workflow run. */
|
|
94
|
+
export const HookPilotHostedRunSchema = z.object({
|
|
95
|
+
id: z.number().int().positive(),
|
|
96
|
+
head_sha: z.string().regex(/^[a-f0-9]{40}$/),
|
|
97
|
+
status: z.literal('completed'),
|
|
98
|
+
conclusion: z.literal('success'),
|
|
99
|
+
path: z.string().max(4096),
|
|
100
|
+
html_url: z.string().url().max(2048),
|
|
101
|
+
repository: z.object({ full_name: z.string().max(256) }),
|
|
102
|
+
});
|
|
103
|
+
/** Minimal runner/step evidence; an empty never-started job cannot satisfy hosted CI. */
|
|
104
|
+
export const HookPilotHostedJobsSchema = z.object({
|
|
105
|
+
jobs: z
|
|
106
|
+
.array(
|
|
107
|
+
z.object({
|
|
108
|
+
name: z.string().max(256),
|
|
109
|
+
status: z.string().max(32),
|
|
110
|
+
conclusion: z.string().nullable(),
|
|
111
|
+
runner_id: z.number().int(),
|
|
112
|
+
runner_name: z.string().nullable(),
|
|
113
|
+
labels: z.array(z.string().max(128)).max(32),
|
|
114
|
+
steps: z
|
|
115
|
+
.array(
|
|
116
|
+
z.object({
|
|
117
|
+
name: z.string().max(512),
|
|
118
|
+
status: z.string().max(32),
|
|
119
|
+
conclusion: z.string().nullable(),
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
.max(256),
|
|
123
|
+
}),
|
|
124
|
+
)
|
|
125
|
+
.max(100),
|
|
126
|
+
});
|
|
127
|
+
/** A bounded API JSON reader. Production uses gh; tests inject only transport responses. */
|
|
128
|
+
export type HookPilotApiReader = (endpoint: string) => string;
|
|
129
|
+
/** Inputs that bind validation to the release currently being promoted. */
|
|
130
|
+
export interface HookPilotValidationOptions {
|
|
131
|
+
/** Repository checkout used for the normalized implementation digest. */
|
|
132
|
+
root: string;
|
|
133
|
+
/** Retained evidence index; its directory contains all proof files. */
|
|
134
|
+
evidencePath: string;
|
|
135
|
+
/** Stable version receiving promotion; cannot precede the canary base. Source proof must match. */
|
|
136
|
+
stableVersion: string;
|
|
137
|
+
/** Trusted release-side pilot repository; evidence cannot redirect authority. */
|
|
138
|
+
vidaRepository: string;
|
|
139
|
+
/** Authoritative read transport, injectable for sanitized tests. */
|
|
140
|
+
readApi?: HookPilotApiReader;
|
|
141
|
+
}
|
|
142
|
+
/** Reusable normalized source digest and exact publish cohort. */
|
|
143
|
+
export interface HookPilotSourceDigest {
|
|
144
|
+
/** Algorithm format, including inclusion and normalization rules. */
|
|
145
|
+
schemaVersion: 1;
|
|
146
|
+
/** SHA-256 of sorted framed mode/path/content records with manifest normalization. */
|
|
147
|
+
digest: string;
|
|
148
|
+
/** Number of assessed tracked files. */
|
|
149
|
+
fileCount: number;
|
|
150
|
+
/** Package names from release.yml's publish_pkg SSoT. */
|
|
151
|
+
cohort: string[];
|
|
152
|
+
}
|
|
153
|
+
/** Validation result contains attribution only, never proof payloads or credentials. */
|
|
154
|
+
export interface HookPilotValidationResult {
|
|
155
|
+
/** Confirmed independent canary. */
|
|
156
|
+
canaryVersion: string;
|
|
157
|
+
/** Normalized source digest used by this validation. */
|
|
158
|
+
sourceDigest: string;
|
|
159
|
+
/** Exact VidaPeps commit validated by hosted CI. */
|
|
160
|
+
vidaCommit: string;
|
|
161
|
+
/** Required checks validated. */
|
|
162
|
+
checks: HookPilotCheck[];
|
|
163
|
+
}
|
package/src/release/plan.test.ts
CHANGED
|
@@ -133,8 +133,8 @@ describe('Release plan constant tuples', () => {
|
|
|
133
133
|
expect(RELEASE_STATUS).toHaveLength(8);
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
-
it('exports the
|
|
137
|
-
expect(new Set(RELEASE_CHANNEL)).toEqual(new Set(['latest', 'beta', 'alpha', 'rc']));
|
|
136
|
+
it('exports the independent canary channel alongside existing channels', () => {
|
|
137
|
+
expect(new Set(RELEASE_CHANNEL)).toEqual(new Set(['latest', 'beta', 'alpha', 'rc', 'canary']));
|
|
138
138
|
});
|
|
139
139
|
|
|
140
140
|
it('exports the 3-scheme set including calver-suffix', () => {
|
|
@@ -254,10 +254,22 @@ describe('ReleasePlanSchema — status FSM', () => {
|
|
|
254
254
|
|
|
255
255
|
describe('ReleasePlanSchema — enum rejection', () => {
|
|
256
256
|
it('rejects unknown channel', () => {
|
|
257
|
-
const plan = { ...makeValidPlan(), channel: '
|
|
257
|
+
const plan = { ...makeValidPlan(), channel: 'rolling' };
|
|
258
258
|
expect(() => parseReleasePlan(plan)).toThrow();
|
|
259
259
|
});
|
|
260
260
|
|
|
261
|
+
it('accepts numbered canaries and rejects channel/version disagreement', () => {
|
|
262
|
+
const plan = {
|
|
263
|
+
...makeValidPlan(),
|
|
264
|
+
version: 'v2026.10.6-canary.1',
|
|
265
|
+
resolvedVersion: 'v2026.10.6-canary.1',
|
|
266
|
+
channel: 'canary',
|
|
267
|
+
};
|
|
268
|
+
expect(parseReleasePlan(plan).channel).toBe('canary');
|
|
269
|
+
expect(() => parseReleasePlan({ ...plan, channel: 'latest' })).toThrow();
|
|
270
|
+
expect(() => parseReleasePlan({ ...plan, resolvedVersion: 'v2026.10.6-canary' })).toThrow();
|
|
271
|
+
});
|
|
272
|
+
|
|
261
273
|
it('rejects unknown scheme', () => {
|
|
262
274
|
const plan = { ...makeValidPlan(), scheme: 'rolling' };
|
|
263
275
|
expect(() => parseReleasePlan(plan)).toThrow();
|
package/src/release/plan.ts
CHANGED
|
@@ -52,7 +52,15 @@ export const RELEASE_PLAN_SCHEMA_URL = 'https://cleocode.io/schemas/release-plan
|
|
|
52
52
|
* Includes `rc` in addition to the legacy `latest|beta|alpha` triple to allow
|
|
53
53
|
* release candidates without conflating with `beta` per SPEC §6.1.
|
|
54
54
|
*/
|
|
55
|
-
export const RELEASE_CHANNEL = ['latest', 'beta', 'alpha', 'rc'] as const;
|
|
55
|
+
export const RELEASE_CHANNEL = ['latest', 'beta', 'alpha', 'rc', 'canary'] as const;
|
|
56
|
+
/** Persisted prerelease classification, shared by planning and reconciliation. */
|
|
57
|
+
export const RELEASE_PERSISTED_CHANNEL = {
|
|
58
|
+
latest: 'latest',
|
|
59
|
+
beta: 'beta',
|
|
60
|
+
alpha: 'dev',
|
|
61
|
+
rc: 'beta',
|
|
62
|
+
canary: 'beta',
|
|
63
|
+
} as const;
|
|
56
64
|
|
|
57
65
|
/**
|
|
58
66
|
* Version-scheme variants. `calver-suffix` is the hotfix grammar
|
|
@@ -347,61 +355,78 @@ export const ReleasePlanMetaSchema = z
|
|
|
347
355
|
* - R-305: `platformMatrix[]` non-empty.
|
|
348
356
|
* - R-306: deterministic re-validation inside `open` and `reconcile`.
|
|
349
357
|
*/
|
|
350
|
-
export const ReleasePlanSchema = z
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
358
|
+
export const ReleasePlanSchema = z
|
|
359
|
+
.object({
|
|
360
|
+
/** Schema URL for this plan version. */
|
|
361
|
+
$schema: z.string().optional(),
|
|
362
|
+
/** Requested version string (e.g. "v2026.6.0"). Includes the leading `v`. */
|
|
363
|
+
version: NonEmptyString,
|
|
364
|
+
/** Resolved version string after suffix application (e.g. "v2026.6.0.2"). */
|
|
365
|
+
resolvedVersion: NonEmptyString,
|
|
366
|
+
/** True if a `calver-suffix` was applied to disambiguate a same-day hotfix. */
|
|
367
|
+
suffixApplied: z.boolean(),
|
|
368
|
+
/** Versioning scheme governing `version` / `resolvedVersion`. */
|
|
369
|
+
scheme: ReleaseSchemeSchema,
|
|
370
|
+
/** npm dist-tag channel for this release. */
|
|
371
|
+
channel: ReleaseChannelSchema,
|
|
372
|
+
/**
|
|
373
|
+
* Epic ID this release ships: the `--epic` epic or the `--saga` saga. `null`
|
|
374
|
+
* for a `--tasks` plan, whose tasks may span several epics; each task's own
|
|
375
|
+
* epic is on its `epicAncestor` (T13323).
|
|
376
|
+
*/
|
|
377
|
+
epicId: NonEmptyString.nullable(),
|
|
378
|
+
/** Release-kind classification. */
|
|
379
|
+
releaseKind: ReleaseKindSchema,
|
|
380
|
+
/** Explicit hooks-v1 stable promotion; ordinary and hotfix releases are exempt. */
|
|
381
|
+
hooksV1Promotion: z.boolean().optional(),
|
|
382
|
+
/** ISO-8601 timestamp the plan was written. */
|
|
383
|
+
createdAt: Iso8601,
|
|
384
|
+
/** Identifier of the actor that wrote the plan (agent name or operator). */
|
|
385
|
+
createdBy: NonEmptyString,
|
|
386
|
+
/**
|
|
387
|
+
* Version of the previous release on the same channel. MUST be `null` only
|
|
388
|
+
* for first-ever releases (R-300, enforced at the verb layer).
|
|
389
|
+
*/
|
|
390
|
+
previousVersion: z.string().nullable(),
|
|
391
|
+
/** Git tag of the previous release (typically `previousVersion` prefixed). */
|
|
392
|
+
previousTag: z.string().nullable(),
|
|
393
|
+
/** ISO-8601 timestamp the previous release was published. */
|
|
394
|
+
previousShippedAt: Iso8601.nullable(),
|
|
395
|
+
/** Tasks rolled into this release. */
|
|
396
|
+
tasks: z.array(ReleasePlanTaskSchema),
|
|
397
|
+
/** Bucketed changelog. */
|
|
398
|
+
changelog: ReleasePlanChangelogSchema,
|
|
399
|
+
/** Per-gate verification status. */
|
|
400
|
+
gates: z.array(ReleaseGateSchema),
|
|
401
|
+
/** Platform / publisher matrix. */
|
|
402
|
+
platformMatrix: z.array(ReleasePlatformMatrixEntrySchema),
|
|
403
|
+
/** Preflight summary from `cleo release plan`. */
|
|
404
|
+
preflightSummary: ReleasePreflightSummarySchema,
|
|
405
|
+
/** URL of the GHA workflow run (populated by `release-prepare.yml`). */
|
|
406
|
+
workflowRunUrl: z.string().nullable(),
|
|
407
|
+
/** URL of the bump PR (populated by `cleo release open`). */
|
|
408
|
+
prUrl: z.string().nullable(),
|
|
409
|
+
/** Merge commit SHA on `main` (populated by `release-publish.yml`). */
|
|
410
|
+
mergeCommitSha: z.string().nullable(),
|
|
411
|
+
/** Current FSM state per R-302. */
|
|
412
|
+
status: ReleaseStatusSchema,
|
|
413
|
+
/** Informational / forward-compat metadata. */
|
|
414
|
+
meta: ReleasePlanMetaSchema.optional(),
|
|
415
|
+
})
|
|
416
|
+
.superRefine((plan, context) => {
|
|
417
|
+
const canaryVersion = /-canary\.\d+$/.test(plan.resolvedVersion);
|
|
418
|
+
const canarySuffix = /-canary(?:\.|$)/.test(plan.resolvedVersion);
|
|
419
|
+
if (
|
|
420
|
+
(plan.channel === 'canary' && !canaryVersion) ||
|
|
421
|
+
(plan.channel !== 'canary' && canarySuffix)
|
|
422
|
+
) {
|
|
423
|
+
context.addIssue({
|
|
424
|
+
code: 'custom',
|
|
425
|
+
path: ['channel'],
|
|
426
|
+
message: 'Numbered canary versions require the independent canary channel.',
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
});
|
|
405
430
|
|
|
406
431
|
// ─── Inferred TypeScript types ───────────────────────────────────────────────
|
|
407
432
|
|
package/src/row-identity.ts
CHANGED
|
@@ -127,6 +127,13 @@ export interface RowIdentitySpec {
|
|
|
127
127
|
readonly storedRefUids?: readonly StoredRefUid[];
|
|
128
128
|
/** The key is a user-facing display id that a collision re-mints. */
|
|
129
129
|
readonly displayId?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* A LOCAL key column, other than {@link key}, that never travels (T12915):
|
|
132
|
+
* each device assigns its own value, references from other tables point at
|
|
133
|
+
* it, and on the wire they carry the row's uid instead. A received row gets
|
|
134
|
+
* a fresh local value (the next INTEGER id, or its uid for a TEXT column).
|
|
135
|
+
*/
|
|
136
|
+
readonly localKey?: string;
|
|
130
137
|
/** Task that declared the table. */
|
|
131
138
|
readonly task: string;
|
|
132
139
|
}
|
package/src/task.ts
CHANGED
|
@@ -313,6 +313,11 @@ export type EvidenceAtom =
|
|
|
313
313
|
* `tool:test-affected`'s `untestedPackages` (T12965 review).
|
|
314
314
|
*/
|
|
315
315
|
untestedPackages?: string[];
|
|
316
|
+
/**
|
|
317
|
+
* Tracked files matching `evidence.runtimePaths` whose working-tree
|
|
318
|
+
* edits the tree hash left out (T13554).
|
|
319
|
+
*/
|
|
320
|
+
runtimeExcluded?: string[];
|
|
316
321
|
}
|
|
317
322
|
| {
|
|
318
323
|
/**
|
|
@@ -337,6 +342,8 @@ export type EvidenceAtom =
|
|
|
337
342
|
headSha?: string;
|
|
338
343
|
/** Tool-cache tree hash at verify time; `cleo complete` refuses the atom once it moves. */
|
|
339
344
|
treeHash?: string;
|
|
345
|
+
/** Tracked `evidence.runtimePaths` files the tree hash left out (T13554). */
|
|
346
|
+
runtimeExcluded?: string[];
|
|
340
347
|
}
|
|
341
348
|
| {
|
|
342
349
|
kind: 'tool';
|