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

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.4` 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.4
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.4";
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, VISUAL_COMPOSITION_CATALOG, computeVisualCompositionCatalogHash, expandVisualCompositionPreset, } 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.4';
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.4';
11
+ const CORE_VERSION = '0.1.0-rc.4';
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,107 @@ 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) {
345
- const ir = compileConstraints(probeInput(probe, resolution, caseSpec));
346
- return {
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) {
354
+ const baseInput = probeInput(probe, resolution, caseSpec);
355
+ const ir = compileConstraints(baseInput);
356
+ const presetInput = { ...baseInput, changeIntents: [...baseInput.changeIntents, ...expandVisualCompositionPreset('rule-of-thirds')] };
357
+ const presetIr = compileConstraints(presetInput);
358
+ const evidence = {
347
359
  status: ir.status, constraintHash: ir.deterministicSignature, blockingConflictCount: ir.conflicts.filter((item) => item.blocking).length,
348
360
  conflictCodes: ir.conflicts.map((item) => item.code).sort(compare), reviewRequirementCount: ir.reviewRequirements.length,
349
361
  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
362
  degradationCount: ir.degradedPreferences.length, personIntentCount: (probe.intents ?? []).filter((item) => item.targetPath.startsWith('person.')).length,
363
+ constraintTargetPaths: ir.constraints.flatMap((item) => item.targetPaths).sort(compare),
364
+ constraintSourceIds: [...new Set(ir.constraints.flatMap((item) => item.sourceIds))].sort(compare),
365
+ presetId: 'rule-of-thirds', presetConstraintStatus: presetIr.status, presetConstraintHashBefore: ir.deterministicSignature, presetConstraintHashAfter: presetIr.deterministicSignature,
366
+ };
367
+ const candidates = referenceProbeCandidates(probe, ir);
368
+ const presetCandidates = referenceProbeCandidates(probe, presetIr);
369
+ const maximumReferenceCount = probe.maximumReferenceCount ?? profile.maximumReferenceCount;
370
+ 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: [] } });
371
+ const presetPlan = planReferences({ schemaVersion: 'voce.reference-planning-input/v1alpha1', caseId: caseSpec.id, caseRevision: caseSpec.revision, contextHash: presetIr.contextHash, constraintIR: presetIr, candidates: presetCandidates, dependencies: [], profile, budget: { maximumReferenceCount, usedReferenceCount: 0, byteLengthKnown: true, unknownByteLengthAssetIds: [] } });
372
+ evidence.referenceStatus = plan.status;
373
+ evidence.referencePlanHash = plan.planHash;
374
+ evidence.selectedRoles = plan.ordered.map((item) => item.role).sort(compare);
375
+ evidence.omittedRoles = plan.omitted.map((item) => candidates.find((candidate) => candidate.id === item.candidateId)?.role ?? item.candidateId).sort(compare);
376
+ evidence.blockedRoles = plan.blockedReferences.map((item) => candidates.find((candidate) => candidate.id === item.candidateId)?.role ?? item.candidateId).sort(compare);
377
+ evidence.selectedSourceBindingIds = [...new Set(plan.ordered.flatMap((item) => item.sourceBindingIds))].sort(compare);
378
+ evidence.presetReferenceCountBefore = plan.ordered.length;
379
+ evidence.presetReferenceCountAfter = presetPlan.ordered.length;
380
+ evidence.presetReferencePlanHashBefore = plan.planHash;
381
+ evidence.presetReferencePlanHashAfter = presetPlan.planHash;
382
+ if (!probe.references?.length)
383
+ return evidence;
384
+ if (ir.status !== 'ok' || plan.status !== 'ok') {
385
+ evidence.promptStatus = 'blocked';
386
+ return evidence;
387
+ }
388
+ 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 });
389
+ if (!pipelineResult.pipelinePlan) {
390
+ evidence.promptStatus = 'blocked';
391
+ return evidence;
392
+ }
393
+ const outputContract = probeInput(probe, resolution, caseSpec).outputContract;
394
+ const prompt = compilePromptIR({ schemaVersion: 'voce.prompt-compilation-input/v1alpha2', 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 }, effectiveScenario: resolution.effectiveScenario });
395
+ evidence.promptStatus = 'ok';
396
+ evidence.promptHash = prompt.deterministicSignature;
397
+ 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);
398
+ evidence.promptReferenceRoles = prompt.referenceMappings.map((mapping) => mapping.role).sort(compare);
399
+ return evidence;
400
+ }
401
+ function scenarioContributions(scenario, category) { return scenario[category].filter(isObject); }
402
+ function contributionArray(contributions, field) { return contributions.flatMap((contribution) => Array.isArray(contribution[field]) ? contribution[field].filter(isObject) : []); }
403
+ function semanticCriteriaForScenario(scenario) {
404
+ const criteria = contributionArray(scenarioContributions(scenario, 'reviewTemplates'), 'criteria').map((criterion) => {
405
+ const id = stringField(criterion, 'id', 'REVIEW_TEMPLATE_INVALID');
406
+ const kind = stringField(criterion, 'kind', 'REVIEW_TEMPLATE_INVALID');
407
+ const importance = criterion.importance;
408
+ if (importance !== 'hard' && importance !== 'required' && importance !== 'preferred')
409
+ throw new CliError('REVIEW_TEMPLATE_INVALID', 'Review criterion importance is invalid.', EXIT.contract);
410
+ if (criterion.targetPath !== undefined && typeof criterion.targetPath !== 'string')
411
+ throw new CliError('REVIEW_TEMPLATE_INVALID', 'Review criterion targetPath is invalid.', EXIT.contract);
412
+ if (criterion.prompt !== undefined && typeof criterion.prompt !== 'string')
413
+ throw new CliError('REVIEW_TEMPLATE_INVALID', 'Review criterion prompt is invalid.', EXIT.contract);
414
+ return { id, kind, importance, ...(criterion.targetPath === undefined ? {} : { targetPath: criterion.targetPath }), ...(criterion.prompt === undefined ? {} : { prompt: criterion.prompt }) };
415
+ });
416
+ const byId = new Map();
417
+ for (const criterion of criteria.sort((left, right) => compare(left.id, right.id))) {
418
+ const prior = byId.get(criterion.id);
419
+ if (prior && canonicalize(prior) !== canonicalize(criterion))
420
+ throw new CliError('REVIEW_TEMPLATE_ID_COLLISION', 'Review criteria with one ID disagree.', EXIT.contract);
421
+ byId.set(criterion.id, criterion);
422
+ }
423
+ return [...byId.values()];
424
+ }
425
+ function scenarioEvidence(scenario) {
426
+ const ontology = contributionArray(scenarioContributions(scenario, 'ontologyVocabulary'), 'paths');
427
+ const scopes = scenarioContributions(scenario, 'interpretationScopes');
428
+ const bindings = contributionArray(scopes, 'bindings');
429
+ const promptDefinitions = contributionArray(scenarioContributions(scenario, 'promptSections'), 'sections');
430
+ const promptPaths = promptDefinitions.flatMap((definition) => Array.isArray(definition.requiredPaths) ? definition.requiredPaths.filter((item) => typeof item === 'string') : []);
431
+ const promptSectionOrder = promptDefinitions.map((definition) => ({ id: String(definition.id ?? ''), order: typeof definition.order === 'number' ? definition.order : 0 })).filter((item) => item.id).sort((left, right) => left.order - right.order || compare(left.id, right.id)).map((item) => item.id);
432
+ const criteria = semanticCriteriaForScenario(scenario);
433
+ return {
434
+ ontologyPaths: ontology.map((item) => String(item.path ?? '')).filter(Boolean).sort(compare),
435
+ bindingPolicies: bindings.map((item) => `${String(item.assetRole ?? '')}|${String(item.relation ?? '')}|${String(item.targetPath ?? '')}|${String(item.priority ?? '')}`).sort(compare),
436
+ promptTargetPaths: [...new Set(promptPaths)].sort(compare),
437
+ promptSectionOrder,
438
+ visualComposition: { catalogId: VISUAL_COMPOSITION_CATALOG.id, catalogHash: computeVisualCompositionCatalogHash(), pathCount: VISUAL_COMPOSITION_CATALOG.paths.length, presetCount: VISUAL_COMPOSITION_CATALOG.presets.length, fullShotChanges: expandVisualCompositionPreset('full-shot').map((item) => ({ targetPath: item.targetPath, ...(item.requestedValue === undefined ? {} : { requestedValue: item.requestedValue }) })) },
439
+ reviewCriterionIds: criteria.map((item) => item.id).sort(compare),
440
+ reviewTargetPaths: criteria.map((item) => item.targetPath).filter((item) => typeof item === 'string').sort(compare),
351
441
  };
352
442
  }
353
443
  function referenceEvidence(fixture, resolution, caseSpec) {
@@ -406,13 +496,13 @@ function profileFor(id) {
406
496
  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
497
  function profilePin(profile) { return { id: profile.id, version: profile.version, digest: profile.profileHash }; }
408
498
  function toolPin(id, version) { return { id, version, digest: sha256({ id, version }) }; }
409
- function defaultCase(doc, packId) {
499
+ function defaultCase(doc, packId, packVersion) {
410
500
  assertKnown(doc, ['schemaVersion', 'id', 'revision', 'fixtureId', 'mode', 'scenario', 'userIntent', 'assets', 'trustedMetadata', 'policies', 'requestedOutput'], 'CASE_UNKNOWN_FIELD');
411
501
  const id = typeof doc.id === 'string' ? doc.id : `${packId}-case`;
412
502
  const revision = typeof doc.revision === 'number' ? doc.revision : 1;
413
503
  const output = isObject(doc.requestedOutput) ? doc.requestedOutput : { artifactKind: 'image', dataType: 'image', mediaTypes: ['image/png'], cardinality: { min: 1, max: 1 }, background: 'opaque', allowAlpha: false };
414
504
  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 };
505
+ 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
506
  }
417
507
  async function compileInputs(casePath, scenarioPath, profilePath) {
418
508
  const caseDoc = record(await readJson(path.resolve(casePath), 'CASE_FILE_UNSAFE'), 'CASE_JSON_INVALID');
@@ -422,14 +512,14 @@ async function compileInputs(casePath, scenarioPath, profilePath) {
422
512
  const resolution = registry.resolve({ root: { packId: loaded.descriptor.manifest.packId, versionRange: loaded.descriptor.manifest.version }, extensions: [] });
423
513
  if (resolution.status !== 'resolved')
424
514
  throw new CliError('PACK_RESOLUTION_BLOCKED', 'ScenarioPack resolution is blocked.', EXIT.contract);
425
- const caseSpec = defaultCase(caseDoc, loaded.descriptor.manifest.packId);
515
+ const caseSpec = defaultCase(caseDoc, loaded.descriptor.manifest.packId, loaded.descriptor.manifest.version);
426
516
  const fixtureId = typeof caseDoc.fixtureId === 'string' ? caseDoc.fixtureId : caseSpec.id;
427
517
  const fixture = loaded.fixtures.find((candidate) => candidate.id === fixtureId) ?? loaded.fixtures[0] ?? { id: fixtureId, profileId: MOCK_IMAGE_PROFILE.id };
428
518
  const profile = await loadProfile(profilePath);
429
519
  const prompt = fixtureM5Candidate(fixtureM5PromptIR(profile));
430
520
  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 } };
521
+ const coreProbes = Object.fromEntries((fixture.coreProbes ?? []).sort((left, right) => compare(left.id, right.id)).map((probe) => [probe.id, coreProbeEvidence(probe, resolution, caseSpec, profile)]));
522
+ 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
523
  return { loaded, resolution, caseSpec, profile, fixture, input, prompt, acceptance };
434
524
  }
435
525
  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 +636,12 @@ async function compileCommand(args) {
546
636
  const written = await writeBundle(outputPath, 'compiled', { id: result.caseSpec.id, revision: result.caseSpec.revision }, bundlePins(result.profile, result.loaded), payload);
547
637
  return outputSummary(written.manifest, { promptGuard: guard.status, scenario: result.loaded.descriptor.manifest.packId });
548
638
  }
549
- async function semanticForRun(run, artifacts, profile) {
639
+ async function semanticForRun(run, artifacts, profile, criteria) {
550
640
  const reviewer = new FixtureSemanticReviewer();
551
641
  const inputHash = sha256({ runId: run.id, artifacts: artifacts.map((item) => item.contentHash).sort() });
552
642
  const model = reviewer.version;
553
643
  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 } };
644
+ 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
645
  const request = { ...requestBase, requestHash: computeSemanticReviewRequestHash(requestBase) };
556
646
  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
647
  const execution = await executeSemanticReview(reviewer, request, authorization);
@@ -578,11 +668,13 @@ async function runCommand(args) {
578
668
  const outputContract = { artifactKind: 'image', dataType: 'image', mediaTypes: ['image/png'], cardinality: { min: 1, max: 1 }, background: 'opaque', allowAlpha: false };
579
669
  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
670
  const fixture = bundle.payload.fixture;
671
+ const scenario = bundle.payload.scenario?.effectiveScenario;
672
+ const semanticCriteria = scenario ? semanticCriteriaForScenario(scenario) : [];
581
673
  let semanticProposal;
582
674
  const semanticReceipts = [];
583
675
  const reconciliation = [];
584
676
  if (fixture?.semanticReview) {
585
- const semantic = await semanticForRun(run, [structuralArtifact, ...artifacts], profileFor(String(bundle.payload.profile?.id ?? MOCK_IMAGE_PROFILE.id)));
677
+ const semantic = await semanticForRun(run, [structuralArtifact, ...artifacts], profileFor(String(bundle.payload.profile?.id ?? MOCK_IMAGE_PROFILE.id)), semanticCriteria);
586
678
  semanticProposal = semantic.report;
587
679
  semanticReceipts.push(semantic.receipt);
588
680
  reconciliation.push(semantic.remote);
@@ -591,7 +683,7 @@ async function runCommand(args) {
591
683
  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
684
  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
685
  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 };
686
+ 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
687
  acceptance.run = runEvidence;
596
688
  const runAssertions = fixture ? assertFixtureAssertions(fixture, acceptance, 'run') : [];
597
689
  acceptance.assertions = [...(Array.isArray(acceptance.assertions) ? acceptance.assertions : []), ...runAssertions].sort((left, right) => compare(String(left.id), String(right.id)));
@@ -615,15 +707,16 @@ async function packTestCommand(source) {
615
707
  const resolution = registry.resolve({ root: { packId: loaded.descriptor.manifest.packId, versionRange: loaded.descriptor.manifest.version }, extensions: [] });
616
708
  if (resolution.status !== 'resolved')
617
709
  throw new CliError('PACK_RESOLUTION_BLOCKED', 'ScenarioPack resolution is blocked.', EXIT.contract);
710
+ const semanticCriteria = semanticCriteriaForScenario(resolution.effectiveScenario);
618
711
  const results = [];
619
712
  for (const fixture of loaded.fixtures) {
620
713
  const profile = profileFor(fixture.profileId ?? MOCK_IMAGE_PROFILE.id);
621
- const caseSpec = defaultCase({ id: fixture.id }, loaded.descriptor.manifest.packId);
714
+ const caseSpec = defaultCase({ id: fixture.id }, loaded.descriptor.manifest.packId, loaded.descriptor.manifest.version);
622
715
  const input = fixtureM5ExecutionInput(profile);
623
716
  const prompt = fixtureM5Candidate(fixtureM5PromptIR(profile));
624
717
  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 } };
718
+ const coreProbes = Object.fromEntries((fixture.coreProbes ?? []).sort((left, right) => compare(left.id, right.id)).map((probe) => [probe.id, coreProbeEvidence(probe, resolution, caseSpec, profile)]));
719
+ 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
720
  const result = executeOffline(input);
628
721
  const run = result.run ?? result.executionRun;
629
722
  if (!run)
@@ -636,7 +729,7 @@ async function packTestCommand(source) {
636
729
  const semanticReceipts = [];
637
730
  const reconciliation = [];
638
731
  if (fixture.semanticReview) {
639
- const semantic = await semanticForRun(run, [structuralArtifact, ...artifacts], profile);
732
+ const semantic = await semanticForRun(run, [structuralArtifact, ...artifacts], profile, semanticCriteria);
640
733
  semanticProposal = semantic.report;
641
734
  semanticReceipts.push(semantic.receipt);
642
735
  reconciliation.push(semantic.remote);
@@ -645,7 +738,7 @@ async function packTestCommand(source) {
645
738
  const compiledHash = sha256({ packId: loaded.descriptor.manifest.packId, fixtureId: fixture.id });
646
739
  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
740
  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 };
741
+ 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
742
  const assertions = assertFixtureAssertions(fixture, acceptance, 'all');
650
743
  const expected = fixture.expectedStatus ?? 'completed';
651
744
  const accepted = result.status === expected || (expected === 'needs_review' && result.status === 'completed');
@@ -654,7 +747,23 @@ async function packTestCommand(source) {
654
747
  const failed = results.some((item) => item.status === 'failed');
655
748
  return { status: failed ? 'failed' : 'passed', packId: loaded.descriptor.manifest.packId, resolutionHash: resolution.report.reportHash, lockHash: resolution.lock.lockHash, effectiveScenarioHash: resolution.effectiveScenario.effectiveScenarioHash, fixtureCount: results.length, results };
656
749
  }
657
- async function doctorCommand() { const major = Number(process.versions.node.split('.')[0]); return { status: major >= 20 ? 'ok' : 'blocked', node: { version: process.versions.node, supported: major >= 20 }, contracts: { schemaVersion: BUNDLE_MANIFEST_SCHEMA_VERSION, packageVersion: CONTRACTS_VERSION }, paths: { explicitOnly: true }, providers: { default: 'disabled', mock: 'offline', networkProbe: false }, authProbe: { inspected: false } }; }
750
+ async function doctorCommand() {
751
+ const major = Number(process.versions.node.split('.')[0]);
752
+ return {
753
+ status: major >= 20 ? 'ok' : 'blocked',
754
+ node: { version: process.versions.node, supported: major >= 20 },
755
+ contracts: { schemaVersion: BUNDLE_MANIFEST_SCHEMA_VERSION, packageVersion: CONTRACTS_VERSION },
756
+ paths: { explicitOnly: true },
757
+ providers: { default: 'disabled', mock: 'offline', networkProbe: false },
758
+ authProbe: { inspected: false },
759
+ visualComposition: {
760
+ catalogId: VISUAL_COMPOSITION_CATALOG.id,
761
+ catalogHash: computeVisualCompositionCatalogHash(),
762
+ pathCount: VISUAL_COMPOSITION_CATALOG.paths.length,
763
+ presetCount: VISUAL_COMPOSITION_CATALOG.presets.length,
764
+ },
765
+ };
766
+ }
658
767
  function required(args, key) { if (!args[key])
659
768
  throw new CliError('ARGUMENT_MISSING', `Missing --${key}.`, EXIT.usage); return args[key]; }
660
769
  function help() { return `voce ${CLI_VERSION}\n\nOffline-first explicit-path CLI.\n\nCommands:\n voce pack inspect --source <path> [--json]\n voce pack validate --source <path> [--json]\n voce pack test --source <path> [--json]\n voce case compile --case <file> --scenario <path> --profile <file> --out <dir> [--json]\n voce case run --bundle <dir> --provider mock --out <dir> [--json]\n voce trace render --bundle <dir> --out <html> [--json]\n voce compare --before <dir> --after <dir> [--out <file>] [--json]\n voce doctor [--json]\n\nExit codes: 0 success, 2 usage, 3 input, 4 contract/hash, 5 offline/provider, 6 output, 7 internal.`; }
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.4",
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.4",
40
+ "@voce-engine/core": "0.1.0-rc.4",
41
+ "@voce-engine/testkit": "0.1.0-rc.4"
42
42
  },
43
43
  "scripts": {
44
44
  "build": "tsc -b"