@neurcode-ai/contracts 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/proposed-change-validation.d.ts +20 -0
- package/dist/proposed-change-validation.d.ts.map +1 -0
- package/dist/proposed-change-validation.js +603 -0
- package/dist/proposed-change-validation.js.map +1 -0
- package/dist/repo-intelligence-v2.d.ts +622 -0
- package/dist/repo-intelligence-v2.d.ts.map +1 -0
- package/dist/repo-intelligence-v2.js +135 -0
- package/dist/repo-intelligence-v2.js.map +1 -0
- package/package.json +7 -1
- package/src/admission/admission-framing.test.ts +0 -93
- package/src/admission/framing.ts +0 -78
- package/src/admission/index.ts +0 -58
- package/src/admission/privacy.ts +0 -93
- package/src/admission/schema.ts +0 -392
- package/src/index.ts +0 -806
- package/src/intelligence.ts +0 -698
- package/src/remediation/capabilities.ts +0 -53
- package/src/remediation/index.ts +0 -29
- package/src/remediation/request.ts +0 -236
- package/src/remediation/response.ts +0 -129
- package/src/remediation/validation.ts +0 -109
- package/src/runtime-compatibility-manifest.test.ts +0 -43
- package/src/status-vocabulary.ts +0 -125
- package/src/verification/canonical-finding.ts +0 -196
- package/src/verification/index.ts +0 -41
- package/src/verification/pipeline.ts +0 -199
- package/src/verification/taxonomy.ts +0 -46
- package/tsconfig.json +0 -19
package/src/index.ts
DELETED
|
@@ -1,806 +0,0 @@
|
|
|
1
|
-
export const CLI_JSON_CONTRACT_VERSION = '2026-05-11';
|
|
2
|
-
|
|
3
|
-
/** Compare YYYY-MM-DD contract stamps; returns null when either side is unparsable. */
|
|
4
|
-
export function compareCalendarContractVersion(left: string, right: string): number | null {
|
|
5
|
-
const parse = (value: string): number | null => {
|
|
6
|
-
const match = /^(\d{4}-\d{2}-\d{2})/.exec(value.trim());
|
|
7
|
-
if (!match) return null;
|
|
8
|
-
const ms = Date.parse(`${match[1]}T00:00:00.000Z`);
|
|
9
|
-
return Number.isNaN(ms) ? null : ms;
|
|
10
|
-
};
|
|
11
|
-
const leftMs = parse(left);
|
|
12
|
-
const rightMs = parse(right);
|
|
13
|
-
if (leftMs === null || rightMs === null) return null;
|
|
14
|
-
if (leftMs === rightMs) return 0;
|
|
15
|
-
return leftMs < rightMs ? -1 : 1;
|
|
16
|
-
}
|
|
17
|
-
export * from './intelligence';
|
|
18
|
-
export * from './status-vocabulary';
|
|
19
|
-
export * from './verification';
|
|
20
|
-
export * from './remediation';
|
|
21
|
-
export * from './admission';
|
|
22
|
-
export const RUNTIME_COMPATIBILITY_CONTRACT_ID = 'neurcode-runtime-compatibility';
|
|
23
|
-
export const RUNTIME_COMPATIBILITY_CONTRACT_VERSION = '2026-04-04';
|
|
24
|
-
export const RUNTIME_COMPATIBILITY_MANIFEST_VERSION = '2026-06-02.1';
|
|
25
|
-
export const RUNTIME_COMPATIBILITY_MANIFEST_SCHEMA_VERSION = 1;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Runtime Admission contract (Phase A — Provenance Core). Additive: surfaces a
|
|
29
|
-
* version for the self-attested admission artifact + coverage manifest so the
|
|
30
|
-
* future Action and backend can negotiate compatibility. No enforcement yet.
|
|
31
|
-
*/
|
|
32
|
-
export const ADMISSION_CONTRACT_ID = 'neurcode-runtime-admission';
|
|
33
|
-
export const ADMISSION_CONTRACT_VERSION = '2026-06-02';
|
|
34
|
-
|
|
35
|
-
export type RuntimeComponent = 'cli' | 'action' | 'api';
|
|
36
|
-
|
|
37
|
-
export type RuntimeMinimumPeerVersions = Partial<Record<RuntimeComponent, string>>;
|
|
38
|
-
|
|
39
|
-
export interface RuntimeCompatibilityTriplet {
|
|
40
|
-
id: string;
|
|
41
|
-
channel: 'current' | 'support-floor' | 'compat-canary';
|
|
42
|
-
versions: Record<RuntimeComponent, string>;
|
|
43
|
-
notes?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface RuntimeCompatibilityManifest {
|
|
47
|
-
schemaVersion: number;
|
|
48
|
-
manifestVersion: string;
|
|
49
|
-
contractId: string;
|
|
50
|
-
runtimeContractVersion: string;
|
|
51
|
-
cliJsonContractVersion: string;
|
|
52
|
-
/** Runtime Admission provenance contract version (additive; Phase A). */
|
|
53
|
-
admissionContractVersion: string;
|
|
54
|
-
minimumPeerVersions: Record<RuntimeComponent, RuntimeMinimumPeerVersions>;
|
|
55
|
-
validatedTriplets: RuntimeCompatibilityTriplet[];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const RUNTIME_COMPATIBILITY_MANIFEST: RuntimeCompatibilityManifest = {
|
|
59
|
-
schemaVersion: RUNTIME_COMPATIBILITY_MANIFEST_SCHEMA_VERSION,
|
|
60
|
-
manifestVersion: RUNTIME_COMPATIBILITY_MANIFEST_VERSION,
|
|
61
|
-
contractId: RUNTIME_COMPATIBILITY_CONTRACT_ID,
|
|
62
|
-
runtimeContractVersion: RUNTIME_COMPATIBILITY_CONTRACT_VERSION,
|
|
63
|
-
cliJsonContractVersion: CLI_JSON_CONTRACT_VERSION,
|
|
64
|
-
admissionContractVersion: ADMISSION_CONTRACT_VERSION,
|
|
65
|
-
minimumPeerVersions: {
|
|
66
|
-
cli: {
|
|
67
|
-
action: '0.2.1',
|
|
68
|
-
api: '0.2.0',
|
|
69
|
-
},
|
|
70
|
-
action: {
|
|
71
|
-
cli: '0.9.35',
|
|
72
|
-
api: '0.2.0',
|
|
73
|
-
},
|
|
74
|
-
api: {
|
|
75
|
-
cli: '0.9.35',
|
|
76
|
-
action: '0.2.1',
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
validatedTriplets: [
|
|
80
|
-
{
|
|
81
|
-
id: 'current',
|
|
82
|
-
channel: 'current',
|
|
83
|
-
versions: {
|
|
84
|
-
cli: '0.14.0',
|
|
85
|
-
action: '0.2.4',
|
|
86
|
-
api: '0.2.0',
|
|
87
|
-
},
|
|
88
|
-
notes: 'Current release train validated in monorepo CI.',
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
id: 'support-floor',
|
|
92
|
-
channel: 'support-floor',
|
|
93
|
-
versions: {
|
|
94
|
-
cli: '0.9.35',
|
|
95
|
-
action: '0.2.1',
|
|
96
|
-
api: '0.2.0',
|
|
97
|
-
},
|
|
98
|
-
notes: 'Minimum supported compatibility floor for enterprise rollout.',
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const RUNTIME_MINIMUM_PEER_VERSIONS: Record<RuntimeComponent, RuntimeMinimumPeerVersions> =
|
|
104
|
-
RUNTIME_COMPATIBILITY_MANIFEST.minimumPeerVersions;
|
|
105
|
-
|
|
106
|
-
export function getRuntimeCompatibilityManifest(): RuntimeCompatibilityManifest {
|
|
107
|
-
return {
|
|
108
|
-
...RUNTIME_COMPATIBILITY_MANIFEST,
|
|
109
|
-
admissionContractVersion: RUNTIME_COMPATIBILITY_MANIFEST.admissionContractVersion,
|
|
110
|
-
minimumPeerVersions: {
|
|
111
|
-
cli: { ...RUNTIME_COMPATIBILITY_MANIFEST.minimumPeerVersions.cli },
|
|
112
|
-
action: { ...RUNTIME_COMPATIBILITY_MANIFEST.minimumPeerVersions.action },
|
|
113
|
-
api: { ...RUNTIME_COMPATIBILITY_MANIFEST.minimumPeerVersions.api },
|
|
114
|
-
},
|
|
115
|
-
validatedTriplets: RUNTIME_COMPATIBILITY_MANIFEST.validatedTriplets.map((triplet) => ({
|
|
116
|
-
...triplet,
|
|
117
|
-
versions: { ...triplet.versions },
|
|
118
|
-
})),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export interface RuntimeCompatibilityDescriptor {
|
|
123
|
-
contractId: string;
|
|
124
|
-
runtimeContractVersion: string;
|
|
125
|
-
cliJsonContractVersion: string;
|
|
126
|
-
manifestVersion?: string;
|
|
127
|
-
/** Runtime Admission provenance contract version (additive; optional for legacy peers). */
|
|
128
|
-
admissionContractVersion?: string;
|
|
129
|
-
component: RuntimeComponent;
|
|
130
|
-
componentVersion: string;
|
|
131
|
-
minimumPeerVersions: RuntimeMinimumPeerVersions;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export interface CliContractBase {
|
|
135
|
-
contractVersion?: string;
|
|
136
|
-
[key: string]: unknown;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export interface CliPlanJsonPayload extends CliContractBase {
|
|
140
|
-
success: boolean;
|
|
141
|
-
cached: boolean;
|
|
142
|
-
mode: string;
|
|
143
|
-
planId: string | null;
|
|
144
|
-
sessionId: string | null;
|
|
145
|
-
timestamp: string;
|
|
146
|
-
message: string;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export interface CliApplyJsonPayload extends CliContractBase {
|
|
150
|
-
success: boolean;
|
|
151
|
-
planId: string;
|
|
152
|
-
filesGenerated: number;
|
|
153
|
-
files: unknown[];
|
|
154
|
-
writtenFiles: unknown[];
|
|
155
|
-
message: string;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export type VerifyVerdict = 'PASS' | 'WARN' | 'FAIL';
|
|
159
|
-
export type VerifySeverity = 'critical' | 'high' | 'warning' | 'info';
|
|
160
|
-
|
|
161
|
-
export interface VerifyOutputSummary {
|
|
162
|
-
totalFilesChanged: number;
|
|
163
|
-
totalViolations: number;
|
|
164
|
-
totalWarnings: number;
|
|
165
|
-
totalScopeIssues: number;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export interface VerifyOutputViolation {
|
|
169
|
-
file: string;
|
|
170
|
-
message: string;
|
|
171
|
-
policy: string;
|
|
172
|
-
severity: VerifySeverity;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export interface VerifyOutputWarning {
|
|
176
|
-
file: string;
|
|
177
|
-
message: string;
|
|
178
|
-
policy: string;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export type VerifyScopeIssuePolicy = 'forbidden' | 'review-required' | 'out-of-scope' | 'generated-code' | 'unscoped';
|
|
182
|
-
export type VerifyScopeIssueBoundaryType = 'sensitive' | 'infra' | 'ci' | 'dependency-manifest' | 'service' | 'module' | 'generated-code' | 'unspecified';
|
|
183
|
-
|
|
184
|
-
export type VerifyImportEdgeKind = 'static' | 'relative' | 'dynamic' | 'require' | 'side-effect';
|
|
185
|
-
export type VerifyImportEdgeLanguage = 'python' | 'typescript' | 'javascript';
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Discriminator metadata attached to a scope issue when the breach was
|
|
189
|
-
* detected through an import edge rather than a touched file path.
|
|
190
|
-
* Present iff the scope issue originated from `evaluateImportEdgeGovernance`.
|
|
191
|
-
*/
|
|
192
|
-
export interface VerifyOutputImportEdge {
|
|
193
|
-
sourceFile: string;
|
|
194
|
-
sourceLine: number;
|
|
195
|
-
importTarget: string;
|
|
196
|
-
resolvedTargetPath: string;
|
|
197
|
-
resolvedBoundary: string;
|
|
198
|
-
edgeKind: VerifyImportEdgeKind;
|
|
199
|
-
language: VerifyImportEdgeLanguage;
|
|
200
|
-
deterministic: true;
|
|
201
|
-
replayStable: true;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export interface VerifyOutputScopeIssue {
|
|
205
|
-
file: string;
|
|
206
|
-
message: string;
|
|
207
|
-
/**
|
|
208
|
-
* Severity / governance classification of the scope issue.
|
|
209
|
-
* Optional for backward compatibility with pre-runtime-activation payloads.
|
|
210
|
-
*/
|
|
211
|
-
policy?: VerifyScopeIssuePolicy;
|
|
212
|
-
/**
|
|
213
|
-
* Boundary category this file touched (when known). Optional so legacy
|
|
214
|
-
* payloads remain valid.
|
|
215
|
-
*/
|
|
216
|
-
boundaryType?: VerifyScopeIssueBoundaryType;
|
|
217
|
-
/**
|
|
218
|
-
* Set on issues raised by the deterministic import-edge governance layer
|
|
219
|
-
* (an allowed source file importing from a forbidden boundary).
|
|
220
|
-
*/
|
|
221
|
-
importEdge?: VerifyOutputImportEdge;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export interface VerifyOutput {
|
|
225
|
-
verdict: VerifyVerdict;
|
|
226
|
-
summary: VerifyOutputSummary;
|
|
227
|
-
violations: VerifyOutputViolation[];
|
|
228
|
-
warnings: VerifyOutputWarning[];
|
|
229
|
-
scopeIssues: VerifyOutputScopeIssue[];
|
|
230
|
-
driftScore?: number;
|
|
231
|
-
/** Canonical governance model (additive; absent in legacy payloads). */
|
|
232
|
-
governanceVerification?: import('./verification').GovernanceVerificationEnvelope;
|
|
233
|
-
governanceFindings?: import('./verification').GovernanceFinding[];
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export type CliVerifyJsonPayload = VerifyOutput;
|
|
237
|
-
|
|
238
|
-
export interface CliPromptJsonPayload extends CliContractBase {
|
|
239
|
-
success: boolean;
|
|
240
|
-
planId: string | null;
|
|
241
|
-
intent: string | null;
|
|
242
|
-
prompt: string | null;
|
|
243
|
-
copied: boolean;
|
|
244
|
-
outputPath: string | null;
|
|
245
|
-
message: string;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export interface CliContractImportJsonPayload extends CliContractBase {
|
|
249
|
-
success: boolean;
|
|
250
|
-
provider: string | null;
|
|
251
|
-
planId: string | null;
|
|
252
|
-
sessionId: string | null;
|
|
253
|
-
projectId: string | null;
|
|
254
|
-
parseMode: 'json' | 'text' | null;
|
|
255
|
-
importedFiles: number;
|
|
256
|
-
sourcePath?: string | null;
|
|
257
|
-
autoDetect?: Record<string, unknown> | null;
|
|
258
|
-
warnings: unknown[];
|
|
259
|
-
changeContract?: Record<string, unknown> | null;
|
|
260
|
-
message: string;
|
|
261
|
-
timestamp: string;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export interface CliShipJsonPayload extends CliContractBase {
|
|
265
|
-
success: boolean;
|
|
266
|
-
status: string;
|
|
267
|
-
finalPlanId: string | null;
|
|
268
|
-
audit?: Record<string, unknown>;
|
|
269
|
-
error?: Record<string, unknown>;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export interface CliShipRunsJsonPayload extends CliContractBase {
|
|
273
|
-
runs: unknown[];
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
export interface CliShipResumeJsonPayload extends CliContractBase {
|
|
277
|
-
success: boolean;
|
|
278
|
-
status: string;
|
|
279
|
-
error?: Record<string, unknown>;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export interface CliShipAttestationVerifyJsonPayload extends CliContractBase {
|
|
283
|
-
pass: boolean;
|
|
284
|
-
message?: string;
|
|
285
|
-
digest?: Record<string, unknown>;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
export interface CliCompatJsonPayload extends CliContractBase {
|
|
289
|
-
success: boolean;
|
|
290
|
-
timestamp: string;
|
|
291
|
-
component: 'cli';
|
|
292
|
-
componentVersion: string;
|
|
293
|
-
compatibility: RuntimeCompatibilityDescriptor;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function asRecord(value: unknown, label: string): Record<string, unknown> {
|
|
297
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
298
|
-
throw new Error(`${label}: expected object`);
|
|
299
|
-
}
|
|
300
|
-
return value as Record<string, unknown>;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function asBoolean(record: Record<string, unknown>, key: string, label: string): boolean {
|
|
304
|
-
if (typeof record[key] !== 'boolean') {
|
|
305
|
-
throw new Error(`${label}: expected ${key}:boolean`);
|
|
306
|
-
}
|
|
307
|
-
return record[key] as boolean;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function asNumber(record: Record<string, unknown>, key: string, label: string): number {
|
|
311
|
-
if (typeof record[key] !== 'number' || Number.isNaN(record[key])) {
|
|
312
|
-
throw new Error(`${label}: expected ${key}:number`);
|
|
313
|
-
}
|
|
314
|
-
return record[key] as number;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function asString(record: Record<string, unknown>, key: string, label: string): string {
|
|
318
|
-
if (typeof record[key] !== 'string') {
|
|
319
|
-
throw new Error(`${label}: expected ${key}:string`);
|
|
320
|
-
}
|
|
321
|
-
return record[key] as string;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function asNullableString(record: Record<string, unknown>, key: string, label: string): string | null {
|
|
325
|
-
const value = record[key];
|
|
326
|
-
if (value === null || typeof value === 'string') {
|
|
327
|
-
return value as string | null;
|
|
328
|
-
}
|
|
329
|
-
throw new Error(`${label}: expected ${key}:string|null`);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
function asArray(record: Record<string, unknown>, key: string, label: string): unknown[] {
|
|
333
|
-
if (!Array.isArray(record[key])) {
|
|
334
|
-
throw new Error(`${label}: expected ${key}:array`);
|
|
335
|
-
}
|
|
336
|
-
return record[key] as unknown[];
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function asOptionalRecord(record: Record<string, unknown>, key: string, label: string): Record<string, unknown> | undefined {
|
|
340
|
-
const value = record[key];
|
|
341
|
-
if (value === undefined) return undefined;
|
|
342
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
343
|
-
throw new Error(`${label}: expected ${key}:object`);
|
|
344
|
-
}
|
|
345
|
-
return value as Record<string, unknown>;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function asOptionalString(record: Record<string, unknown>, key: string, label: string): string | undefined {
|
|
349
|
-
const value = record[key];
|
|
350
|
-
if (value === undefined) return undefined;
|
|
351
|
-
if (typeof value !== 'string') {
|
|
352
|
-
throw new Error(`${label}: expected ${key}:string`);
|
|
353
|
-
}
|
|
354
|
-
return value;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function asIntegerNumber(record: Record<string, unknown>, key: string, label: string): number {
|
|
358
|
-
const value = asNumber(record, key, label);
|
|
359
|
-
return Math.max(0, Math.floor(value));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
function asContractVersion(record: Record<string, unknown>): string | undefined {
|
|
363
|
-
const value = record.contractVersion;
|
|
364
|
-
if (value === undefined) return undefined;
|
|
365
|
-
if (typeof value !== 'string') {
|
|
366
|
-
throw new Error('contractVersion: expected string when present');
|
|
367
|
-
}
|
|
368
|
-
return value;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function asRuntimeComponent(record: Record<string, unknown>, key: string, label: string): RuntimeComponent {
|
|
372
|
-
const value = asString(record, key, label);
|
|
373
|
-
if (value === 'cli' || value === 'action' || value === 'api') {
|
|
374
|
-
return value;
|
|
375
|
-
}
|
|
376
|
-
throw new Error(`${label}: expected ${key}:("cli"|"action"|"api")`);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
function parseRuntimeMinimumPeerVersions(
|
|
380
|
-
value: unknown,
|
|
381
|
-
label: string
|
|
382
|
-
): RuntimeMinimumPeerVersions {
|
|
383
|
-
if (value === undefined || value === null) return {};
|
|
384
|
-
const record = asRecord(value, `${label}.minimumPeerVersions`);
|
|
385
|
-
const next: RuntimeMinimumPeerVersions = {};
|
|
386
|
-
for (const component of ['cli', 'action', 'api'] as const) {
|
|
387
|
-
const componentValue = record[component];
|
|
388
|
-
if (componentValue === undefined) continue;
|
|
389
|
-
if (typeof componentValue !== 'string' || !componentValue.trim()) {
|
|
390
|
-
throw new Error(`${label}.minimumPeerVersions: expected ${component}:string`);
|
|
391
|
-
}
|
|
392
|
-
next[component] = componentValue.trim();
|
|
393
|
-
}
|
|
394
|
-
return next;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
function parseRuntimeCompatibilityDescriptor(
|
|
398
|
-
value: unknown,
|
|
399
|
-
label: string
|
|
400
|
-
): RuntimeCompatibilityDescriptor {
|
|
401
|
-
const record = asRecord(value, `${label}.compatibility`);
|
|
402
|
-
return {
|
|
403
|
-
contractId: asString(record, 'contractId', `${label}.compatibility`),
|
|
404
|
-
runtimeContractVersion: asString(record, 'runtimeContractVersion', `${label}.compatibility`),
|
|
405
|
-
cliJsonContractVersion: asString(record, 'cliJsonContractVersion', `${label}.compatibility`),
|
|
406
|
-
manifestVersion: asOptionalString(record, 'manifestVersion', `${label}.compatibility`),
|
|
407
|
-
admissionContractVersion: asOptionalString(record, 'admissionContractVersion', `${label}.compatibility`),
|
|
408
|
-
component: asRuntimeComponent(record, 'component', `${label}.compatibility`),
|
|
409
|
-
componentVersion: asString(record, 'componentVersion', `${label}.compatibility`),
|
|
410
|
-
minimumPeerVersions: parseRuntimeMinimumPeerVersions(record.minimumPeerVersions, `${label}.compatibility`),
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function parseSemver(value: string): [number, number, number] | null {
|
|
415
|
-
const normalized = value.trim().replace(/^v/i, '').split('+')[0].split('-')[0];
|
|
416
|
-
const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)$/);
|
|
417
|
-
if (!match) return null;
|
|
418
|
-
const major = Number(match[1]);
|
|
419
|
-
const minor = Number(match[2]);
|
|
420
|
-
const patch = Number(match[3]);
|
|
421
|
-
if (!Number.isFinite(major) || !Number.isFinite(minor) || !Number.isFinite(patch)) {
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
return [major, minor, patch];
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
export function compareSemver(left: string, right: string): number | null {
|
|
428
|
-
const l = parseSemver(left);
|
|
429
|
-
const r = parseSemver(right);
|
|
430
|
-
if (!l || !r) return null;
|
|
431
|
-
for (let idx = 0; idx < 3; idx += 1) {
|
|
432
|
-
if (l[idx] > r[idx]) return 1;
|
|
433
|
-
if (l[idx] < r[idx]) return -1;
|
|
434
|
-
}
|
|
435
|
-
return 0;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
export function isSemverAtLeast(actual: string, minimum: string): boolean | null {
|
|
439
|
-
const compare = compareSemver(actual, minimum);
|
|
440
|
-
if (compare === null) return null;
|
|
441
|
-
return compare >= 0;
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
export function getMinimumCompatiblePeerVersion(
|
|
445
|
-
component: RuntimeComponent,
|
|
446
|
-
peer: RuntimeComponent
|
|
447
|
-
): string | undefined {
|
|
448
|
-
return RUNTIME_MINIMUM_PEER_VERSIONS[component][peer];
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
export function getRuntimeMinimumPeerVersionMatrix(): Record<RuntimeComponent, RuntimeMinimumPeerVersions> {
|
|
452
|
-
return {
|
|
453
|
-
cli: { ...RUNTIME_MINIMUM_PEER_VERSIONS.cli },
|
|
454
|
-
action: { ...RUNTIME_MINIMUM_PEER_VERSIONS.action },
|
|
455
|
-
api: { ...RUNTIME_MINIMUM_PEER_VERSIONS.api },
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
export function buildRuntimeCompatibilityDescriptor(
|
|
460
|
-
component: RuntimeComponent,
|
|
461
|
-
componentVersion: string
|
|
462
|
-
): RuntimeCompatibilityDescriptor {
|
|
463
|
-
return {
|
|
464
|
-
contractId: RUNTIME_COMPATIBILITY_CONTRACT_ID,
|
|
465
|
-
runtimeContractVersion: RUNTIME_COMPATIBILITY_CONTRACT_VERSION,
|
|
466
|
-
cliJsonContractVersion: CLI_JSON_CONTRACT_VERSION,
|
|
467
|
-
manifestVersion: RUNTIME_COMPATIBILITY_MANIFEST_VERSION,
|
|
468
|
-
admissionContractVersion: ADMISSION_CONTRACT_VERSION,
|
|
469
|
-
component,
|
|
470
|
-
componentVersion,
|
|
471
|
-
minimumPeerVersions: { ...RUNTIME_MINIMUM_PEER_VERSIONS[component] },
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
export interface RuntimePeerCompatibilityIssue {
|
|
476
|
-
component: RuntimeComponent;
|
|
477
|
-
peer: RuntimeComponent;
|
|
478
|
-
required: string;
|
|
479
|
-
actual: string;
|
|
480
|
-
code: 'UNPARSABLE_VERSION' | 'VERSION_BELOW_MINIMUM';
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
export function evaluateRuntimePeerCompatibility(versions: Record<RuntimeComponent, string>): RuntimePeerCompatibilityIssue[] {
|
|
484
|
-
const issues: RuntimePeerCompatibilityIssue[] = [];
|
|
485
|
-
for (const component of ['cli', 'action', 'api'] as const) {
|
|
486
|
-
for (const peer of ['cli', 'action', 'api'] as const) {
|
|
487
|
-
if (peer === component) continue;
|
|
488
|
-
const required = getMinimumCompatiblePeerVersion(component, peer);
|
|
489
|
-
if (!required) continue;
|
|
490
|
-
const actual = versions[peer];
|
|
491
|
-
const compatible = isSemverAtLeast(actual, required);
|
|
492
|
-
if (compatible === null) {
|
|
493
|
-
issues.push({
|
|
494
|
-
component,
|
|
495
|
-
peer,
|
|
496
|
-
required,
|
|
497
|
-
actual,
|
|
498
|
-
code: 'UNPARSABLE_VERSION',
|
|
499
|
-
});
|
|
500
|
-
} else if (!compatible) {
|
|
501
|
-
issues.push({
|
|
502
|
-
component,
|
|
503
|
-
peer,
|
|
504
|
-
required,
|
|
505
|
-
actual,
|
|
506
|
-
code: 'VERSION_BELOW_MINIMUM',
|
|
507
|
-
});
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
return issues;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
export function parseCliPlanJsonPayload(value: unknown, label = 'plan'): CliPlanJsonPayload {
|
|
515
|
-
const record = asRecord(value, label);
|
|
516
|
-
return {
|
|
517
|
-
...record,
|
|
518
|
-
contractVersion: asContractVersion(record),
|
|
519
|
-
success: asBoolean(record, 'success', label),
|
|
520
|
-
cached: asBoolean(record, 'cached', label),
|
|
521
|
-
mode: asString(record, 'mode', label),
|
|
522
|
-
planId: asNullableString(record, 'planId', label),
|
|
523
|
-
sessionId: asNullableString(record, 'sessionId', label),
|
|
524
|
-
timestamp: asString(record, 'timestamp', label),
|
|
525
|
-
message: asString(record, 'message', label),
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
export function parseCliApplyJsonPayload(value: unknown, label = 'apply'): CliApplyJsonPayload {
|
|
530
|
-
const record = asRecord(value, label);
|
|
531
|
-
return {
|
|
532
|
-
...record,
|
|
533
|
-
contractVersion: asContractVersion(record),
|
|
534
|
-
success: asBoolean(record, 'success', label),
|
|
535
|
-
planId: asString(record, 'planId', label),
|
|
536
|
-
filesGenerated: asNumber(record, 'filesGenerated', label),
|
|
537
|
-
files: asArray(record, 'files', label),
|
|
538
|
-
writtenFiles: asArray(record, 'writtenFiles', label),
|
|
539
|
-
message: asString(record, 'message', label),
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
export function parseCliVerifyJsonPayload(value: unknown, label = 'verify'): CliVerifyJsonPayload {
|
|
544
|
-
return parseVerifyOutput(value, label);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
export function parseVerifyOutput(value: unknown, label = 'verify'): VerifyOutput {
|
|
548
|
-
const record = asRecord(value, label);
|
|
549
|
-
const verdictRaw = asString(record, 'verdict', label).trim().toUpperCase();
|
|
550
|
-
if (verdictRaw !== 'PASS' && verdictRaw !== 'WARN' && verdictRaw !== 'FAIL') {
|
|
551
|
-
throw new Error(`${label}: expected verdict:"PASS"|"WARN"|"FAIL"`);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const summaryRecord = asRecord(record.summary, `${label}.summary`);
|
|
555
|
-
const summary: VerifyOutputSummary = {
|
|
556
|
-
totalFilesChanged: asIntegerNumber(summaryRecord, 'totalFilesChanged', `${label}.summary`),
|
|
557
|
-
totalViolations: asIntegerNumber(summaryRecord, 'totalViolations', `${label}.summary`),
|
|
558
|
-
totalWarnings: asIntegerNumber(summaryRecord, 'totalWarnings', `${label}.summary`),
|
|
559
|
-
totalScopeIssues: asIntegerNumber(summaryRecord, 'totalScopeIssues', `${label}.summary`),
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const violations = asArray(record, 'violations', label).map((entry, index) => {
|
|
563
|
-
const item = asRecord(entry, `${label}.violations[${index}]`);
|
|
564
|
-
const severity = asString(item, 'severity', `${label}.violations[${index}]`).trim().toLowerCase();
|
|
565
|
-
if (severity !== 'critical' && severity !== 'high' && severity !== 'warning' && severity !== 'info') {
|
|
566
|
-
throw new Error(
|
|
567
|
-
`${label}.violations[${index}]: expected severity:"critical"|"high"|"warning"|"info"`
|
|
568
|
-
);
|
|
569
|
-
}
|
|
570
|
-
return {
|
|
571
|
-
file: asString(item, 'file', `${label}.violations[${index}]`),
|
|
572
|
-
message: asString(item, 'message', `${label}.violations[${index}]`),
|
|
573
|
-
policy: asString(item, 'policy', `${label}.violations[${index}]`),
|
|
574
|
-
severity,
|
|
575
|
-
} as VerifyOutputViolation;
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
const warnings = asArray(record, 'warnings', label).map((entry, index) => {
|
|
579
|
-
const item = asRecord(entry, `${label}.warnings[${index}]`);
|
|
580
|
-
return {
|
|
581
|
-
file: asString(item, 'file', `${label}.warnings[${index}]`),
|
|
582
|
-
message: asString(item, 'message', `${label}.warnings[${index}]`),
|
|
583
|
-
policy: asString(item, 'policy', `${label}.warnings[${index}]`),
|
|
584
|
-
} as VerifyOutputWarning;
|
|
585
|
-
});
|
|
586
|
-
|
|
587
|
-
const scopeIssues = asArray(record, 'scopeIssues', label).map((entry, index) => {
|
|
588
|
-
const item = asRecord(entry, `${label}.scopeIssues[${index}]`);
|
|
589
|
-
const issue: VerifyOutputScopeIssue = {
|
|
590
|
-
file: asString(item, 'file', `${label}.scopeIssues[${index}]`),
|
|
591
|
-
message: asString(item, 'message', `${label}.scopeIssues[${index}]`),
|
|
592
|
-
};
|
|
593
|
-
const rawPolicy = item.policy;
|
|
594
|
-
if (typeof rawPolicy === 'string' && rawPolicy.length > 0) {
|
|
595
|
-
const allowedPolicies: VerifyScopeIssuePolicy[] = ['forbidden', 'review-required', 'out-of-scope', 'generated-code', 'unscoped'];
|
|
596
|
-
if ((allowedPolicies as string[]).includes(rawPolicy)) {
|
|
597
|
-
issue.policy = rawPolicy as VerifyScopeIssuePolicy;
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
const rawBoundary = item.boundaryType;
|
|
601
|
-
if (typeof rawBoundary === 'string' && rawBoundary.length > 0) {
|
|
602
|
-
const allowedBoundaries: VerifyScopeIssueBoundaryType[] = [
|
|
603
|
-
'sensitive', 'infra', 'ci', 'dependency-manifest', 'service', 'module', 'generated-code', 'unspecified',
|
|
604
|
-
];
|
|
605
|
-
if ((allowedBoundaries as string[]).includes(rawBoundary)) {
|
|
606
|
-
issue.boundaryType = rawBoundary as VerifyScopeIssueBoundaryType;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
const rawImportEdge = item.importEdge;
|
|
610
|
-
if (rawImportEdge && typeof rawImportEdge === 'object' && !Array.isArray(rawImportEdge)) {
|
|
611
|
-
const edgeRecord = rawImportEdge as Record<string, unknown>;
|
|
612
|
-
const allowedEdgeKinds: VerifyImportEdgeKind[] = ['static', 'relative', 'dynamic', 'require', 'side-effect'];
|
|
613
|
-
const allowedEdgeLanguages: VerifyImportEdgeLanguage[] = ['python', 'typescript', 'javascript'];
|
|
614
|
-
const sourceFile = edgeRecord.sourceFile;
|
|
615
|
-
const importTarget = edgeRecord.importTarget;
|
|
616
|
-
const resolvedTargetPath = edgeRecord.resolvedTargetPath;
|
|
617
|
-
const resolvedBoundary = edgeRecord.resolvedBoundary;
|
|
618
|
-
const sourceLine = edgeRecord.sourceLine;
|
|
619
|
-
const edgeKind = edgeRecord.edgeKind;
|
|
620
|
-
const language = edgeRecord.language;
|
|
621
|
-
if (
|
|
622
|
-
typeof sourceFile === 'string'
|
|
623
|
-
&& typeof importTarget === 'string'
|
|
624
|
-
&& typeof resolvedTargetPath === 'string'
|
|
625
|
-
&& typeof resolvedBoundary === 'string'
|
|
626
|
-
&& typeof sourceLine === 'number'
|
|
627
|
-
&& Number.isFinite(sourceLine)
|
|
628
|
-
&& typeof edgeKind === 'string'
|
|
629
|
-
&& typeof language === 'string'
|
|
630
|
-
&& (allowedEdgeKinds as string[]).includes(edgeKind)
|
|
631
|
-
&& (allowedEdgeLanguages as string[]).includes(language)
|
|
632
|
-
) {
|
|
633
|
-
issue.importEdge = {
|
|
634
|
-
sourceFile,
|
|
635
|
-
sourceLine,
|
|
636
|
-
importTarget,
|
|
637
|
-
resolvedTargetPath,
|
|
638
|
-
resolvedBoundary,
|
|
639
|
-
edgeKind: edgeKind as VerifyImportEdgeKind,
|
|
640
|
-
language: language as VerifyImportEdgeLanguage,
|
|
641
|
-
deterministic: true,
|
|
642
|
-
replayStable: true,
|
|
643
|
-
};
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
return issue;
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
const driftScoreRaw = record.driftScore;
|
|
650
|
-
const driftScore =
|
|
651
|
-
driftScoreRaw === undefined
|
|
652
|
-
? undefined
|
|
653
|
-
: (typeof driftScoreRaw === 'number' && Number.isFinite(driftScoreRaw)
|
|
654
|
-
? Math.round(Math.max(0, Math.min(100, driftScoreRaw)))
|
|
655
|
-
: (() => {
|
|
656
|
-
throw new Error(`${label}: expected driftScore:number when present`);
|
|
657
|
-
})());
|
|
658
|
-
|
|
659
|
-
const governanceFindingsRaw = record.governanceFindings;
|
|
660
|
-
if (governanceFindingsRaw !== undefined && !Array.isArray(governanceFindingsRaw)) {
|
|
661
|
-
throw new Error(`${label}: expected governanceFindings:array when present`);
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const governanceVerificationRaw = record.governanceVerification;
|
|
665
|
-
if (
|
|
666
|
-
governanceVerificationRaw !== undefined
|
|
667
|
-
&& (typeof governanceVerificationRaw !== 'object' || governanceVerificationRaw === null || Array.isArray(governanceVerificationRaw))
|
|
668
|
-
) {
|
|
669
|
-
throw new Error(`${label}: expected governanceVerification:object when present`);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
return {
|
|
673
|
-
verdict: verdictRaw,
|
|
674
|
-
summary,
|
|
675
|
-
violations,
|
|
676
|
-
warnings,
|
|
677
|
-
scopeIssues,
|
|
678
|
-
...(typeof driftScore === 'number' ? { driftScore } : {}),
|
|
679
|
-
...(governanceFindingsRaw !== undefined
|
|
680
|
-
? { governanceFindings: governanceFindingsRaw as import('./verification').GovernanceFinding[] }
|
|
681
|
-
: {}),
|
|
682
|
-
...(governanceVerificationRaw !== undefined
|
|
683
|
-
? {
|
|
684
|
-
governanceVerification:
|
|
685
|
-
governanceVerificationRaw as import('./verification').GovernanceVerificationEnvelope,
|
|
686
|
-
}
|
|
687
|
-
: {}),
|
|
688
|
-
};
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
export function parseCliPromptJsonPayload(value: unknown, label = 'prompt'): CliPromptJsonPayload {
|
|
692
|
-
const record = asRecord(value, label);
|
|
693
|
-
return {
|
|
694
|
-
...record,
|
|
695
|
-
contractVersion: asContractVersion(record),
|
|
696
|
-
success: asBoolean(record, 'success', label),
|
|
697
|
-
planId: asNullableString(record, 'planId', label),
|
|
698
|
-
intent: asNullableString(record, 'intent', label),
|
|
699
|
-
prompt: asNullableString(record, 'prompt', label),
|
|
700
|
-
copied: asBoolean(record, 'copied', label),
|
|
701
|
-
outputPath: asNullableString(record, 'outputPath', label),
|
|
702
|
-
message: asString(record, 'message', label),
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
export function parseCliContractImportJsonPayload(
|
|
707
|
-
value: unknown,
|
|
708
|
-
label = 'contract-import'
|
|
709
|
-
): CliContractImportJsonPayload {
|
|
710
|
-
const record = asRecord(value, label);
|
|
711
|
-
const parseModeRaw = record.parseMode;
|
|
712
|
-
let parseMode: 'json' | 'text' | null = null;
|
|
713
|
-
if (parseModeRaw !== undefined && parseModeRaw !== null) {
|
|
714
|
-
const parseModeValue = asString(record, 'parseMode', label);
|
|
715
|
-
if (parseModeValue !== 'json' && parseModeValue !== 'text') {
|
|
716
|
-
throw new Error(`${label}: expected parseMode:"json"|"text"|null`);
|
|
717
|
-
}
|
|
718
|
-
parseMode = parseModeValue;
|
|
719
|
-
}
|
|
720
|
-
const changeContractValue = record.changeContract;
|
|
721
|
-
let changeContract: Record<string, unknown> | null | undefined;
|
|
722
|
-
if (changeContractValue === null) {
|
|
723
|
-
changeContract = null;
|
|
724
|
-
} else if (changeContractValue !== undefined) {
|
|
725
|
-
changeContract = asOptionalRecord(record, 'changeContract', label);
|
|
726
|
-
}
|
|
727
|
-
return {
|
|
728
|
-
...record,
|
|
729
|
-
contractVersion: asContractVersion(record),
|
|
730
|
-
success: asBoolean(record, 'success', label),
|
|
731
|
-
provider: asNullableString(record, 'provider', label),
|
|
732
|
-
planId: asNullableString(record, 'planId', label),
|
|
733
|
-
sessionId: asNullableString(record, 'sessionId', label),
|
|
734
|
-
projectId: asNullableString(record, 'projectId', label),
|
|
735
|
-
parseMode,
|
|
736
|
-
importedFiles: asNumber(record, 'importedFiles', label),
|
|
737
|
-
warnings: asArray(record, 'warnings', label),
|
|
738
|
-
changeContract,
|
|
739
|
-
message: asString(record, 'message', label),
|
|
740
|
-
timestamp: asString(record, 'timestamp', label),
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
|
|
744
|
-
export function parseCliShipJsonPayload(value: unknown, label = 'ship'): CliShipJsonPayload {
|
|
745
|
-
const record = asRecord(value, label);
|
|
746
|
-
return {
|
|
747
|
-
...record,
|
|
748
|
-
contractVersion: asContractVersion(record),
|
|
749
|
-
success: asBoolean(record, 'success', label),
|
|
750
|
-
status: asString(record, 'status', label),
|
|
751
|
-
finalPlanId: asNullableString(record, 'finalPlanId', label),
|
|
752
|
-
audit: asOptionalRecord(record, 'audit', label),
|
|
753
|
-
error: asOptionalRecord(record, 'error', label),
|
|
754
|
-
};
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
export function parseCliShipRunsJsonPayload(value: unknown, label = 'ship-runs'): CliShipRunsJsonPayload {
|
|
758
|
-
const record = asRecord(value, label);
|
|
759
|
-
return {
|
|
760
|
-
...record,
|
|
761
|
-
contractVersion: asContractVersion(record),
|
|
762
|
-
runs: asArray(record, 'runs', label),
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
export function parseCliShipResumeJsonPayload(value: unknown, label = 'ship-resume'): CliShipResumeJsonPayload {
|
|
767
|
-
const record = asRecord(value, label);
|
|
768
|
-
return {
|
|
769
|
-
...record,
|
|
770
|
-
contractVersion: asContractVersion(record),
|
|
771
|
-
success: asBoolean(record, 'success', label),
|
|
772
|
-
status: asString(record, 'status', label),
|
|
773
|
-
error: asOptionalRecord(record, 'error', label),
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
export function parseCliShipAttestationVerifyJsonPayload(
|
|
778
|
-
value: unknown,
|
|
779
|
-
label = 'ship-attestation-verify'
|
|
780
|
-
): CliShipAttestationVerifyJsonPayload {
|
|
781
|
-
const record = asRecord(value, label);
|
|
782
|
-
return {
|
|
783
|
-
...record,
|
|
784
|
-
contractVersion: asContractVersion(record),
|
|
785
|
-
pass: asBoolean(record, 'pass', label),
|
|
786
|
-
message: record.message === undefined ? undefined : asString(record, 'message', label),
|
|
787
|
-
digest: asOptionalRecord(record, 'digest', label),
|
|
788
|
-
};
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
export function parseCliCompatJsonPayload(value: unknown, label = 'compat'): CliCompatJsonPayload {
|
|
792
|
-
const record = asRecord(value, label);
|
|
793
|
-
const component = asString(record, 'component', label);
|
|
794
|
-
if (component !== 'cli') {
|
|
795
|
-
throw new Error(`${label}: expected component:"cli"`);
|
|
796
|
-
}
|
|
797
|
-
return {
|
|
798
|
-
...record,
|
|
799
|
-
contractVersion: asContractVersion(record),
|
|
800
|
-
success: asBoolean(record, 'success', label),
|
|
801
|
-
timestamp: asString(record, 'timestamp', label),
|
|
802
|
-
component: 'cli',
|
|
803
|
-
componentVersion: asString(record, 'componentVersion', label),
|
|
804
|
-
compatibility: parseRuntimeCompatibilityDescriptor(record.compatibility, label),
|
|
805
|
-
};
|
|
806
|
-
}
|