@voce-engine/cli 0.1.0-rc.2 → 0.1.0-rc.3

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 CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  The `voce` command is the offline-first, explicit-path CLI for VOCE v0.1. It does not load lifecycle scripts, discover global packages, inspect credentials, or call a Provider unless the command explicitly selects the built-in `mock` provider.
4
4
 
5
- > `0.1.0-rc.2` is a release candidate, not production-ready. CLI behavior may change before `0.1.0`.
5
+ > `0.1.0-rc.3` is a release candidate, not production-ready. CLI behavior may change before `0.1.0`.
6
6
 
7
7
  ```bash
8
- npm install --global @voce-engine/cli@0.1.0-rc.2
8
+ npm install --global @voce-engine/cli@0.1.0-rc.3
9
9
  ```
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.1.0-rc.2";
1
+ export declare const CLI_VERSION = "0.1.0-rc.3";
2
2
  export declare function runCli(argv?: string[]): Promise<number>;
package/dist/index.js CHANGED
@@ -2,13 +2,13 @@ import { createHash, randomUUID } from 'node:crypto';
2
2
  import { lstat, mkdir, readdir, readFile, realpath, rename, writeFile } from 'node:fs/promises';
3
3
  import * as path from 'node:path';
4
4
  import * as process from 'node:process';
5
- import { MOCK_IMAGE_PROFILE, MOCK_JPEG_PROFILE, MOCK_LIMITED_REFERENCE_PROFILE, compileEvaluationReport, computeExecutionRunHash, computeCompilationContextHash, computeOntologyInstanceHash, computeSemanticReviewRequestHash, createConstraintWaiver, createHumanAcceptanceDecision, compileConstraints, createScenarioPackRegistry, executeOffline, executeSemanticReview, FixtureReferenceInterpreter, FixtureSemanticReviewer, guardPromptCandidate, renderStaticTraceReport, sha256, traceModelFromExecution, validateStructuralImage, canonicalize, } from '@voce-engine/core';
5
+ import { MOCK_IMAGE_PROFILE, MOCK_JPEG_PROFILE, MOCK_LIMITED_REFERENCE_PROFILE, compileEvaluationReport, computeExecutionRunHash, computeCompilationContextHash, computeOntologyInstanceHash, computeSemanticReviewRequestHash, createConstraintWaiver, createHumanAcceptanceDecision, compileConstraints, compilePromptIR, createReferenceCandidate, planPipeline, planReferences, createScenarioPackRegistry, executeOffline, executeSemanticReview, FixtureReferenceInterpreter, FixtureSemanticReviewer, guardPromptCandidate, renderStaticTraceReport, sha256, traceModelFromExecution, validateStructuralImage, canonicalize, } from '@voce-engine/core';
6
6
  import { FIXTURE_M6_OPAQUE_PNG, fixtureChangeIntent, fixtureM4ConstraintInput, fixtureM5Candidate, fixtureM5ExecutionInput, fixtureM5GuardInput, fixtureM5PromptIR, fixtureM6Artifact, fixtureM6Authorization, fixtureScopePlan, } from '@voce-engine/testkit';
7
7
  import { BUNDLE_MANIFEST_SCHEMA_VERSION } from '@voce-engine/contracts';
8
- export const CLI_VERSION = '0.1.0-rc.2';
8
+ export const CLI_VERSION = '0.1.0-rc.3';
9
9
  const TOOL_ID = '@voce-engine/cli';
10
- const CONTRACTS_VERSION = '0.1.0-rc.2';
11
- const CORE_VERSION = '0.1.0-rc.2';
10
+ const CONTRACTS_VERSION = '0.1.0-rc.3';
11
+ const CORE_VERSION = '0.1.0-rc.3';
12
12
  const SOURCE_SCHEMA = 'voce.scenario-pack-source/v1alpha1';
13
13
  const PACK_SCHEMA = 'voce.scenario-pack/v1alpha1';
14
14
  const HASH = /^sha256:[0-9a-f]{64}$/;
@@ -337,17 +337,93 @@ function probeInput(probe, resolution, caseSpec) {
337
337
  const requestedScopePlan = fixtureScopePlan(['person.identity'], 'ref-01', caseSpec.id, caseSpec.revision);
338
338
  const ontologyBase = { ...base.ontologyInstance, id: `ontology-${probe.id}`, caseId: caseSpec.id, caseRevision: caseSpec.revision, contextHash: context.contextHash, requestedScopePlanHash: requestedScopePlan.planHash, unknownPaths: probe.unknownPaths ?? [], unspecifiedPaths: [], facts: [], conflicts: [], unresolvedItems: [], decisionTrace: [] };
339
339
  const ontologyInstance = { ...ontologyBase, instanceHash: computeOntologyInstanceHash({ ...ontologyBase, instanceHash: '' }) };
340
- const intents = (probe.intents ?? []).map((intent) => ({ ...fixtureChangeIntent(intent.id, intent.operation, intent.targetPath, intent.requestedValue), importance: intent.importance ?? 'required' }));
340
+ const intents = (probe.intents ?? []).map((intent) => ({ ...fixtureChangeIntent(intent.id, intent.operation, intent.targetPath, intent.requestedValue), importance: intent.importance ?? 'required', ...(intent.sourceHintIds === undefined ? {} : { sourceHintIds: [...intent.sourceHintIds].sort(compare) }) }));
341
341
  const waivers = probe.waiverTarget ? [createConstraintWaiver({ schemaVersion: 'voce.constraint-waiver/v1alpha1', id: `waiver-${probe.id}`, caseId: caseSpec.id, caseRevision: caseSpec.revision, contextHash: context.contextHash, targetId: probe.waiverTarget, authority: 'user', decidedBy: 'fixture-reviewer', reasonCode: 'FIXTURE_EXPLICIT_WAIVER', decidedAt: '2026-01-01T00:00:00.000Z' })] : [];
342
342
  return fixtureM4ConstraintInput({ caseId: caseSpec.id, caseRevision: caseSpec.revision, context, contextHash: context.contextHash, requestedScopePlanHash: requestedScopePlan.planHash, ontologyInstance, changeIntents: intents, effectiveScenario: resolution.effectiveScenario, waivers });
343
343
  }
344
- function coreProbeEvidence(probe, resolution, caseSpec) {
344
+ function pathRelated(left, right) { return left === right || left.startsWith(`${right}.`) || right.startsWith(`${left}.`); }
345
+ function referenceProbeCandidates(probe, ir) {
346
+ return (probe.references ?? []).map((reference) => {
347
+ const constraintIds = ir.constraints.filter((constraint) => reference.ontologyScopes.some((scope) => constraint.targetPaths.some((target) => pathRelated(scope, target)))).map((constraint) => constraint.id);
348
+ const goalIds = ir.goals.filter((goal) => typeof goal.targetPath === 'string' && reference.ontologyScopes.some((scope) => pathRelated(scope, goal.targetPath))).map((goal) => goal.id);
349
+ const artifact = { id: reference.assetId ?? reference.id, storeId: 'fixture-store', contentHash: sha256({ fixtureReference: reference.id }), mediaType: 'image/png', byteLength: 64, role: 'reference', resolverId: 'fixture-resolver', availability: 'available', retentionClass: 'fixture', redactionPolicy: 'hash-only' };
350
+ return createReferenceCandidate({ schemaVersion: 'voce.reference-candidate/v1alpha1', id: reference.id, assetId: artifact.id, artifact, contentHash: artifact.contentHash, mediaType: artifact.mediaType, byteLength: artifact.byteLength, role: reference.role, ontologyScopes: reference.ontologyScopes, importance: reference.importance, constraintIds, sourceBindingIds: reference.sourceBindingIds ?? [], goalIds, orderKey: reference.orderKey ?? reference.id });
351
+ });
352
+ }
353
+ function coreProbeEvidence(probe, resolution, caseSpec, profile) {
345
354
  const ir = compileConstraints(probeInput(probe, resolution, caseSpec));
346
- return {
355
+ const evidence = {
347
356
  status: ir.status, constraintHash: ir.deterministicSignature, blockingConflictCount: ir.conflicts.filter((item) => item.blocking).length,
348
357
  conflictCodes: ir.conflicts.map((item) => item.code).sort(compare), reviewRequirementCount: ir.reviewRequirements.length,
349
358
  reviewReasons: ir.reviewRequirements.map((item) => item.reasonCode).sort(compare), waivedWarnings: ir.warnings.filter((item) => item === 'REQUIRED_CONFLICT_WAIVED' || item === 'HARD_CONFLICT_CANNOT_WAIVE').sort(compare),
350
359
  degradationCount: ir.degradedPreferences.length, personIntentCount: (probe.intents ?? []).filter((item) => item.targetPath.startsWith('person.')).length,
360
+ constraintTargetPaths: ir.constraints.flatMap((item) => item.targetPaths).sort(compare),
361
+ constraintSourceIds: [...new Set(ir.constraints.flatMap((item) => item.sourceIds))].sort(compare),
362
+ };
363
+ if (!probe.references?.length)
364
+ return evidence;
365
+ const candidates = referenceProbeCandidates(probe, ir);
366
+ const maximumReferenceCount = probe.maximumReferenceCount ?? profile.maximumReferenceCount;
367
+ const plan = planReferences({ schemaVersion: 'voce.reference-planning-input/v1alpha1', caseId: caseSpec.id, caseRevision: caseSpec.revision, contextHash: ir.contextHash, constraintIR: ir, candidates, dependencies: [], profile, budget: { maximumReferenceCount, usedReferenceCount: 0, byteLengthKnown: true, unknownByteLengthAssetIds: [] } });
368
+ evidence.referenceStatus = plan.status;
369
+ evidence.referencePlanHash = plan.planHash;
370
+ evidence.selectedRoles = plan.ordered.map((item) => item.role).sort(compare);
371
+ evidence.omittedRoles = plan.omitted.map((item) => candidates.find((candidate) => candidate.id === item.candidateId)?.role ?? item.candidateId).sort(compare);
372
+ evidence.blockedRoles = plan.blockedReferences.map((item) => candidates.find((candidate) => candidate.id === item.candidateId)?.role ?? item.candidateId).sort(compare);
373
+ evidence.selectedSourceBindingIds = [...new Set(plan.ordered.flatMap((item) => item.sourceBindingIds))].sort(compare);
374
+ if (ir.status !== 'ok' || plan.status !== 'ok') {
375
+ evidence.promptStatus = 'blocked';
376
+ return evidence;
377
+ }
378
+ const pipelineResult = planPipeline({ schemaVersion: 'voce.pipeline-planning-input/v1alpha1', caseId: caseSpec.id, caseRevision: caseSpec.revision, contextHash: ir.contextHash, outputContract: probeInput(probe, resolution, caseSpec).outputContract, constraintIR: ir, referencePlan: plan, profile });
379
+ if (!pipelineResult.pipelinePlan) {
380
+ evidence.promptStatus = 'blocked';
381
+ return evidence;
382
+ }
383
+ const outputContract = probeInput(probe, resolution, caseSpec).outputContract;
384
+ const prompt = compilePromptIR({ schemaVersion: 'voce.prompt-compilation-input/v1alpha1', caseId: caseSpec.id, caseRevision: caseSpec.revision, context: probeInput(probe, resolution, caseSpec).context, contextHash: ir.contextHash, constraintIR: ir, referencePlan: plan, pipelinePlan: pipelineResult.pipelinePlan, outputContract, targetAdapter: { id: profile.adapterId, version: profile.version, digest: profile.adapterDigest }, targetCapabilityProfile: { id: profile.id, version: profile.version, digest: profile.profileHash } });
385
+ evidence.promptStatus = 'ok';
386
+ evidence.promptHash = prompt.deterministicSignature;
387
+ evidence.promptTargetPaths = ir.constraints.filter((constraint) => prompt.constraintCoverage.some((coverage) => coverage.constraintId === constraint.id && (coverage.sectionIds.length > 0 || coverage.referenceMappingIds.length > 0 || coverage.parameterIds.length > 0))).flatMap((constraint) => constraint.targetPaths).sort(compare);
388
+ evidence.promptReferenceRoles = prompt.referenceMappings.map((mapping) => mapping.role).sort(compare);
389
+ return evidence;
390
+ }
391
+ function scenarioContributions(scenario, category) { return scenario[category].filter(isObject); }
392
+ function contributionArray(contributions, field) { return contributions.flatMap((contribution) => Array.isArray(contribution[field]) ? contribution[field].filter(isObject) : []); }
393
+ function semanticCriteriaForScenario(scenario) {
394
+ const criteria = contributionArray(scenarioContributions(scenario, 'reviewTemplates'), 'criteria').map((criterion) => {
395
+ const id = stringField(criterion, 'id', 'REVIEW_TEMPLATE_INVALID');
396
+ const kind = stringField(criterion, 'kind', 'REVIEW_TEMPLATE_INVALID');
397
+ const importance = criterion.importance;
398
+ if (importance !== 'hard' && importance !== 'required' && importance !== 'preferred')
399
+ throw new CliError('REVIEW_TEMPLATE_INVALID', 'Review criterion importance is invalid.', EXIT.contract);
400
+ if (criterion.targetPath !== undefined && typeof criterion.targetPath !== 'string')
401
+ throw new CliError('REVIEW_TEMPLATE_INVALID', 'Review criterion targetPath is invalid.', EXIT.contract);
402
+ if (criterion.prompt !== undefined && typeof criterion.prompt !== 'string')
403
+ throw new CliError('REVIEW_TEMPLATE_INVALID', 'Review criterion prompt is invalid.', EXIT.contract);
404
+ return { id, kind, importance, ...(criterion.targetPath === undefined ? {} : { targetPath: criterion.targetPath }), ...(criterion.prompt === undefined ? {} : { prompt: criterion.prompt }) };
405
+ });
406
+ const byId = new Map();
407
+ for (const criterion of criteria.sort((left, right) => compare(left.id, right.id))) {
408
+ const prior = byId.get(criterion.id);
409
+ if (prior && canonicalize(prior) !== canonicalize(criterion))
410
+ throw new CliError('REVIEW_TEMPLATE_ID_COLLISION', 'Review criteria with one ID disagree.', EXIT.contract);
411
+ byId.set(criterion.id, criterion);
412
+ }
413
+ return [...byId.values()];
414
+ }
415
+ function scenarioEvidence(scenario) {
416
+ const ontology = contributionArray(scenarioContributions(scenario, 'ontologyVocabulary'), 'paths');
417
+ const scopes = scenarioContributions(scenario, 'interpretationScopes');
418
+ const bindings = contributionArray(scopes, 'bindings');
419
+ const promptPaths = scenarioContributions(scenario, 'promptSections').flatMap((contribution) => Array.isArray(contribution.requiredPaths) ? contribution.requiredPaths.filter((item) => typeof item === 'string') : []);
420
+ const criteria = semanticCriteriaForScenario(scenario);
421
+ return {
422
+ ontologyPaths: ontology.map((item) => String(item.path ?? '')).filter(Boolean).sort(compare),
423
+ bindingPolicies: bindings.map((item) => `${String(item.assetRole ?? '')}|${String(item.relation ?? '')}|${String(item.targetPath ?? '')}|${String(item.priority ?? '')}`).sort(compare),
424
+ promptTargetPaths: [...new Set(promptPaths)].sort(compare),
425
+ reviewCriterionIds: criteria.map((item) => item.id).sort(compare),
426
+ reviewTargetPaths: criteria.map((item) => item.targetPath).filter((item) => typeof item === 'string').sort(compare),
351
427
  };
352
428
  }
353
429
  function referenceEvidence(fixture, resolution, caseSpec) {
@@ -406,13 +482,13 @@ function profileFor(id) {
406
482
  async function loadProfile(filePath) { const object = record(await readJson(path.resolve(filePath), 'PROFILE_FILE_UNSAFE'), 'PROFILE_JSON_INVALID'); return profileFor(stringField(object, 'id', 'PROFILE_INVALID')); }
407
483
  function profilePin(profile) { return { id: profile.id, version: profile.version, digest: profile.profileHash }; }
408
484
  function toolPin(id, version) { return { id, version, digest: sha256({ id, version }) }; }
409
- function defaultCase(doc, packId) {
485
+ function defaultCase(doc, packId, packVersion) {
410
486
  assertKnown(doc, ['schemaVersion', 'id', 'revision', 'fixtureId', 'mode', 'scenario', 'userIntent', 'assets', 'trustedMetadata', 'policies', 'requestedOutput'], 'CASE_UNKNOWN_FIELD');
411
487
  const id = typeof doc.id === 'string' ? doc.id : `${packId}-case`;
412
488
  const revision = typeof doc.revision === 'number' ? doc.revision : 1;
413
489
  const output = isObject(doc.requestedOutput) ? doc.requestedOutput : { artifactKind: 'image', dataType: 'image', mediaTypes: ['image/png'], cardinality: { min: 1, max: 1 }, background: 'opaque', allowAlpha: false };
414
490
  const asset = { id: 'ref-01', storeId: 'fixture-store', contentHash: sha256({ fixture: `${id}:ref-01` }), mediaType: 'image/png', byteLength: 64, role: 'reference', resolverId: 'fixture-resolver', availability: 'available', retentionClass: 'fixture', redactionPolicy: 'hash-only' };
415
- return { schemaVersion: 'voce.case-spec/v1alpha1', id, revision, mode: 'manual', scenario: { root: { packId, versionRange: '0.1.0' }, extensions: [] }, userIntent: typeof doc.userIntent === 'string' ? doc.userIntent : 'Offline fixture case.', assets: [asset], trustedMetadata: [], policies: { schemaVersion: 'voce.case-policies/v1alpha1', observationConfirmation: 'explicit', bindingConfirmation: 'explicit', allowDeclaredDefaults: true }, requestedOutput: output };
491
+ return { schemaVersion: 'voce.case-spec/v1alpha1', id, revision, mode: 'manual', scenario: { root: { packId, versionRange: packVersion }, extensions: [] }, userIntent: typeof doc.userIntent === 'string' ? doc.userIntent : 'Offline fixture case.', assets: [asset], trustedMetadata: [], policies: { schemaVersion: 'voce.case-policies/v1alpha1', observationConfirmation: 'explicit', bindingConfirmation: 'explicit', allowDeclaredDefaults: true }, requestedOutput: output };
416
492
  }
417
493
  async function compileInputs(casePath, scenarioPath, profilePath) {
418
494
  const caseDoc = record(await readJson(path.resolve(casePath), 'CASE_FILE_UNSAFE'), 'CASE_JSON_INVALID');
@@ -422,14 +498,14 @@ async function compileInputs(casePath, scenarioPath, profilePath) {
422
498
  const resolution = registry.resolve({ root: { packId: loaded.descriptor.manifest.packId, versionRange: loaded.descriptor.manifest.version }, extensions: [] });
423
499
  if (resolution.status !== 'resolved')
424
500
  throw new CliError('PACK_RESOLUTION_BLOCKED', 'ScenarioPack resolution is blocked.', EXIT.contract);
425
- const caseSpec = defaultCase(caseDoc, loaded.descriptor.manifest.packId);
501
+ const caseSpec = defaultCase(caseDoc, loaded.descriptor.manifest.packId, loaded.descriptor.manifest.version);
426
502
  const fixtureId = typeof caseDoc.fixtureId === 'string' ? caseDoc.fixtureId : caseSpec.id;
427
503
  const fixture = loaded.fixtures.find((candidate) => candidate.id === fixtureId) ?? loaded.fixtures[0] ?? { id: fixtureId, profileId: MOCK_IMAGE_PROFILE.id };
428
504
  const profile = await loadProfile(profilePath);
429
505
  const prompt = fixtureM5Candidate(fixtureM5PromptIR(profile));
430
506
  const input = fixtureM5ExecutionInput(profile);
431
- const coreProbes = Object.fromEntries((fixture.coreProbes ?? []).sort((left, right) => compare(left.id, right.id)).map((probe) => [probe.id, coreProbeEvidence(probe, resolution, caseSpec)]));
432
- const acceptance = { resolution: { status: resolution.status, selectedCount: resolution.report.selected.length, effectiveScenarioHash: resolution.effectiveScenario.effectiveScenarioHash }, reference: referenceEvidence(fixture, resolution, caseSpec), coreProbes, compile: { constraintHash: input.constraintIR.deterministicSignature, referencePlanHash: input.referencePlan.planHash, pipelinePlanHash: input.pipelinePlan.planHash } };
507
+ const coreProbes = Object.fromEntries((fixture.coreProbes ?? []).sort((left, right) => compare(left.id, right.id)).map((probe) => [probe.id, coreProbeEvidence(probe, resolution, caseSpec, profile)]));
508
+ const acceptance = { resolution: { status: resolution.status, selectedCount: resolution.report.selected.length, effectiveScenarioHash: resolution.effectiveScenario.effectiveScenarioHash }, scenario: scenarioEvidence(resolution.effectiveScenario), reference: referenceEvidence(fixture, resolution, caseSpec), coreProbes, compile: { constraintHash: input.constraintIR.deterministicSignature, referencePlanHash: input.referencePlan.planHash, pipelinePlanHash: input.pipelinePlan.planHash } };
433
509
  return { loaded, resolution, caseSpec, profile, fixture, input, prompt, acceptance };
434
510
  }
435
511
  function bundlePins(profile, scenario) { return { tool: toolPin(TOOL_ID, CLI_VERSION), core: toolPin('@voce-engine/core', CORE_VERSION), contracts: toolPin('@voce-engine/contracts', CONTRACTS_VERSION), scenario: { id: scenario.descriptor.manifest.packId, version: scenario.descriptor.manifest.version, digest: scenario.descriptor.packageDigest }, profile: profilePin(profile) }; }
@@ -546,12 +622,12 @@ async function compileCommand(args) {
546
622
  const written = await writeBundle(outputPath, 'compiled', { id: result.caseSpec.id, revision: result.caseSpec.revision }, bundlePins(result.profile, result.loaded), payload);
547
623
  return outputSummary(written.manifest, { promptGuard: guard.status, scenario: result.loaded.descriptor.manifest.packId });
548
624
  }
549
- async function semanticForRun(run, artifacts, profile) {
625
+ async function semanticForRun(run, artifacts, profile, criteria) {
550
626
  const reviewer = new FixtureSemanticReviewer();
551
627
  const inputHash = sha256({ runId: run.id, artifacts: artifacts.map((item) => item.contentHash).sort() });
552
628
  const model = reviewer.version;
553
629
  const adapter = { id: 'voce.fixture-semantic-reviewer-adapter', version: '1.0.0', digest: sha256({ id: 'voce.fixture-semantic-reviewer-adapter', version: '1.0.0' }) };
554
- const requestBase = { schemaVersion: 'voce.semantic-review-request/v1alpha1', id: `semantic-${run.id}`, caseId: run.caseId, caseRevision: run.caseRevision, contextHash: run.contextHash, inputHash, outputArtifacts: artifacts, criteria: [{ id: 'fixture.semantic', kind: 'semantic_fidelity', importance: 'required', prompt: 'Offline fixture semantic proposal.' }], model, adapter, profile: { id: profile.id, version: profile.version, digest: profile.profileHash }, authorizationId: `semantic-auth-${run.id}`, destination: 'local', allowedEvidenceRegionIds: [], dataCategories: ['image'], budget: { schemaVersion: 'voce.budget/v1alpha1', id: `semantic-budget-${run.id}`, maximumCalls: 1, maximumRetries: 0, timeoutMs: 60_000 } };
630
+ const requestBase = { schemaVersion: 'voce.semantic-review-request/v1alpha1', id: `semantic-${run.id}`, caseId: run.caseId, caseRevision: run.caseRevision, contextHash: run.contextHash, inputHash, outputArtifacts: artifacts, criteria: criteria.length ? criteria : [{ id: 'fixture.semantic', kind: 'semantic_fidelity', importance: 'required', prompt: 'Offline fixture semantic proposal.' }], model, adapter, profile: { id: profile.id, version: profile.version, digest: profile.profileHash }, authorizationId: `semantic-auth-${run.id}`, destination: 'local', allowedEvidenceRegionIds: [], dataCategories: ['image'], budget: { schemaVersion: 'voce.budget/v1alpha1', id: `semantic-budget-${run.id}`, maximumCalls: 1, maximumRetries: 0, timeoutMs: 60_000 } };
555
631
  const request = { ...requestBase, requestHash: computeSemanticReviewRequestHash(requestBase) };
556
632
  const authorization = fixtureM6Authorization({ id: request.authorizationId, caseId: request.caseId, caseRevision: request.caseRevision, contextHash: request.contextHash, stepId: request.id, purpose: 'semantic_review', inputHash, artifactHashes: artifacts.map((item) => item.contentHash), adapter, profileDigest: request.profile.digest, destination: 'local', dataCategories: ['image'], budget: request.budget, modelId: model.id, modelVersion: model.version });
557
633
  const execution = await executeSemanticReview(reviewer, request, authorization);
@@ -578,11 +654,13 @@ async function runCommand(args) {
578
654
  const outputContract = { artifactKind: 'image', dataType: 'image', mediaTypes: ['image/png'], cardinality: { min: 1, max: 1 }, background: 'opaque', allowAlpha: false };
579
655
  const structural = validateStructuralImage({ schemaVersion: 'voce.structural-validation-input/v1alpha1', id: `structural-${run.id}`, artifacts: [{ artifact: structuralArtifact, bytes: FIXTURE_M6_OPAQUE_PNG }], outputContract, expectedCardinality: { min: 1, max: 1 } });
580
656
  const fixture = bundle.payload.fixture;
657
+ const scenario = bundle.payload.scenario?.effectiveScenario;
658
+ const semanticCriteria = scenario ? semanticCriteriaForScenario(scenario) : [];
581
659
  let semanticProposal;
582
660
  const semanticReceipts = [];
583
661
  const reconciliation = [];
584
662
  if (fixture?.semanticReview) {
585
- const semantic = await semanticForRun(run, [structuralArtifact, ...artifacts], profileFor(String(bundle.payload.profile?.id ?? MOCK_IMAGE_PROFILE.id)));
663
+ const semantic = await semanticForRun(run, [structuralArtifact, ...artifacts], profileFor(String(bundle.payload.profile?.id ?? MOCK_IMAGE_PROFILE.id)), semanticCriteria);
586
664
  semanticProposal = semantic.report;
587
665
  semanticReceipts.push(semantic.receipt);
588
666
  reconciliation.push(semantic.remote);
@@ -591,7 +669,7 @@ async function runCommand(args) {
591
669
  const evaluation = compileEvaluationReport({ run: { id: run.id, technicalOutcome: run.technicalOutcome, state: run.state, contextHash: run.contextHash, pipelinePlanHash: run.pipelinePlanHash, promptArtifactHash: run.promptArtifactHash }, structural, semanticProposal, humanAcceptance: human, cleanup: result.cleanupReceipts, replay: { mode: 'artifact', status: 'available', code: 'REPLAY_AVAILABLE', artifactIds: artifacts.map((item) => item.id) }, artifacts: [structuralArtifact, ...artifacts], sourceHashes: { compiledBundle: bundle.manifest.semanticHash, executionRun: computeExecutionRunHash(run) } });
592
670
  const traceModel = traceModelFromExecution({ run, receipts: [...result.receipts, ...semanticReceipts], cleanup: result.cleanupReceipts, reconciliation: [...result.remoteCallRuns, ...reconciliation], artifacts: [structuralArtifact, ...artifacts], structural, semanticProposal, humanAcceptance: human, budgets: input.pipelinePlan.steps.map((step) => step.budget), destinations: input.pipelinePlan.dataTransfers.map((item) => item.destination), constraintHash: input.constraintIR.deterministicSignature, referencePlanHash: input.referencePlan.planHash, promptHash: input.executionAuthorization.promptArtifactHash, warnings: result.reasons });
593
671
  const acceptance = isObject(bundle.payload.acceptance) ? clone(bundle.payload.acceptance) : {};
594
- const runEvidence = { executionStatus: result.status, executionCode: result.code, receiptCount: result.receipts.length + semanticReceipts.length, receiptAdapters: [...result.receipts, ...semanticReceipts].map((item) => item.adapterId).sort(compare), artifactMediaTypes: artifacts.map((item) => item.mediaType).sort(compare), artifactRoles: artifacts.map((item) => item.role).sort(compare), structuralStatus: structural.status, semanticStatus: semanticProposal?.status ?? 'absent', humanStatus: human?.status ?? 'absent', evaluationStatus: evaluation.status, traceModelPresent: true, traceModelHash: traceModel.modelHash };
672
+ const runEvidence = { executionStatus: result.status, executionCode: result.code, receiptCount: result.receipts.length + semanticReceipts.length, receiptAdapters: [...result.receipts, ...semanticReceipts].map((item) => item.adapterId).sort(compare), artifactMediaTypes: artifacts.map((item) => item.mediaType).sort(compare), artifactRoles: artifacts.map((item) => item.role).sort(compare), structuralStatus: structural.status, semanticStatus: semanticProposal?.status ?? 'absent', semanticCriterionIds: semanticProposal?.findings.map((item) => item.criterionId).sort(compare) ?? [], semanticFindingStatuses: semanticProposal?.findings.map((item) => item.status).sort(compare) ?? [], humanStatus: human?.status ?? 'absent', evaluationStatus: evaluation.status, traceModelPresent: true, traceModelHash: traceModel.modelHash };
595
673
  acceptance.run = runEvidence;
596
674
  const runAssertions = fixture ? assertFixtureAssertions(fixture, acceptance, 'run') : [];
597
675
  acceptance.assertions = [...(Array.isArray(acceptance.assertions) ? acceptance.assertions : []), ...runAssertions].sort((left, right) => compare(String(left.id), String(right.id)));
@@ -615,15 +693,16 @@ async function packTestCommand(source) {
615
693
  const resolution = registry.resolve({ root: { packId: loaded.descriptor.manifest.packId, versionRange: loaded.descriptor.manifest.version }, extensions: [] });
616
694
  if (resolution.status !== 'resolved')
617
695
  throw new CliError('PACK_RESOLUTION_BLOCKED', 'ScenarioPack resolution is blocked.', EXIT.contract);
696
+ const semanticCriteria = semanticCriteriaForScenario(resolution.effectiveScenario);
618
697
  const results = [];
619
698
  for (const fixture of loaded.fixtures) {
620
699
  const profile = profileFor(fixture.profileId ?? MOCK_IMAGE_PROFILE.id);
621
- const caseSpec = defaultCase({ id: fixture.id }, loaded.descriptor.manifest.packId);
700
+ const caseSpec = defaultCase({ id: fixture.id }, loaded.descriptor.manifest.packId, loaded.descriptor.manifest.version);
622
701
  const input = fixtureM5ExecutionInput(profile);
623
702
  const prompt = fixtureM5Candidate(fixtureM5PromptIR(profile));
624
703
  const guard = guardPromptCandidate(fixtureM5GuardInput(fixtureM5PromptIR(profile), prompt));
625
- const coreProbes = Object.fromEntries((fixture.coreProbes ?? []).sort((left, right) => compare(left.id, right.id)).map((probe) => [probe.id, coreProbeEvidence(probe, resolution, caseSpec)]));
626
- const acceptance = { resolution: { status: resolution.status, selectedCount: resolution.report.selected.length, effectiveScenarioHash: resolution.effectiveScenario.effectiveScenarioHash }, reference: referenceEvidence(fixture, resolution, caseSpec), coreProbes, compile: { constraintHash: input.constraintIR.deterministicSignature, referencePlanHash: input.referencePlan.planHash, pipelinePlanHash: input.pipelinePlan.planHash, promptGuard: guard.status } };
704
+ const coreProbes = Object.fromEntries((fixture.coreProbes ?? []).sort((left, right) => compare(left.id, right.id)).map((probe) => [probe.id, coreProbeEvidence(probe, resolution, caseSpec, profile)]));
705
+ const acceptance = { resolution: { status: resolution.status, selectedCount: resolution.report.selected.length, effectiveScenarioHash: resolution.effectiveScenario.effectiveScenarioHash }, scenario: scenarioEvidence(resolution.effectiveScenario), reference: referenceEvidence(fixture, resolution, caseSpec), coreProbes, compile: { constraintHash: input.constraintIR.deterministicSignature, referencePlanHash: input.referencePlan.planHash, pipelinePlanHash: input.pipelinePlan.planHash, promptGuard: guard.status } };
627
706
  const result = executeOffline(input);
628
707
  const run = result.run ?? result.executionRun;
629
708
  if (!run)
@@ -636,7 +715,7 @@ async function packTestCommand(source) {
636
715
  const semanticReceipts = [];
637
716
  const reconciliation = [];
638
717
  if (fixture.semanticReview) {
639
- const semantic = await semanticForRun(run, [structuralArtifact, ...artifacts], profile);
718
+ const semantic = await semanticForRun(run, [structuralArtifact, ...artifacts], profile, semanticCriteria);
640
719
  semanticProposal = semantic.report;
641
720
  semanticReceipts.push(semantic.receipt);
642
721
  reconciliation.push(semantic.remote);
@@ -645,7 +724,7 @@ async function packTestCommand(source) {
645
724
  const compiledHash = sha256({ packId: loaded.descriptor.manifest.packId, fixtureId: fixture.id });
646
725
  const evaluation = compileEvaluationReport({ run: { id: run.id, technicalOutcome: run.technicalOutcome, state: run.state, contextHash: run.contextHash, pipelinePlanHash: run.pipelinePlanHash, promptArtifactHash: run.promptArtifactHash }, structural, semanticProposal, humanAcceptance: human, cleanup: result.cleanupReceipts, replay: { mode: 'artifact', status: 'available', code: 'REPLAY_AVAILABLE', artifactIds: artifacts.map((item) => item.id) }, artifacts: [structuralArtifact, ...artifacts], sourceHashes: { compiledBundle: compiledHash, executionRun: computeExecutionRunHash(run) } });
647
726
  const traceModel = traceModelFromExecution({ run, receipts: [...result.receipts, ...semanticReceipts], cleanup: result.cleanupReceipts, reconciliation: [...result.remoteCallRuns, ...reconciliation], artifacts: [structuralArtifact, ...artifacts], structural, semanticProposal, humanAcceptance: human, budgets: input.pipelinePlan.steps.map((step) => step.budget), destinations: input.pipelinePlan.dataTransfers.map((item) => item.destination), constraintHash: input.constraintIR.deterministicSignature, referencePlanHash: input.referencePlan.planHash, promptHash: input.executionAuthorization.promptArtifactHash, warnings: result.reasons });
648
- acceptance.run = { executionStatus: result.status, executionCode: result.code, receiptCount: result.receipts.length + semanticReceipts.length, receiptAdapters: [...result.receipts, ...semanticReceipts].map((item) => item.adapterId).sort(compare), artifactMediaTypes: artifacts.map((item) => item.mediaType).sort(compare), artifactRoles: artifacts.map((item) => item.role).sort(compare), structuralStatus: structural.status, semanticStatus: semanticProposal?.status ?? 'absent', humanStatus: human?.status ?? 'absent', evaluationStatus: evaluation.status, traceModelPresent: true, traceModelHash: traceModel.modelHash };
727
+ acceptance.run = { executionStatus: result.status, executionCode: result.code, receiptCount: result.receipts.length + semanticReceipts.length, receiptAdapters: [...result.receipts, ...semanticReceipts].map((item) => item.adapterId).sort(compare), artifactMediaTypes: artifacts.map((item) => item.mediaType).sort(compare), artifactRoles: artifacts.map((item) => item.role).sort(compare), structuralStatus: structural.status, semanticStatus: semanticProposal?.status ?? 'absent', semanticCriterionIds: semanticProposal?.findings.map((item) => item.criterionId).sort(compare) ?? [], semanticFindingStatuses: semanticProposal?.findings.map((item) => item.status).sort(compare) ?? [], humanStatus: human?.status ?? 'absent', evaluationStatus: evaluation.status, traceModelPresent: true, traceModelHash: traceModel.modelHash };
649
728
  const assertions = assertFixtureAssertions(fixture, acceptance, 'all');
650
729
  const expected = fixture.expectedStatus ?? 'completed';
651
730
  const accepted = result.status === expected || (expected === 'needs_review' && result.status === 'completed');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voce-engine/cli",
3
- "version": "0.1.0-rc.2",
3
+ "version": "0.1.0-rc.3",
4
4
  "description": "Offline-first command line tools for the VOCE v0.1 release candidate.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -36,9 +36,9 @@
36
36
  "tag": "next"
37
37
  },
38
38
  "dependencies": {
39
- "@voce-engine/contracts": "0.1.0-rc.2",
40
- "@voce-engine/core": "0.1.0-rc.2",
41
- "@voce-engine/testkit": "0.1.0-rc.2"
39
+ "@voce-engine/contracts": "0.1.0-rc.3",
40
+ "@voce-engine/core": "0.1.0-rc.3",
41
+ "@voce-engine/testkit": "0.1.0-rc.3"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsc -b"