@sublang/slc 0.4.0 → 0.6.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 +100 -131
- package/dist/app.d.ts +20 -20
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +37 -30
- 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 +1 -1
- package/dist/cligent-agent.js +5 -5
- package/dist/compiled-executor.d.ts +14 -6
- package/dist/compiled-executor.d.ts.map +1 -1
- package/dist/compiled-executor.js +133 -31
- package/dist/compiled-executor.js.map +1 -1
- package/dist/config-file.d.ts +10 -6
- package/dist/config-file.d.ts.map +1 -1
- package/dist/config-file.js +23 -10
- package/dist/config-file.js.map +1 -1
- package/dist/config.d.ts +22 -18
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +90 -27
- 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/host-capabilities.d.ts +115 -0
- package/dist/host-capabilities.d.ts.map +1 -0
- package/dist/host-capabilities.js +507 -0
- package/dist/host-capabilities.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- 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 +4 -4
- package/dist/phase-runner.js +9 -9
- 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 +14 -8
- 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 +53 -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 +6 -4
- 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/reviewing-agent.d.ts +18 -0
- package/dist/reviewing-agent.d.ts.map +1 -0
- package/dist/reviewing-agent.js +295 -0
- package/dist/reviewing-agent.js.map +1 -0
- package/dist/runner.d.ts +1 -1
- package/dist/runner.d.ts.map +1 -1
- package/dist/runner.js +195 -80
- package/dist/runner.js.map +1 -1
- package/dist/slc.config.template.yaml +6 -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 +111 -15
- package/dist/verify.d.ts.map +1 -1
- package/dist/verify.js +656 -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,21 @@ 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 { emitFsmCoverageTest, emitFsmIntrospectionTest, emitGearsFsmConformanceTest, emitPromptContractTest, } from './verify.js';
|
|
32
|
+
import { emitFsmCoverageTest, emitFsmIntrospectionTest, emitGearsFsmConformanceTest, emitPromptContractTest, findMachineConfig, loadFsmModule, loadLinkedModuleForVerification, playbookProvenanceForLinkTarget, resolveArtifactSchemaForVerification, } from './verify.js';
|
|
31
33
|
import { VERIFIER_SUPPORT_MODULE, emitVerifierSupport, verifierSupportFiles, } from './verify-support.js';
|
|
32
34
|
/**
|
|
33
35
|
* Parses argv and runs the requested pipeline, phase, or link end-to-end.
|
|
@@ -46,7 +48,7 @@ export async function runSlc(argv, deps) {
|
|
|
46
48
|
switch (invocation.kind) {
|
|
47
49
|
case 'full':
|
|
48
50
|
// The reserved playbook pipeline supplies a default link target
|
|
49
|
-
// (
|
|
51
|
+
// (self-hosting-13): a bare full run becomes a full-link against the
|
|
50
52
|
// installed @sublang/playbook runtime contract module (DR-014).
|
|
51
53
|
if (invocation.pipeline === 'playbook') {
|
|
52
54
|
return await runFullLink({
|
|
@@ -136,7 +138,7 @@ async function runSinglePhase(invocation, deps) {
|
|
|
136
138
|
entry: pipeline.phases[0] === phase,
|
|
137
139
|
});
|
|
138
140
|
if (raw) {
|
|
139
|
-
// A named phase cannot normalize (
|
|
141
|
+
// A named phase cannot normalize (pipeline-37), so a raw entry source has no
|
|
140
142
|
// path into the phase's declared source format here.
|
|
141
143
|
return failure(`source "${invocation.source}" is a raw input; run the full pipeline to normalize it`);
|
|
142
144
|
}
|
|
@@ -207,7 +209,7 @@ async function runFullLink(invocation, deps) {
|
|
|
207
209
|
const normalize = invocation.normalize || raw;
|
|
208
210
|
const artDir = artifactDir(cwd, basename, invocation.pipeline);
|
|
209
211
|
await mkdir(artDir, { recursive: true });
|
|
210
|
-
// Compile chain: the exit artifact becomes the object artifact (
|
|
212
|
+
// Compile chain: the exit artifact becomes the object artifact (pipeline-15).
|
|
211
213
|
const plan = planArtifacts({ phases: pipeline.phases, basename, artDir });
|
|
212
214
|
const compileSteps = buildCompileSteps({
|
|
213
215
|
pipeline,
|
|
@@ -252,6 +254,7 @@ async function runFullLink(invocation, deps) {
|
|
|
252
254
|
plan,
|
|
253
255
|
artDir,
|
|
254
256
|
basename,
|
|
257
|
+
linkTarget: resolve(cwd, invocation.linkTarget),
|
|
255
258
|
};
|
|
256
259
|
return executeSteps(steps, pipeline, deps, {
|
|
257
260
|
incremental: incrementalRun(invocation, artDir, source),
|
|
@@ -270,7 +273,7 @@ async function runFullLink(invocation, deps) {
|
|
|
270
273
|
],
|
|
271
274
|
};
|
|
272
275
|
}
|
|
273
|
-
// Entry-module emission (DR-014,
|
|
276
|
+
// Entry-module emission (DR-014, self-hosting-15): only the playbook
|
|
274
277
|
// pipeline, only with the linked artifact at its canonical path.
|
|
275
278
|
if (verified.ok && entryPath !== null && gearsPlan !== undefined) {
|
|
276
279
|
await guardTarget(entryPath);
|
|
@@ -292,7 +295,7 @@ async function runFullLink(invocation, deps) {
|
|
|
292
295
|
diagnostics: [
|
|
293
296
|
...verified.diagnostics,
|
|
294
297
|
`entry module ${entryPath} has unresolvable relative imports: ` +
|
|
295
|
-
`${missing.join(', ')} (
|
|
298
|
+
`${missing.join(', ')} (verification-18)`,
|
|
296
299
|
],
|
|
297
300
|
};
|
|
298
301
|
}
|
|
@@ -310,11 +313,11 @@ function runCwd(deps) {
|
|
|
310
313
|
* After a reserved-pipeline full run produces a `gears` intermediate and an `fsm`
|
|
311
314
|
* object at their canonical `<basename>.playbook/` locations, emits the
|
|
312
315
|
* artifact-local checker support plus compilation-correctness tests beside
|
|
313
|
-
* them as `slc` output, appending their paths to the outputs (
|
|
314
|
-
*
|
|
316
|
+
* them as `slc` output, appending their paths to the outputs (verification-2,
|
|
317
|
+
* verification-4;
|
|
315
318
|
* [DR-009](../decisions/009-slc-playbook-pipeline-compilation.md)).
|
|
316
319
|
* Non-reserved pipelines, runs without a gears+fsm pair, and runs whose `fsm` was
|
|
317
|
-
* relocated out of that directory by `-o` (
|
|
320
|
+
* relocated out of that directory by `-o` (pipeline-8) are left unchanged, so an
|
|
318
321
|
* emitted test never imports a file that was not written beside it. A test whose
|
|
319
322
|
* emission needs the produced `fsm` imported (the pinned introspection) degrades
|
|
320
323
|
* to a diagnostic when the artifact cannot be loaded, leaving the run outcome
|
|
@@ -329,6 +332,11 @@ async function emitVerification(result, ctx, guardTarget) {
|
|
|
329
332
|
const fsm = ctx.plan.find((artifact) => artifact.phase.target.format === 'fsm');
|
|
330
333
|
if (fsm === undefined)
|
|
331
334
|
throw new Error('verification plan lost its FSM');
|
|
335
|
+
const provenance = ctx.linkTarget === undefined
|
|
336
|
+
? undefined
|
|
337
|
+
: await playbookProvenanceForLinkTarget(ctx.linkTarget);
|
|
338
|
+
const schemaResolution = await resolveVerificationSchema(ctx, provenance);
|
|
339
|
+
const artifactSchema = schemaResolution.artifactSchema;
|
|
332
340
|
const outputs = [...result.outputs];
|
|
333
341
|
const diagnostics = [...result.diagnostics];
|
|
334
342
|
if (guardTarget !== undefined) {
|
|
@@ -344,6 +352,8 @@ async function emitVerification(result, ctx, guardTarget) {
|
|
|
344
352
|
artifactDir: ctx.artDir,
|
|
345
353
|
basename: ctx.basename,
|
|
346
354
|
verifyModule: VERIFIER_SUPPORT_MODULE,
|
|
355
|
+
...(artifactSchema === undefined ? {} : { artifactSchema }),
|
|
356
|
+
schemaFindings: schemaResolution.findings,
|
|
347
357
|
}));
|
|
348
358
|
try {
|
|
349
359
|
if (guardTarget !== undefined) {
|
|
@@ -366,6 +376,8 @@ async function emitVerification(result, ctx, guardTarget) {
|
|
|
366
376
|
artifactDir: ctx.artDir,
|
|
367
377
|
basename: ctx.basename,
|
|
368
378
|
verifyModule: VERIFIER_SUPPORT_MODULE,
|
|
379
|
+
...(artifactSchema === undefined ? {} : { artifactSchema }),
|
|
380
|
+
...(provenance === undefined ? {} : { provenance }),
|
|
369
381
|
});
|
|
370
382
|
outputs.push(promptContract.path);
|
|
371
383
|
diagnostics.push(...promptContract.diagnostics.map((diagnostic) => `verification: ${diagnostic}`));
|
|
@@ -390,6 +402,34 @@ async function emitVerification(result, ctx, guardTarget) {
|
|
|
390
402
|
}
|
|
391
403
|
return { ...result, outputs, diagnostics };
|
|
392
404
|
}
|
|
405
|
+
/** Reconciles every schema witness available from one just-built artifact. */
|
|
406
|
+
async function resolveVerificationSchema(ctx, provenance) {
|
|
407
|
+
const fsmPath = join(ctx.artDir, `${ctx.basename}.fsm.ts`);
|
|
408
|
+
let config;
|
|
409
|
+
try {
|
|
410
|
+
config = findMachineConfig(await loadFsmModule(fsmPath));
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// Broken FSM loading already degrades the generated derivation checks to
|
|
414
|
+
// diagnostics; reviewed provenance remains usable conformance evidence.
|
|
415
|
+
}
|
|
416
|
+
let linked;
|
|
417
|
+
try {
|
|
418
|
+
linked = (await loadLinkedModuleForVerification({
|
|
419
|
+
linkedPath: join(ctx.artDir, `${ctx.basename}.playbook.ts`),
|
|
420
|
+
fsmPath,
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
// A missing module supplies no signal; prompt emission reports an
|
|
425
|
+
// unloadable module through its existing diagnostic.
|
|
426
|
+
}
|
|
427
|
+
return resolveArtifactSchemaForVerification({
|
|
428
|
+
...(provenance === undefined ? {} : { provenance }),
|
|
429
|
+
...(config === undefined ? {} : { config }),
|
|
430
|
+
...(linked === undefined ? {} : { linked }),
|
|
431
|
+
});
|
|
432
|
+
}
|
|
393
433
|
/** Exact deterministic writes attempted by {@link emitVerification}. */
|
|
394
434
|
function verificationHostTargets(ctx) {
|
|
395
435
|
if (!isReservedPipeline(ctx.pipeline))
|
|
@@ -493,15 +533,35 @@ function compileStep(pipeline, phase, source, target) {
|
|
|
493
533
|
}
|
|
494
534
|
/**
|
|
495
535
|
* 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
|
-
*
|
|
536
|
+
* the pin index and stopping at the first failure with its report (phase-execution-9,
|
|
537
|
+
* phase-execution-27). An unparseable pin file fails the run closed before any phase.
|
|
498
538
|
*/
|
|
499
539
|
async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
500
540
|
const complete = opts.complete ?? (async (result) => result);
|
|
501
|
-
const pinInputs = new Set([
|
|
541
|
+
const pinInputs = new Set([
|
|
542
|
+
join(pipeline.dir, PINS_FILE),
|
|
543
|
+
join(pipeline.dir, PIN_INPUTS_FILE),
|
|
544
|
+
]);
|
|
502
545
|
let pinFile;
|
|
503
546
|
try {
|
|
504
547
|
pinFile = (await loadPinFile(pipeline.dir)).file;
|
|
548
|
+
if (pinFile !== undefined) {
|
|
549
|
+
const verdicts = await evaluatePinFile(pipeline.dir, pinFile, {
|
|
550
|
+
observePath: (path) => pinInputs.add(resolve(path)),
|
|
551
|
+
});
|
|
552
|
+
const malformed = Object.entries(verdicts).find(([, verdict]) => verdict.status === 'malformed');
|
|
553
|
+
if (malformed !== undefined) {
|
|
554
|
+
return {
|
|
555
|
+
ok: false,
|
|
556
|
+
outputs: [],
|
|
557
|
+
diagnostics: [
|
|
558
|
+
`pin is malformed: ${malformed[1].status === 'malformed'
|
|
559
|
+
? malformed[1].reason
|
|
560
|
+
: 'invalid pin index'}`,
|
|
561
|
+
],
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
}
|
|
505
565
|
}
|
|
506
566
|
catch (error) {
|
|
507
567
|
if (error instanceof PinError) {
|
|
@@ -509,23 +569,6 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
509
569
|
}
|
|
510
570
|
throw error;
|
|
511
571
|
}
|
|
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
572
|
const definitions = [
|
|
530
573
|
...new Set([
|
|
531
574
|
...chainDefinitions(pipeline),
|
|
@@ -586,7 +629,7 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
586
629
|
const stepDeclared = declaredByStep[index];
|
|
587
630
|
const identity = state === undefined
|
|
588
631
|
? null
|
|
589
|
-
: await stepIdentity(step, state, index, stepDeclared.complete ? stepDeclared
|
|
632
|
+
: await stepIdentity(step, state, index, stepDeclared.complete ? stepDeclared : null);
|
|
590
633
|
const mode = state === undefined
|
|
591
634
|
? { mode: 'ordinary' }
|
|
592
635
|
: await selectStepMode(step, index, target, identity, state);
|
|
@@ -598,7 +641,7 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
598
641
|
}
|
|
599
642
|
let reasons;
|
|
600
643
|
try {
|
|
601
|
-
reasons = await reusePinFailure(step
|
|
644
|
+
reasons = await reusePinFailure(step, pipeline.dir, pinFile);
|
|
602
645
|
}
|
|
603
646
|
catch (error) {
|
|
604
647
|
reasons = [messageOf(error)];
|
|
@@ -627,7 +670,7 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
627
670
|
});
|
|
628
671
|
continue;
|
|
629
672
|
}
|
|
630
|
-
// In-run progress (DR-019,
|
|
673
|
+
// In-run progress (DR-019, cli-32): announce the phase, then report its
|
|
631
674
|
// outcome with the elapsed time.
|
|
632
675
|
const startedAt = Date.now();
|
|
633
676
|
deps.progress?.({ kind: 'phase-start', phase: step.phase, target });
|
|
@@ -639,13 +682,13 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
639
682
|
});
|
|
640
683
|
// Selecting a compiled executor can throw rather than return a verdict —
|
|
641
684
|
// notably an unmapped pinned Playbook provenance, which the host factory
|
|
642
|
-
// rejects (
|
|
685
|
+
// rejects (phase-execution-30). That is the same fail-closed family as a stale pin,
|
|
643
686
|
// so it is reported through the phase-failure path; letting it unwind
|
|
644
687
|
// would strand the phase-start line with no terminal event and drop the
|
|
645
|
-
// phase and target from the report (
|
|
688
|
+
// phase and target from the report (cli-4, cli-32, phase-execution-27).
|
|
646
689
|
let selection;
|
|
647
690
|
try {
|
|
648
|
-
selection = await selectExecutor(step
|
|
691
|
+
selection = await selectExecutor(step, pipeline.dir, pinFile, deps);
|
|
649
692
|
}
|
|
650
693
|
catch (error) {
|
|
651
694
|
selection = { kind: 'fail', reasons: [messageOf(error)] };
|
|
@@ -695,30 +738,35 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
695
738
|
return { ok: false, outputs, diagnostics };
|
|
696
739
|
}
|
|
697
740
|
diagnostics.push(...result.diagnostics);
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
//
|
|
741
|
+
// Settle an agent-chosen `.js`/`.ts` link-object edge from the sibling that
|
|
742
|
+
// currently exists (pipeline-40), then keep rejecting every genuinely
|
|
743
|
+
// unresolved import (verification-18).
|
|
701
744
|
if (step.phase === 'link' &&
|
|
702
745
|
(step.targetExt === '.ts' || step.targetExt === '.js')) {
|
|
703
746
|
let missing;
|
|
704
747
|
try {
|
|
705
|
-
|
|
748
|
+
const linked = step.request.kind === 'link' ? step.request.linked : target;
|
|
749
|
+
const rewrites = await reconcileLinkObjectImportSpecifiers(linked, step.request.kind === 'link' ? step.request.objects : []);
|
|
750
|
+
diagnostics.push(...rewrites.map(({ from, to }) => `linked module ${linked} reconciled link-object import ` +
|
|
751
|
+
`${JSON.stringify(from)} to ${JSON.stringify(to)} (pipeline-40)`));
|
|
752
|
+
missing = await unresolvableRelativeImports(linked);
|
|
706
753
|
}
|
|
707
754
|
catch (error) {
|
|
708
|
-
// A target that cannot
|
|
709
|
-
//
|
|
710
|
-
//
|
|
711
|
-
// every started phase still reaches a terminal event (CLI-32).
|
|
755
|
+
// A target that cannot be reconciled and checked is a dead artifact
|
|
756
|
+
// too. Fail the link here so every started phase still reaches a
|
|
757
|
+
// terminal event (cli-32).
|
|
712
758
|
fail();
|
|
713
|
-
diagnostics.push(`linked module ${target} could not be
|
|
714
|
-
|
|
759
|
+
diagnostics.push(`linked module ${target} could not be settled and checked: ` +
|
|
760
|
+
`${messageOf(error)} — ` +
|
|
761
|
+
'post-link completion and load integrity are mandatory ' +
|
|
762
|
+
'(pipeline-40, verification-18)');
|
|
715
763
|
return { ok: false, outputs, diagnostics };
|
|
716
764
|
}
|
|
717
765
|
if (missing.length > 0) {
|
|
718
766
|
fail();
|
|
719
767
|
diagnostics.push(`linked module ${target} has unresolvable relative imports: ` +
|
|
720
768
|
`${missing.join(', ')} — an emitted module that cannot load ` +
|
|
721
|
-
'fails the link (
|
|
769
|
+
'fails the link (verification-18)');
|
|
722
770
|
return { ok: false, outputs, diagnostics };
|
|
723
771
|
}
|
|
724
772
|
}
|
|
@@ -758,7 +806,7 @@ async function executeSteps(steps, pipeline, deps, opts = {}) {
|
|
|
758
806
|
? { outcome: 'up-to-date' }
|
|
759
807
|
: {}),
|
|
760
808
|
};
|
|
761
|
-
const completed = await complete(selected, guardCompletionTarget);
|
|
809
|
+
const completed = await complete(selected, guardCompletionTarget, pinFile);
|
|
762
810
|
return state === undefined
|
|
763
811
|
? completed
|
|
764
812
|
: publishIncremental(completed, state, steps);
|
|
@@ -832,7 +880,8 @@ async function stepIdentity(step, state, index, declared) {
|
|
|
832
880
|
inputs: [
|
|
833
881
|
hashBytes(chainedInput),
|
|
834
882
|
...rootHashes,
|
|
835
|
-
...
|
|
883
|
+
...declared.declarationHashes,
|
|
884
|
+
...(await Promise.all(declared.paths.map(async (path) => hashBytes(await readFile(path))))),
|
|
836
885
|
],
|
|
837
886
|
};
|
|
838
887
|
}
|
|
@@ -842,7 +891,8 @@ async function stepIdentity(step, state, index, declared) {
|
|
|
842
891
|
inputs.push(framedHash('object', encodeLocator(state.artDir, path), hashBytes(await readFile(path))));
|
|
843
892
|
}
|
|
844
893
|
inputs.push(hashBytes(await readFile(step.request.definitionPath)));
|
|
845
|
-
inputs.push(...
|
|
894
|
+
inputs.push(...declared.declarationHashes);
|
|
895
|
+
inputs.push(...(await Promise.all(declared.paths.map(async (path) => hashBytes(await readFile(path))))));
|
|
846
896
|
const linkTarget = resolve(step.request.linkTarget);
|
|
847
897
|
const targetInfo = await stat(linkTarget);
|
|
848
898
|
let targetKind;
|
|
@@ -872,36 +922,73 @@ async function stepIdentity(step, state, index, declared) {
|
|
|
872
922
|
}
|
|
873
923
|
}
|
|
874
924
|
async function stepDeclaredInputs(step, pipeline, pinFile) {
|
|
875
|
-
const roots =
|
|
876
|
-
|
|
877
|
-
|
|
925
|
+
const roots = [
|
|
926
|
+
{ path: step.request.definitionPath, phase: step.pinKey },
|
|
927
|
+
];
|
|
928
|
+
if (step.request.kind === 'compile') {
|
|
929
|
+
for (const reference of step.request.references ?? []) {
|
|
930
|
+
roots.push({ path: reference });
|
|
931
|
+
}
|
|
932
|
+
}
|
|
878
933
|
return declaredInputPaths(pipeline, pinFile, roots);
|
|
879
934
|
}
|
|
880
935
|
/** Current local semantic-input closure for definitions and references. */
|
|
881
936
|
async function declaredInputPaths(pipeline, pinFile, roots) {
|
|
882
937
|
const boundary = pinFile?.pathBoundary.path ?? '.';
|
|
883
|
-
const base = new Set(roots.map((
|
|
938
|
+
const base = new Set(roots.map((root) => resolve(root.path)));
|
|
884
939
|
const seen = new Set(base);
|
|
885
940
|
const inputs = [];
|
|
941
|
+
const declarationHashes = [];
|
|
886
942
|
let complete = true;
|
|
887
943
|
const normalize = resolve(normalizeDefinitionPath());
|
|
888
944
|
for (const root of roots) {
|
|
889
|
-
const absolute = resolve(root);
|
|
945
|
+
const absolute = resolve(root.path);
|
|
890
946
|
if (!absolute.toLowerCase().endsWith('.md') || absolute === normalize) {
|
|
891
947
|
continue;
|
|
892
948
|
}
|
|
893
949
|
const locator = relative(pipeline.dir, absolute).split(sep).join('/');
|
|
894
|
-
|
|
950
|
+
let closure;
|
|
895
951
|
try {
|
|
896
|
-
await
|
|
952
|
+
closure = await inspectClosureWithDeclaration(pipeline.dir, boundary, locator, root.phase);
|
|
897
953
|
}
|
|
898
954
|
catch {
|
|
899
|
-
//
|
|
900
|
-
//
|
|
901
|
-
// otherwise interpretable phase degrades to Ordinary execution.
|
|
955
|
+
// An unexpected inspection failure still degrades runner-only identity
|
|
956
|
+
// to Ordinary execution; structured PinError outcomes retain safe paths.
|
|
902
957
|
complete = false;
|
|
958
|
+
continue;
|
|
903
959
|
}
|
|
904
|
-
|
|
960
|
+
if (!closure.complete) {
|
|
961
|
+
// An incomplete closure is unusable for identity, but every independently
|
|
962
|
+
// resolved in-boundary member remains protected from output aliasing.
|
|
963
|
+
complete = false;
|
|
964
|
+
for (const path of closure.paths) {
|
|
965
|
+
const absolutePath = resolve(path);
|
|
966
|
+
if (seen.has(absolutePath))
|
|
967
|
+
continue;
|
|
968
|
+
seen.add(absolutePath);
|
|
969
|
+
inputs.push(absolutePath);
|
|
970
|
+
}
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
if (closure.declaration === 'sidecar' && root.phase !== undefined) {
|
|
974
|
+
const members = [...closure.paths]
|
|
975
|
+
.map((path) => resolve(path))
|
|
976
|
+
.filter((path) => path !== absolute)
|
|
977
|
+
.map((path) => ({
|
|
978
|
+
path,
|
|
979
|
+
locator: relative(pipeline.dir, path).split(sep).join('/'),
|
|
980
|
+
}))
|
|
981
|
+
.sort((left, right) => compareUtf8(left.locator, right.locator));
|
|
982
|
+
declarationHashes.push(framedHash('sidecar-closure-v1', root.phase, ...members.map((member) => member.locator)));
|
|
983
|
+
for (const member of members) {
|
|
984
|
+
if (seen.has(member.path))
|
|
985
|
+
continue;
|
|
986
|
+
seen.add(member.path);
|
|
987
|
+
inputs.push(member.path);
|
|
988
|
+
}
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
for (const path of closure.paths) {
|
|
905
992
|
const absolutePath = resolve(path);
|
|
906
993
|
if (seen.has(absolutePath))
|
|
907
994
|
continue;
|
|
@@ -909,7 +996,7 @@ async function declaredInputPaths(pipeline, pinFile, roots) {
|
|
|
909
996
|
inputs.push(absolutePath);
|
|
910
997
|
}
|
|
911
998
|
}
|
|
912
|
-
return { paths: inputs, complete };
|
|
999
|
+
return { paths: inputs, declarationHashes, complete };
|
|
913
1000
|
}
|
|
914
1001
|
async function selectStepMode(step, index, target, identity, state) {
|
|
915
1002
|
if (state.rebuild || state.history === null || identity === null) {
|
|
@@ -960,15 +1047,33 @@ function sameHashes(left, right) {
|
|
|
960
1047
|
return (left.length === right.length &&
|
|
961
1048
|
left.every((hash, index) => hash === right[index]));
|
|
962
1049
|
}
|
|
963
|
-
async function reusePinFailure(
|
|
964
|
-
const record = ownPin(pinFile, pinKey);
|
|
965
|
-
if (pinFile === undefined ||
|
|
1050
|
+
async function reusePinFailure(step, pipelineDir, pinFile) {
|
|
1051
|
+
const record = ownPin(pinFile, step.pinKey);
|
|
1052
|
+
if (pinFile === undefined ||
|
|
1053
|
+
step.pinKey === undefined ||
|
|
1054
|
+
record === undefined) {
|
|
966
1055
|
return null;
|
|
967
1056
|
}
|
|
968
|
-
const verdict = await evaluatePin(pipelineDir, pinFile, pinKey, record);
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1057
|
+
const verdict = await evaluatePin(pipelineDir, pinFile, step.pinKey, record);
|
|
1058
|
+
if (verdict.status !== 'current') {
|
|
1059
|
+
return [`pin is ${verdict.status}: ${verdict.reason}`];
|
|
1060
|
+
}
|
|
1061
|
+
const definitionIssue = await currentPinDefinitionIssue(step, pipelineDir, pinFile, record);
|
|
1062
|
+
return definitionIssue === null ? null : [definitionIssue];
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Keeps external-definition pinning dormant until pipeline discovery selects
|
|
1066
|
+
* the same physical definition (phase-execution-27; DR-026).
|
|
1067
|
+
*/
|
|
1068
|
+
async function currentPinDefinitionIssue(step, pipelineDir, pinFile, record) {
|
|
1069
|
+
const recorded = resolvePinPath(pipelineDir, pinFile.pathBoundary.path, record.definition.path, 'definition');
|
|
1070
|
+
const selected = resolve(step.request.definitionPath);
|
|
1071
|
+
if (await pathsAlias(recorded, selected))
|
|
1072
|
+
return null;
|
|
1073
|
+
const selectedLocator = relative(pipelineDir, selected).split(sep).join('/');
|
|
1074
|
+
return (`pin-recorded definition "${record.definition.path}" does not match ` +
|
|
1075
|
+
`selected pipeline definition "${selectedLocator}"; ` +
|
|
1076
|
+
'external-definition execution remains disabled until pipeline resolution selects the same physical definition');
|
|
972
1077
|
}
|
|
973
1078
|
/**
|
|
974
1079
|
* Removes any complete snapshot that could otherwise bless this target after
|
|
@@ -1051,29 +1156,39 @@ function historyDiagnostic(result, reason) {
|
|
|
1051
1156
|
};
|
|
1052
1157
|
}
|
|
1053
1158
|
/**
|
|
1054
|
-
* Selects a phase's execution strategy from the pin index (
|
|
1159
|
+
* Selects a phase's execution strategy from the pin index (phase-execution-27; DR-005,
|
|
1055
1160
|
* DR-007): a phase with no pin interprets, a current pin runs its compiled
|
|
1056
1161
|
* artifact, and a stale or malformed pin fails closed and is never silently
|
|
1057
1162
|
* interpreted.
|
|
1058
1163
|
*/
|
|
1059
|
-
async function selectExecutor(
|
|
1060
|
-
const record = ownPin(pinFile, pinKey);
|
|
1061
|
-
if (pinFile === undefined ||
|
|
1164
|
+
async function selectExecutor(step, pipelineDir, pinFile, deps) {
|
|
1165
|
+
const record = ownPin(pinFile, step.pinKey);
|
|
1166
|
+
if (pinFile === undefined ||
|
|
1167
|
+
step.pinKey === undefined ||
|
|
1168
|
+
record === undefined) {
|
|
1062
1169
|
return { kind: 'run', executor: deps.executor };
|
|
1063
1170
|
}
|
|
1064
|
-
const verdict = await evaluatePin(pipelineDir, pinFile, pinKey, record);
|
|
1171
|
+
const verdict = await evaluatePin(pipelineDir, pinFile, step.pinKey, record);
|
|
1065
1172
|
if (verdict.status === 'current') {
|
|
1173
|
+
const definitionIssue = await currentPinDefinitionIssue(step, pipelineDir, pinFile, record);
|
|
1174
|
+
if (definitionIssue !== null) {
|
|
1175
|
+
return { kind: 'fail', reasons: [definitionIssue] };
|
|
1176
|
+
}
|
|
1066
1177
|
if (deps.compiled === undefined) {
|
|
1067
1178
|
return {
|
|
1068
1179
|
kind: 'fail',
|
|
1069
1180
|
reasons: [
|
|
1070
|
-
`phase "${phase}" is pinned to a compiled artifact, but this host has no compiled executor configured`,
|
|
1181
|
+
`phase "${step.phase}" is pinned to a compiled artifact, but this host has no compiled executor configured`,
|
|
1071
1182
|
],
|
|
1072
1183
|
};
|
|
1073
1184
|
}
|
|
1074
1185
|
return {
|
|
1075
1186
|
kind: 'run',
|
|
1076
|
-
executor: deps.compiled({
|
|
1187
|
+
executor: deps.compiled({
|
|
1188
|
+
phase: step.pinKey,
|
|
1189
|
+
pipelineDir,
|
|
1190
|
+
record,
|
|
1191
|
+
}),
|
|
1077
1192
|
};
|
|
1078
1193
|
}
|
|
1079
1194
|
return {
|
|
@@ -1103,7 +1218,7 @@ async function requireLink(pipeline, reference) {
|
|
|
1103
1218
|
throw new Error(`pipeline "${reference}" has no link phase`);
|
|
1104
1219
|
}
|
|
1105
1220
|
// `loadLinkFile` relaxes the ## Link Targets requirement intrinsically for
|
|
1106
|
-
// Playbook's `playbook` linked format (DR-002, DR-009,
|
|
1221
|
+
// Playbook's `playbook` linked format (DR-002, DR-009, pipeline-11), so the
|
|
1107
1222
|
// requirement does not depend on how the pipeline reference resolved.
|
|
1108
1223
|
return loadLinkFile(pipeline.linkFile);
|
|
1109
1224
|
}
|