@sublang/slc 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -5
- package/dist/app.d.ts +20 -20
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +44 -31
- package/dist/app.js.map +1 -1
- package/dist/artifacts.d.ts +6 -6
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +9 -9
- package/dist/artifacts.js.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cligent-agent.d.ts +9 -2
- package/dist/cligent-agent.d.ts.map +1 -1
- package/dist/cligent-agent.js +6 -5
- package/dist/cligent-agent.js.map +1 -1
- package/dist/compiled-execution.d.ts +44 -0
- package/dist/compiled-execution.d.ts.map +1 -0
- package/dist/compiled-execution.js +184 -0
- package/dist/compiled-execution.js.map +1 -0
- package/dist/compiled-executor.d.ts +16 -6
- package/dist/compiled-executor.d.ts.map +1 -1
- package/dist/compiled-executor.js +182 -35
- package/dist/compiled-executor.js.map +1 -1
- package/dist/config-file.d.ts +9 -6
- package/dist/config-file.d.ts.map +1 -1
- package/dist/config-file.js +27 -9
- package/dist/config-file.js.map +1 -1
- package/dist/config.d.ts +77 -24
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +128 -52
- package/dist/config.js.map +1 -1
- package/dist/emitted-imports.d.ts +14 -0
- package/dist/emitted-imports.d.ts.map +1 -1
- package/dist/emitted-imports.js +63 -7
- package/dist/emitted-imports.js.map +1 -1
- package/dist/entry-module.d.ts +5 -3
- package/dist/entry-module.d.ts.map +1 -1
- package/dist/entry-module.js +41 -13
- package/dist/entry-module.js.map +1 -1
- package/dist/execution.d.ts +10 -10
- package/dist/execution.d.ts.map +1 -1
- package/dist/execution.js +13 -13
- package/dist/execution.js.map +1 -1
- package/dist/hash.js +2 -2
- package/dist/interpreter.d.ts +2 -2
- package/dist/interpreter.d.ts.map +1 -1
- package/dist/interpreter.js +7 -7
- package/dist/invocation.d.ts +6 -6
- package/dist/invocation.d.ts.map +1 -1
- package/dist/invocation.js +1 -1
- package/dist/link.d.ts +4 -4
- package/dist/link.d.ts.map +1 -1
- package/dist/link.js +11 -11
- package/dist/link.js.map +1 -1
- package/dist/phase-runner.d.ts +13 -6
- package/dist/phase-runner.d.ts.map +1 -1
- package/dist/phase-runner.js +12 -10
- package/dist/phase-runner.js.map +1 -1
- package/dist/phase.d.ts +4 -4
- package/dist/phase.d.ts.map +1 -1
- package/dist/phase.js +7 -7
- package/dist/phase.js.map +1 -1
- package/dist/pin-closure.d.ts +35 -7
- package/dist/pin-closure.d.ts.map +1 -1
- package/dist/pin-closure.js +135 -21
- package/dist/pin-closure.js.map +1 -1
- package/dist/pin-currency.d.ts +9 -4
- package/dist/pin-currency.d.ts.map +1 -1
- package/dist/pin-currency.js +57 -20
- package/dist/pin-currency.js.map +1 -1
- package/dist/pin-generate.d.ts +7 -7
- package/dist/pin-generate.d.ts.map +1 -1
- package/dist/pin-generate.js +24 -10
- package/dist/pin-generate.js.map +1 -1
- package/dist/pin-inputs.d.ts +41 -0
- package/dist/pin-inputs.d.ts.map +1 -0
- package/dist/pin-inputs.js +179 -0
- package/dist/pin-inputs.js.map +1 -0
- package/dist/pin-paths.d.ts +1 -1
- package/dist/pin-paths.js +4 -4
- package/dist/pin-paths.js.map +1 -1
- package/dist/pins.d.ts +4 -4
- package/dist/pins.d.ts.map +1 -1
- package/dist/pins.js +5 -5
- package/dist/pins.js.map +1 -1
- package/dist/pipeline.d.ts +5 -5
- package/dist/pipeline.js +10 -10
- package/dist/playbook-contract.d.ts +49 -5
- package/dist/playbook-contract.d.ts.map +1 -1
- package/dist/playbook-contract.js +70 -21
- package/dist/playbook-contract.js.map +1 -1
- package/dist/playbook-ports.d.ts +9 -9
- package/dist/playbook-ports.d.ts.map +1 -1
- package/dist/playbook-ports.js +3 -3
- package/dist/playbook-ports.js.map +1 -1
- package/dist/progress.d.ts +8 -9
- package/dist/progress.d.ts.map +1 -1
- package/dist/progress.js +3 -3
- package/dist/progress.js.map +1 -1
- package/dist/resolver.d.ts +9 -9
- package/dist/resolver.d.ts.map +1 -1
- package/dist/resolver.js +12 -12
- package/dist/resolver.js.map +1 -1
- package/dist/runner.d.ts +2 -2
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +218 -81
- package/dist/runner.js.map +1 -1
- package/dist/runtime-contract.d.ts +17 -0
- package/dist/runtime-contract.d.ts.map +1 -0
- package/dist/runtime-contract.js +114 -0
- package/dist/runtime-contract.js.map +1 -0
- package/dist/slc.config.template.yaml +7 -2
- package/dist/verify-coverage.d.ts +4 -3
- package/dist/verify-coverage.d.ts.map +1 -1
- package/dist/verify-coverage.js +509 -71
- package/dist/verify-coverage.js.map +1 -1
- package/dist/verify.d.ts +153 -15
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +707 -59
- package/dist/verify.js.map +1 -1
- package/package.json +3 -3
package/dist/runner.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* pipeline, computes artifact paths, then runs each phase through `runPhase`,
|
|
8
8
|
* stopping at the first failure with its report. The resolver and executor are
|
|
9
9
|
* injected so a host wires the real pipeline resolution and Cligent agent while
|
|
10
|
-
* tests supply fakes. See specs/
|
|
10
|
+
* tests supply fakes. See specs/packages/pipeline.md and specs/packages/phase-execution.md.
|
|
11
11
|
*/
|
|
12
12
|
import { mkdir, readFile, stat } from 'node:fs/promises';
|
|
13
13
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
@@ -15,19 +15,22 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
15
|
import { artifactDir, planArtifacts, parseSource } from './artifacts.js';
|
|
16
16
|
import { encodeLocator, invalidateBuildHistory, loadBuildHistory, recordBuild, verifiedInput, } from './build-history.js';
|
|
17
17
|
import { emitEntryModule } from './entry-module.js';
|
|
18
|
-
import { unresolvableRelativeImports } from './emitted-imports.js';
|
|
18
|
+
import { reconcileLinkObjectImportSpecifiers, unresolvableRelativeImports, } from './emitted-imports.js';
|
|
19
19
|
import { messageOf } from './errors.js';
|
|
20
20
|
import { assertSafeTarget, formatFailureReport, pathsAlias, runPhase, } from './execution.js';
|
|
21
|
-
import { hashBytes, isHash } from './hash.js';
|
|
21
|
+
import { compareUtf8, hashBytes, isHash } from './hash.js';
|
|
22
22
|
import { parseInvocation } from './invocation.js';
|
|
23
23
|
import { unifiedLineDiff } from './line-diff.js';
|
|
24
24
|
import { linkedArtifactPath, loadLinkFile } from './link.js';
|
|
25
|
-
import {
|
|
25
|
+
import { inspectClosureWithDeclaration, } from './pin-closure.js';
|
|
26
|
+
import { PIN_INPUTS_FILE } from './pin-inputs.js';
|
|
27
|
+
import { resolvePinPath } from './pin-paths.js';
|
|
26
28
|
import { evaluatePin, evaluatePinFile, hashTree } from './pin-currency.js';
|
|
27
29
|
import { PINS_FILE, PinError, loadPinFile, } from './pins.js';
|
|
28
30
|
import { loadPipeline, resolvePipeline, } from './pipeline.js';
|
|
29
31
|
import { defaultPlaybookLinkTarget, isReservedPipeline } from './resolver.js';
|
|
30
|
-
import {
|
|
32
|
+
import { readPlaybookRuntimeDeclaration, } from './runtime-contract.js';
|
|
33
|
+
import { artifactSchemaForPlaybookProvenance, emitFsmCoverageTest, emitFsmIntrospectionTest, emitGearsFsmConformanceTest, emitPromptContractTest, findMachineConfig, loadFsmModule, loadLinkedModuleForVerification, playbookProvenanceForLinkTarget, resolveArtifactSchemaForVerification, } from './verify.js';
|
|
31
34
|
import { VERIFIER_SUPPORT_MODULE, emitVerifierSupport, verifierSupportFiles, } from './verify-support.js';
|
|
32
35
|
/**
|
|
33
36
|
* Parses argv and runs the requested pipeline, phase, or link end-to-end.
|
|
@@ -46,7 +49,7 @@ export async function runSlc(argv, deps) {
|
|
|
46
49
|
switch (invocation.kind) {
|
|
47
50
|
case 'full':
|
|
48
51
|
// The reserved playbook pipeline supplies a default link target
|
|
49
|
-
// (
|
|
52
|
+
// (self-hosting-13): a bare full run becomes a full-link against the
|
|
50
53
|
// installed @sublang/playbook runtime contract module (DR-014).
|
|
51
54
|
if (invocation.pipeline === 'playbook') {
|
|
52
55
|
return await runFullLink({
|
|
@@ -136,7 +139,7 @@ async function runSinglePhase(invocation, deps) {
|
|
|
136
139
|
entry: pipeline.phases[0] === phase,
|
|
137
140
|
});
|
|
138
141
|
if (raw) {
|
|
139
|
-
// A named phase cannot normalize (
|
|
142
|
+
// A named phase cannot normalize (pipeline-37), so a raw entry source has no
|
|
140
143
|
// path into the phase's declared source format here.
|
|
141
144
|
return failure(`source "${invocation.source}" is a raw input; run the full pipeline to normalize it`);
|
|
142
145
|
}
|
|
@@ -207,7 +210,7 @@ async function runFullLink(invocation, deps) {
|
|
|
207
210
|
const normalize = invocation.normalize || raw;
|
|
208
211
|
const artDir = artifactDir(cwd, basename, invocation.pipeline);
|
|
209
212
|
await mkdir(artDir, { recursive: true });
|
|
210
|
-
// Compile chain: the exit artifact becomes the object artifact (
|
|
213
|
+
// Compile chain: the exit artifact becomes the object artifact (pipeline-15).
|
|
211
214
|
const plan = planArtifacts({ phases: pipeline.phases, basename, artDir });
|
|
212
215
|
const compileSteps = buildCompileSteps({
|
|
213
216
|
pipeline,
|
|
@@ -252,6 +255,7 @@ async function runFullLink(invocation, deps) {
|
|
|
252
255
|
plan,
|
|
253
256
|
artDir,
|
|
254
257
|
basename,
|
|
258
|
+
linkTarget: resolve(cwd, invocation.linkTarget),
|
|
255
259
|
};
|
|
256
260
|
return executeSteps(steps, pipeline, deps, {
|
|
257
261
|
incremental: incrementalRun(invocation, artDir, source),
|
|
@@ -270,7 +274,7 @@ async function runFullLink(invocation, deps) {
|
|
|
270
274
|
],
|
|
271
275
|
};
|
|
272
276
|
}
|
|
273
|
-
// Entry-module emission (DR-014,
|
|
277
|
+
// Entry-module emission (DR-014, self-hosting-15): only the playbook
|
|
274
278
|
// pipeline, only with the linked artifact at its canonical path.
|
|
275
279
|
if (verified.ok && entryPath !== null && gearsPlan !== undefined) {
|
|
276
280
|
await guardTarget(entryPath);
|
|
@@ -292,7 +296,7 @@ async function runFullLink(invocation, deps) {
|
|
|
292
296
|
diagnostics: [
|
|
293
297
|
...verified.diagnostics,
|
|
294
298
|
`entry module ${entryPath} has unresolvable relative imports: ` +
|
|
295
|
-
`${missing.join(', ')} (
|
|
299
|
+
`${missing.join(', ')} (verification-18)`,
|
|
296
300
|
],
|
|
297
301
|
};
|
|
298
302
|
}
|
|
@@ -310,11 +314,11 @@ function runCwd(deps) {
|
|
|
310
314
|
* After a reserved-pipeline full run produces a `gears` intermediate and an `fsm`
|
|
311
315
|
* object at their canonical `<basename>.playbook/` locations, emits the
|
|
312
316
|
* artifact-local checker support plus compilation-correctness tests beside
|
|
313
|
-
* them as `slc` output, appending their paths to the outputs (
|
|
314
|
-
*
|
|
317
|
+
* them as `slc` output, appending their paths to the outputs (verification-2,
|
|
318
|
+
* verification-4;
|
|
315
319
|
* [DR-009](../decisions/009-slc-playbook-pipeline-compilation.md)).
|
|
316
320
|
* Non-reserved pipelines, runs without a gears+fsm pair, and runs whose `fsm` was
|
|
317
|
-
* relocated out of that directory by `-o` (
|
|
321
|
+
* relocated out of that directory by `-o` (pipeline-8) are left unchanged, so an
|
|
318
322
|
* emitted test never imports a file that was not written beside it. A test whose
|
|
319
323
|
* emission needs the produced `fsm` imported (the pinned introspection) degrades
|
|
320
324
|
* to a diagnostic when the artifact cannot be loaded, leaving the run outcome
|
|
@@ -329,6 +333,22 @@ async function emitVerification(result, ctx, guardTarget) {
|
|
|
329
333
|
const fsm = ctx.plan.find((artifact) => artifact.phase.target.format === 'fsm');
|
|
330
334
|
if (fsm === undefined)
|
|
331
335
|
throw new Error('verification plan lost its FSM');
|
|
336
|
+
const provenance = ctx.linkTarget === undefined
|
|
337
|
+
? undefined
|
|
338
|
+
: await playbookProvenanceForLinkTarget(ctx.linkTarget);
|
|
339
|
+
// A provenance outside the exact historical map is schema evidence only
|
|
340
|
+
// through the installed engine's own declaration (DR-028); an unreadable
|
|
341
|
+
// engine simply supplies no signal, and the emission reports the gap.
|
|
342
|
+
const runtimeDeclaration = ctx.linkTarget === undefined ||
|
|
343
|
+
artifactSchemaForPlaybookProvenance(provenance) !== undefined
|
|
344
|
+
? undefined
|
|
345
|
+
: await linkTargetRuntimeDeclaration(ctx.linkTarget);
|
|
346
|
+
const evidence = {
|
|
347
|
+
...(provenance === undefined ? {} : { provenance }),
|
|
348
|
+
...(runtimeDeclaration === undefined ? {} : { runtimeDeclaration }),
|
|
349
|
+
};
|
|
350
|
+
const schemaResolution = await resolveVerificationSchema(ctx, evidence);
|
|
351
|
+
const artifactSchema = schemaResolution.artifactSchema;
|
|
332
352
|
const outputs = [...result.outputs];
|
|
333
353
|
const diagnostics = [...result.diagnostics];
|
|
334
354
|
if (guardTarget !== undefined) {
|
|
@@ -344,6 +364,8 @@ async function emitVerification(result, ctx, guardTarget) {
|
|
|
344
364
|
artifactDir: ctx.artDir,
|
|
345
365
|
basename: ctx.basename,
|
|
346
366
|
verifyModule: VERIFIER_SUPPORT_MODULE,
|
|
367
|
+
...(artifactSchema === undefined ? {} : { artifactSchema }),
|
|
368
|
+
schemaFindings: schemaResolution.findings,
|
|
347
369
|
}));
|
|
348
370
|
try {
|
|
349
371
|
if (guardTarget !== undefined) {
|
|
@@ -366,6 +388,8 @@ async function emitVerification(result, ctx, guardTarget) {
|
|
|
366
388
|
artifactDir: ctx.artDir,
|
|
367
389
|
basename: ctx.basename,
|
|
368
390
|
verifyModule: VERIFIER_SUPPORT_MODULE,
|
|
391
|
+
...(artifactSchema === undefined ? {} : { artifactSchema }),
|
|
392
|
+
...evidence,
|
|
369
393
|
});
|
|
370
394
|
outputs.push(promptContract.path);
|
|
371
395
|
diagnostics.push(...promptContract.diagnostics.map((diagnostic) => `verification: ${diagnostic}`));
|
|
@@ -390,6 +414,43 @@ async function emitVerification(result, ctx, guardTarget) {
|
|
|
390
414
|
}
|
|
391
415
|
return { ...result, outputs, diagnostics };
|
|
392
416
|
}
|
|
417
|
+
/** Reads the link target's engine declaration; unreadable is no evidence. */
|
|
418
|
+
async function linkTargetRuntimeDeclaration(linkTarget) {
|
|
419
|
+
try {
|
|
420
|
+
return await readPlaybookRuntimeDeclaration(linkTarget);
|
|
421
|
+
}
|
|
422
|
+
catch {
|
|
423
|
+
return undefined;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/** Reconciles every schema witness available from one just-built artifact. */
|
|
427
|
+
async function resolveVerificationSchema(ctx, evidence) {
|
|
428
|
+
const fsmPath = join(ctx.artDir, `${ctx.basename}.fsm.ts`);
|
|
429
|
+
let config;
|
|
430
|
+
try {
|
|
431
|
+
config = findMachineConfig(await loadFsmModule(fsmPath));
|
|
432
|
+
}
|
|
433
|
+
catch {
|
|
434
|
+
// Broken FSM loading already degrades the generated derivation checks to
|
|
435
|
+
// diagnostics; reviewed provenance remains usable conformance evidence.
|
|
436
|
+
}
|
|
437
|
+
let linked;
|
|
438
|
+
try {
|
|
439
|
+
linked = (await loadLinkedModuleForVerification({
|
|
440
|
+
linkedPath: join(ctx.artDir, `${ctx.basename}.playbook.ts`),
|
|
441
|
+
fsmPath,
|
|
442
|
+
}));
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// A missing module supplies no signal; prompt emission reports an
|
|
446
|
+
// unloadable module through its existing diagnostic.
|
|
447
|
+
}
|
|
448
|
+
return resolveArtifactSchemaForVerification({
|
|
449
|
+
...evidence,
|
|
450
|
+
...(config === undefined ? {} : { config }),
|
|
451
|
+
...(linked === undefined ? {} : { linked }),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
393
454
|
/** Exact deterministic writes attempted by {@link emitVerification}. */
|
|
394
455
|
function verificationHostTargets(ctx) {
|
|
395
456
|
if (!isReservedPipeline(ctx.pipeline))
|
|
@@ -493,15 +554,35 @@ function compileStep(pipeline, phase, source, target) {
|
|
|
493
554
|
}
|
|
494
555
|
/**
|
|
495
556
|
* Runs steps in order, selecting interpreted or compiled execution per phase from
|
|
496
|
-
* the pin index and stopping at the first failure with its report (
|
|
497
|
-
*
|
|
557
|
+
* the pin index and stopping at the first failure with its report (phase-execution-9,
|
|
558
|
+
* phase-execution-27). An unparseable pin file fails the run closed before any phase.
|
|
498
559
|
*/
|
|
499
560
|
async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
500
561
|
const complete = opts.complete ?? (async (result) => result);
|
|
501
|
-
const pinInputs = new Set([
|
|
562
|
+
const pinInputs = new Set([
|
|
563
|
+
join(pipeline.dir, PINS_FILE),
|
|
564
|
+
join(pipeline.dir, PIN_INPUTS_FILE),
|
|
565
|
+
]);
|
|
502
566
|
let pinFile;
|
|
503
567
|
try {
|
|
504
568
|
pinFile = (await loadPinFile(pipeline.dir)).file;
|
|
569
|
+
if (pinFile !== undefined) {
|
|
570
|
+
const verdicts = await evaluatePinFile(pipeline.dir, pinFile, {
|
|
571
|
+
observePath: (path) => pinInputs.add(resolve(path)),
|
|
572
|
+
});
|
|
573
|
+
const malformed = Object.entries(verdicts).find(([, verdict]) => verdict.status === 'malformed');
|
|
574
|
+
if (malformed !== undefined) {
|
|
575
|
+
return {
|
|
576
|
+
ok: false,
|
|
577
|
+
outputs: [],
|
|
578
|
+
diagnostics: [
|
|
579
|
+
`pin is malformed: ${malformed[1].status === 'malformed'
|
|
580
|
+
? malformed[1].reason
|
|
581
|
+
: 'invalid pin index'}`,
|
|
582
|
+
],
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
}
|
|
505
586
|
}
|
|
506
587
|
catch (error) {
|
|
507
588
|
if (error instanceof PinError) {
|
|
@@ -509,23 +590,6 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
509
590
|
}
|
|
510
591
|
throw error;
|
|
511
592
|
}
|
|
512
|
-
if (pinFile !== undefined) {
|
|
513
|
-
const verdicts = await evaluatePinFile(pipeline.dir, pinFile, {
|
|
514
|
-
observePath: (path) => pinInputs.add(resolve(path)),
|
|
515
|
-
});
|
|
516
|
-
const malformed = Object.entries(verdicts).find(([, verdict]) => verdict.status === 'malformed');
|
|
517
|
-
if (malformed !== undefined) {
|
|
518
|
-
return {
|
|
519
|
-
ok: false,
|
|
520
|
-
outputs: [],
|
|
521
|
-
diagnostics: [
|
|
522
|
-
`pin is malformed: ${malformed[1].status === 'malformed'
|
|
523
|
-
? malformed[1].reason
|
|
524
|
-
: 'invalid pin index'}`,
|
|
525
|
-
],
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
593
|
const definitions = [
|
|
530
594
|
...new Set([
|
|
531
595
|
...chainDefinitions(pipeline),
|
|
@@ -586,7 +650,7 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
586
650
|
const stepDeclared = declaredByStep[index];
|
|
587
651
|
const identity = state === undefined
|
|
588
652
|
? null
|
|
589
|
-
: await stepIdentity(step, state, index, stepDeclared.complete ? stepDeclared
|
|
653
|
+
: await stepIdentity(step, state, index, stepDeclared.complete ? stepDeclared : null);
|
|
590
654
|
const mode = state === undefined
|
|
591
655
|
? { mode: 'ordinary' }
|
|
592
656
|
: await selectStepMode(step, index, target, identity, state);
|
|
@@ -598,7 +662,7 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
598
662
|
}
|
|
599
663
|
let reasons;
|
|
600
664
|
try {
|
|
601
|
-
reasons = await reusePinFailure(step
|
|
665
|
+
reasons = await reusePinFailure(step, pipeline.dir, pinFile);
|
|
602
666
|
}
|
|
603
667
|
catch (error) {
|
|
604
668
|
reasons = [messageOf(error)];
|
|
@@ -627,7 +691,7 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
627
691
|
});
|
|
628
692
|
continue;
|
|
629
693
|
}
|
|
630
|
-
// In-run progress (DR-019,
|
|
694
|
+
// In-run progress (DR-019, cli-32): announce the phase, then report its
|
|
631
695
|
// outcome with the elapsed time.
|
|
632
696
|
const startedAt = Date.now();
|
|
633
697
|
deps.progress?.({ kind: 'phase-start', phase: step.phase, target });
|
|
@@ -638,14 +702,15 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
638
702
|
elapsedMs: Date.now() - startedAt,
|
|
639
703
|
});
|
|
640
704
|
// Selecting a compiled executor can throw rather than return a verdict —
|
|
641
|
-
// notably
|
|
642
|
-
//
|
|
705
|
+
// notably a pinned link target whose installed engine declares no
|
|
706
|
+
// supported contract, which the host factory rejects (phase-execution-30).
|
|
707
|
+
// That is the same fail-closed family as a stale pin,
|
|
643
708
|
// so it is reported through the phase-failure path; letting it unwind
|
|
644
709
|
// would strand the phase-start line with no terminal event and drop the
|
|
645
|
-
// phase and target from the report (
|
|
710
|
+
// phase and target from the report (cli-4, cli-32, phase-execution-27).
|
|
646
711
|
let selection;
|
|
647
712
|
try {
|
|
648
|
-
selection = await selectExecutor(step
|
|
713
|
+
selection = await selectExecutor(step, pipeline.dir, pinFile, deps);
|
|
649
714
|
}
|
|
650
715
|
catch (error) {
|
|
651
716
|
selection = { kind: 'fail', reasons: [messageOf(error)] };
|
|
@@ -695,30 +760,35 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
695
760
|
return { ok: false, outputs, diagnostics };
|
|
696
761
|
}
|
|
697
762
|
diagnostics.push(...result.diagnostics);
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
//
|
|
763
|
+
// Settle an agent-chosen `.js`/`.ts` link-object edge from the sibling that
|
|
764
|
+
// currently exists (pipeline-40), then keep rejecting every genuinely
|
|
765
|
+
// unresolved import (verification-18).
|
|
701
766
|
if (step.phase === 'link' &&
|
|
702
767
|
(step.targetExt === '.ts' || step.targetExt === '.js')) {
|
|
703
768
|
let missing;
|
|
704
769
|
try {
|
|
705
|
-
|
|
770
|
+
const linked = step.request.kind === 'link' ? step.request.linked : target;
|
|
771
|
+
const rewrites = await reconcileLinkObjectImportSpecifiers(linked, step.request.kind === 'link' ? step.request.objects : []);
|
|
772
|
+
diagnostics.push(...rewrites.map(({ from, to }) => `linked module ${linked} reconciled link-object import ` +
|
|
773
|
+
`${JSON.stringify(from)} to ${JSON.stringify(to)} (pipeline-40)`));
|
|
774
|
+
missing = await unresolvableRelativeImports(linked);
|
|
706
775
|
}
|
|
707
776
|
catch (error) {
|
|
708
|
-
// A target that cannot
|
|
709
|
-
//
|
|
710
|
-
//
|
|
711
|
-
// every started phase still reaches a terminal event (CLI-32).
|
|
777
|
+
// A target that cannot be reconciled and checked is a dead artifact
|
|
778
|
+
// too. Fail the link here so every started phase still reaches a
|
|
779
|
+
// terminal event (cli-32).
|
|
712
780
|
fail();
|
|
713
|
-
diagnostics.push(`linked module ${target} could not be
|
|
714
|
-
|
|
781
|
+
diagnostics.push(`linked module ${target} could not be settled and checked: ` +
|
|
782
|
+
`${messageOf(error)} — ` +
|
|
783
|
+
'post-link completion and load integrity are mandatory ' +
|
|
784
|
+
'(pipeline-40, verification-18)');
|
|
715
785
|
return { ok: false, outputs, diagnostics };
|
|
716
786
|
}
|
|
717
787
|
if (missing.length > 0) {
|
|
718
788
|
fail();
|
|
719
789
|
diagnostics.push(`linked module ${target} has unresolvable relative imports: ` +
|
|
720
790
|
`${missing.join(', ')} — an emitted module that cannot load ` +
|
|
721
|
-
'fails the link (
|
|
791
|
+
'fails the link (verification-18)');
|
|
722
792
|
return { ok: false, outputs, diagnostics };
|
|
723
793
|
}
|
|
724
794
|
}
|
|
@@ -758,7 +828,7 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
758
828
|
? { outcome: 'up-to-date' }
|
|
759
829
|
: {}),
|
|
760
830
|
};
|
|
761
|
-
const completed = await complete(selected, guardCompletionTarget);
|
|
831
|
+
const completed = await complete(selected, guardCompletionTarget, pinFile);
|
|
762
832
|
return state === undefined
|
|
763
833
|
? completed
|
|
764
834
|
: publishIncremental(completed, state, steps);
|
|
@@ -832,7 +902,8 @@ async function stepIdentity(step, state, index, declared) {
|
|
|
832
902
|
inputs: [
|
|
833
903
|
hashBytes(chainedInput),
|
|
834
904
|
...rootHashes,
|
|
835
|
-
...
|
|
905
|
+
...declared.declarationHashes,
|
|
906
|
+
...(await Promise.all(declared.paths.map(async (path) => hashBytes(await readFile(path))))),
|
|
836
907
|
],
|
|
837
908
|
};
|
|
838
909
|
}
|
|
@@ -842,7 +913,8 @@ async function stepIdentity(step, state, index, declared) {
|
|
|
842
913
|
inputs.push(framedHash('object', encodeLocator(state.artDir, path), hashBytes(await readFile(path))));
|
|
843
914
|
}
|
|
844
915
|
inputs.push(hashBytes(await readFile(step.request.definitionPath)));
|
|
845
|
-
inputs.push(...
|
|
916
|
+
inputs.push(...declared.declarationHashes);
|
|
917
|
+
inputs.push(...(await Promise.all(declared.paths.map(async (path) => hashBytes(await readFile(path))))));
|
|
846
918
|
const linkTarget = resolve(step.request.linkTarget);
|
|
847
919
|
const targetInfo = await stat(linkTarget);
|
|
848
920
|
let targetKind;
|
|
@@ -872,36 +944,73 @@ async function stepIdentity(step, state, index, declared) {
|
|
|
872
944
|
}
|
|
873
945
|
}
|
|
874
946
|
async function stepDeclaredInputs(step, pipeline, pinFile) {
|
|
875
|
-
const roots =
|
|
876
|
-
|
|
877
|
-
|
|
947
|
+
const roots = [
|
|
948
|
+
{ path: step.request.definitionPath, phase: step.pinKey },
|
|
949
|
+
];
|
|
950
|
+
if (step.request.kind === 'compile') {
|
|
951
|
+
for (const reference of step.request.references ?? []) {
|
|
952
|
+
roots.push({ path: reference });
|
|
953
|
+
}
|
|
954
|
+
}
|
|
878
955
|
return declaredInputPaths(pipeline, pinFile, roots);
|
|
879
956
|
}
|
|
880
957
|
/** Current local semantic-input closure for definitions and references. */
|
|
881
958
|
async function declaredInputPaths(pipeline, pinFile, roots) {
|
|
882
959
|
const boundary = pinFile?.pathBoundary.path ?? '.';
|
|
883
|
-
const base = new Set(roots.map((
|
|
960
|
+
const base = new Set(roots.map((root) => resolve(root.path)));
|
|
884
961
|
const seen = new Set(base);
|
|
885
962
|
const inputs = [];
|
|
963
|
+
const declarationHashes = [];
|
|
886
964
|
let complete = true;
|
|
887
965
|
const normalize = resolve(normalizeDefinitionPath());
|
|
888
966
|
for (const root of roots) {
|
|
889
|
-
const absolute = resolve(root);
|
|
967
|
+
const absolute = resolve(root.path);
|
|
890
968
|
if (!absolute.toLowerCase().endsWith('.md') || absolute === normalize) {
|
|
891
969
|
continue;
|
|
892
970
|
}
|
|
893
971
|
const locator = relative(pipeline.dir, absolute).split(sep).join('/');
|
|
894
|
-
|
|
972
|
+
let closure;
|
|
895
973
|
try {
|
|
896
|
-
await
|
|
974
|
+
closure = await inspectClosureWithDeclaration(pipeline.dir, boundary, locator, root.phase);
|
|
897
975
|
}
|
|
898
976
|
catch {
|
|
899
|
-
//
|
|
900
|
-
//
|
|
901
|
-
// otherwise interpretable phase degrades to Ordinary execution.
|
|
977
|
+
// An unexpected inspection failure still degrades runner-only identity
|
|
978
|
+
// to Ordinary execution; structured PinError outcomes retain safe paths.
|
|
902
979
|
complete = false;
|
|
980
|
+
continue;
|
|
903
981
|
}
|
|
904
|
-
|
|
982
|
+
if (!closure.complete) {
|
|
983
|
+
// An incomplete closure is unusable for identity, but every independently
|
|
984
|
+
// resolved in-boundary member remains protected from output aliasing.
|
|
985
|
+
complete = false;
|
|
986
|
+
for (const path of closure.paths) {
|
|
987
|
+
const absolutePath = resolve(path);
|
|
988
|
+
if (seen.has(absolutePath))
|
|
989
|
+
continue;
|
|
990
|
+
seen.add(absolutePath);
|
|
991
|
+
inputs.push(absolutePath);
|
|
992
|
+
}
|
|
993
|
+
continue;
|
|
994
|
+
}
|
|
995
|
+
if (closure.declaration === 'sidecar' && root.phase !== undefined) {
|
|
996
|
+
const members = [...closure.paths]
|
|
997
|
+
.map((path) => resolve(path))
|
|
998
|
+
.filter((path) => path !== absolute)
|
|
999
|
+
.map((path) => ({
|
|
1000
|
+
path,
|
|
1001
|
+
locator: relative(pipeline.dir, path).split(sep).join('/'),
|
|
1002
|
+
}))
|
|
1003
|
+
.sort((left, right) => compareUtf8(left.locator, right.locator));
|
|
1004
|
+
declarationHashes.push(framedHash('sidecar-closure-v1', root.phase, ...members.map((member) => member.locator)));
|
|
1005
|
+
for (const member of members) {
|
|
1006
|
+
if (seen.has(member.path))
|
|
1007
|
+
continue;
|
|
1008
|
+
seen.add(member.path);
|
|
1009
|
+
inputs.push(member.path);
|
|
1010
|
+
}
|
|
1011
|
+
continue;
|
|
1012
|
+
}
|
|
1013
|
+
for (const path of closure.paths) {
|
|
905
1014
|
const absolutePath = resolve(path);
|
|
906
1015
|
if (seen.has(absolutePath))
|
|
907
1016
|
continue;
|
|
@@ -909,7 +1018,7 @@ async function declaredInputPaths(pipeline, pinFile, roots) {
|
|
|
909
1018
|
inputs.push(absolutePath);
|
|
910
1019
|
}
|
|
911
1020
|
}
|
|
912
|
-
return { paths: inputs, complete };
|
|
1021
|
+
return { paths: inputs, declarationHashes, complete };
|
|
913
1022
|
}
|
|
914
1023
|
async function selectStepMode(step, index, target, identity, state) {
|
|
915
1024
|
if (state.rebuild || state.history === null || identity === null) {
|
|
@@ -960,15 +1069,33 @@ function sameHashes(left, right) {
|
|
|
960
1069
|
return (left.length === right.length &&
|
|
961
1070
|
left.every((hash, index) => hash === right[index]));
|
|
962
1071
|
}
|
|
963
|
-
async function reusePinFailure(
|
|
964
|
-
const record = ownPin(pinFile, pinKey);
|
|
965
|
-
if (pinFile === undefined ||
|
|
1072
|
+
async function reusePinFailure(step, pipelineDir, pinFile) {
|
|
1073
|
+
const record = ownPin(pinFile, step.pinKey);
|
|
1074
|
+
if (pinFile === undefined ||
|
|
1075
|
+
step.pinKey === undefined ||
|
|
1076
|
+
record === undefined) {
|
|
966
1077
|
return null;
|
|
967
1078
|
}
|
|
968
|
-
const verdict = await evaluatePin(pipelineDir, pinFile, pinKey, record);
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1079
|
+
const verdict = await evaluatePin(pipelineDir, pinFile, step.pinKey, record);
|
|
1080
|
+
if (verdict.status !== 'current') {
|
|
1081
|
+
return [`pin is ${verdict.status}: ${verdict.reason}`];
|
|
1082
|
+
}
|
|
1083
|
+
const definitionIssue = await currentPinDefinitionIssue(step, pipelineDir, pinFile, record);
|
|
1084
|
+
return definitionIssue === null ? null : [definitionIssue];
|
|
1085
|
+
}
|
|
1086
|
+
/**
|
|
1087
|
+
* Keeps external-definition pinning dormant until pipeline discovery selects
|
|
1088
|
+
* the same physical definition (phase-execution-27; DR-026).
|
|
1089
|
+
*/
|
|
1090
|
+
async function currentPinDefinitionIssue(step, pipelineDir, pinFile, record) {
|
|
1091
|
+
const recorded = resolvePinPath(pipelineDir, pinFile.pathBoundary.path, record.definition.path, 'definition');
|
|
1092
|
+
const selected = resolve(step.request.definitionPath);
|
|
1093
|
+
if (await pathsAlias(recorded, selected))
|
|
1094
|
+
return null;
|
|
1095
|
+
const selectedLocator = relative(pipelineDir, selected).split(sep).join('/');
|
|
1096
|
+
return (`pin-recorded definition "${record.definition.path}" does not match ` +
|
|
1097
|
+
`selected pipeline definition "${selectedLocator}"; ` +
|
|
1098
|
+
'external-definition execution remains disabled until pipeline resolution selects the same physical definition');
|
|
972
1099
|
}
|
|
973
1100
|
/**
|
|
974
1101
|
* Removes any complete snapshot that could otherwise bless this target after
|
|
@@ -1051,29 +1178,39 @@ function historyDiagnostic(result, reason) {
|
|
|
1051
1178
|
};
|
|
1052
1179
|
}
|
|
1053
1180
|
/**
|
|
1054
|
-
* Selects a phase's execution strategy from the pin index (
|
|
1181
|
+
* Selects a phase's execution strategy from the pin index (phase-execution-27; DR-005,
|
|
1055
1182
|
* DR-007): a phase with no pin interprets, a current pin runs its compiled
|
|
1056
1183
|
* artifact, and a stale or malformed pin fails closed and is never silently
|
|
1057
1184
|
* interpreted.
|
|
1058
1185
|
*/
|
|
1059
|
-
async function selectExecutor(
|
|
1060
|
-
const record = ownPin(pinFile, pinKey);
|
|
1061
|
-
if (pinFile === undefined ||
|
|
1186
|
+
async function selectExecutor(step, pipelineDir, pinFile, deps) {
|
|
1187
|
+
const record = ownPin(pinFile, step.pinKey);
|
|
1188
|
+
if (pinFile === undefined ||
|
|
1189
|
+
step.pinKey === undefined ||
|
|
1190
|
+
record === undefined) {
|
|
1062
1191
|
return { kind: 'run', executor: deps.executor };
|
|
1063
1192
|
}
|
|
1064
|
-
const verdict = await evaluatePin(pipelineDir, pinFile, pinKey, record);
|
|
1193
|
+
const verdict = await evaluatePin(pipelineDir, pinFile, step.pinKey, record);
|
|
1065
1194
|
if (verdict.status === 'current') {
|
|
1195
|
+
const definitionIssue = await currentPinDefinitionIssue(step, pipelineDir, pinFile, record);
|
|
1196
|
+
if (definitionIssue !== null) {
|
|
1197
|
+
return { kind: 'fail', reasons: [definitionIssue] };
|
|
1198
|
+
}
|
|
1066
1199
|
if (deps.compiled === undefined) {
|
|
1067
1200
|
return {
|
|
1068
1201
|
kind: 'fail',
|
|
1069
1202
|
reasons: [
|
|
1070
|
-
`phase "${phase}" is pinned to a compiled artifact, but this host has no compiled executor configured`,
|
|
1203
|
+
`phase "${step.phase}" is pinned to a compiled artifact, but this host has no compiled executor configured`,
|
|
1071
1204
|
],
|
|
1072
1205
|
};
|
|
1073
1206
|
}
|
|
1074
1207
|
return {
|
|
1075
1208
|
kind: 'run',
|
|
1076
|
-
executor: deps.compiled({
|
|
1209
|
+
executor: await deps.compiled({
|
|
1210
|
+
phase: step.pinKey,
|
|
1211
|
+
pipelineDir,
|
|
1212
|
+
record,
|
|
1213
|
+
}),
|
|
1077
1214
|
};
|
|
1078
1215
|
}
|
|
1079
1216
|
return {
|
|
@@ -1103,7 +1240,7 @@ async function requireLink(pipeline, reference) {
|
|
|
1103
1240
|
throw new Error(`pipeline "${reference}" has no link phase`);
|
|
1104
1241
|
}
|
|
1105
1242
|
// `loadLinkFile` relaxes the ## Link Targets requirement intrinsically for
|
|
1106
|
-
// Playbook's `playbook` linked format (DR-002, DR-009,
|
|
1243
|
+
// Playbook's `playbook` linked format (DR-002, DR-009, pipeline-11), so the
|
|
1107
1244
|
// requirement does not depend on how the pipeline reference resolved.
|
|
1108
1245
|
return loadLinkFile(pipeline.linkFile);
|
|
1109
1246
|
}
|