@aiwg/cli 2026.9.21 → 2026.10.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/agentic/code/providers/capability-matrix.yaml +2 -1
- package/dist/src/artifacts/cli.js +23 -18
- package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
- package/dist/src/artifacts/discovery-shadow.js +195 -0
- package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
- package/dist/src/artifacts/index-builder.js +47 -9
- package/dist/src/artifacts/query-engine.js +108 -5
- package/dist/src/artifacts/types.js +1 -0
- package/dist/src/cli/handlers/decision.js +88 -0
- package/dist/src/cli/handlers/gates.js +154 -0
- package/dist/src/cli/handlers/index.js +5 -1
- package/dist/src/cli/handlers/sessions.js +10 -7
- package/dist/src/cli/handlers/utilities.js +20 -0
- package/dist/src/config/aiwg-config.js +15 -2
- package/dist/src/decision/acceptance.js +25 -0
- package/dist/src/decision/adapters/jev.js +24 -2
- package/dist/src/decision/calibration/registry.js +9 -0
- package/dist/src/decision/compose.js +74 -15
- package/dist/src/decision/context-live-promotion.js +344 -0
- package/dist/src/decision/context-live-qualification.js +591 -0
- package/dist/src/decision/context-pruning.js +847 -0
- package/dist/src/decision/driver.js +430 -0
- package/dist/src/decision/egress-live-qualification.js +874 -0
- package/dist/src/decision/ensemble/contract.js +64 -1
- package/dist/src/decision/ensemble/index.js +1 -0
- package/dist/src/decision/ensemble/report.js +8 -42
- package/dist/src/decision/ensemble/runtime.js +571 -0
- package/dist/src/decision/ensemble-study/artifacts.js +85 -0
- package/dist/src/decision/ensemble-study/calibration.js +443 -0
- package/dist/src/decision/ensemble-study/corpus.js +229 -0
- package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
- package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
- package/dist/src/decision/ensemble-study/multifact.js +241 -0
- package/dist/src/decision/ensemble-study/protocol.js +77 -0
- package/dist/src/decision/ensemble-study/score.js +462 -0
- package/dist/src/decision/ensemble-study/staged.js +87 -0
- package/dist/src/decision/ensemble-study/statistics.js +84 -0
- package/dist/src/decision/evaluate.js +227 -14
- package/dist/src/decision/export.js +4 -0
- package/dist/src/decision/feature-export.js +1074 -0
- package/dist/src/decision/graph-index.js +16 -0
- package/dist/src/decision/graph-live-qualification.js +1041 -0
- package/dist/src/decision/graph-live-workload.js +245 -0
- package/dist/src/decision/graph-skill-bridge.js +21 -3
- package/dist/src/decision/heldout/calibration.js +140 -0
- package/dist/src/decision/heldout/collector.js +430 -0
- package/dist/src/decision/heldout/contract.js +309 -0
- package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
- package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
- package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
- package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
- package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
- package/dist/src/decision/heldout/d29-pools.js +288 -0
- package/dist/src/decision/heldout/d29-v6.js +169 -0
- package/dist/src/decision/heldout/d29-v7.js +202 -0
- package/dist/src/decision/heldout/d29-v8.js +586 -0
- package/dist/src/decision/heldout/generator-registry.js +67 -0
- package/dist/src/decision/heldout/generators.js +696 -0
- package/dist/src/decision/heldout/index.js +8 -0
- package/dist/src/decision/heldout/journal.js +346 -0
- package/dist/src/decision/heldout/study-generators.js +64 -0
- package/dist/src/decision/heldout/types.js +2 -0
- package/dist/src/decision/host-policy.js +61 -0
- package/dist/src/decision/index.js +11 -0
- package/dist/src/decision/interop-profile.v1.json +112 -0
- package/dist/src/decision/interop.js +653 -0
- package/dist/src/decision/issue-triage/index.js +3 -0
- package/dist/src/decision/issue-triage/runtime.js +1069 -0
- package/dist/src/decision/issue-triage/types.js +2 -0
- package/dist/src/decision/patterns/catalog.js +28 -8
- package/dist/src/decision/patterns/playground.js +28 -11
- package/dist/src/decision/patterns/specs.js +19 -10
- package/dist/src/decision/preprocessed-evidence.js +724 -0
- package/dist/src/decision/qualification/capture.js +8 -2
- package/dist/src/decision/qualification/gate-evidence.js +19 -4
- package/dist/src/decision/qualification/load.js +8 -1
- package/dist/src/decision/qualification/quality.js +57 -49
- package/dist/src/decision/qualification/release.js +76 -3
- package/dist/src/decision/qualification/runner.js +5 -2
- package/dist/src/decision/routing/contract.js +175 -0
- package/dist/src/decision/routing/index.js +5 -0
- package/dist/src/decision/routing/report.js +216 -0
- package/dist/src/decision/routing/runtime.js +685 -0
- package/dist/src/decision/routing/types.js +2 -0
- package/dist/src/decision/sdlc-screening.js +1157 -0
- package/dist/src/decision/sensitivity/comparative.js +154 -0
- package/dist/src/decision/sensitivity/contract.js +218 -0
- package/dist/src/decision/sensitivity/index.js +5 -0
- package/dist/src/decision/sensitivity/runtime.js +912 -0
- package/dist/src/decision/sensitivity/types.js +2 -0
- package/dist/src/decision/validate.js +26 -0
- package/dist/src/extensions/commands/definitions.js +38 -0
- package/dist/src/extensions/manifest.js +44 -0
- package/dist/src/gates/discovery.js +315 -0
- package/dist/src/gates/driver.js +388 -0
- package/dist/src/gates/evaluate.js +719 -0
- package/dist/src/gates/floors.js +255 -0
- package/dist/src/gates/index.js +17 -0
- package/dist/src/gates/providers/evidence.js +38 -0
- package/dist/src/gates/providers/index.js +21 -0
- package/dist/src/gates/providers/isolated-child-source.js +446 -0
- package/dist/src/gates/providers/loader.js +1069 -0
- package/dist/src/gates/providers/paired.js +52 -0
- package/dist/src/gates/providers/proportion.js +60 -0
- package/dist/src/gates/providers/registry.js +40 -0
- package/dist/src/gates/providers/scalar.js +41 -0
- package/dist/src/gates/providers/screening.js +53 -0
- package/dist/src/gates/providers/types.js +25 -0
- package/dist/src/gates/registry.js +1003 -0
- package/dist/src/gates/report.js +102 -0
- package/dist/src/gates/schema.js +56 -0
- package/dist/src/gates/stats/binomial.js +26 -0
- package/dist/src/gates/stats/bootstrap.js +62 -0
- package/dist/src/gates/stats/clopper-pearson.js +95 -0
- package/dist/src/gates/stats/digest.js +26 -0
- package/dist/src/gates/stats/error.js +5 -0
- package/dist/src/gates/stats/index.js +9 -0
- package/dist/src/gates/stats/normal.js +35 -0
- package/dist/src/gates/stats/paired.js +120 -0
- package/dist/src/gates/stats/splits.js +30 -0
- package/dist/src/gates/types.js +8 -0
- package/dist/src/governance/redaction.js +14 -4
- package/dist/src/mcp/muse-native-config.mjs +123 -0
- package/dist/src/mcp/tools/decision.mjs +188 -0
- package/dist/src/mcp/tools/subsystems.mjs +2 -0
- package/dist/src/providers/capability-matrix.yaml +2 -1
- package/dist/src/providers/provider-definitions.js +2 -1
- package/dist/src/sessions/adapters/muse.js +336 -39
- package/dist/src/sessions/batch-import.js +7 -0
- package/dist/src/sessions/workspace-discovery.js +13 -2
- package/package.json +1 -1
- package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
- package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
- package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
- package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
- package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
- package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
- package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
- package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
- package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
- package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
- package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
- package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
- package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
- package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
- package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
- package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
- package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
- package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
- package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
- package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
- package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
- package/schemas/decision/D29Study.v1.schema.json +1903 -0
- package/schemas/decision/D29Study.v2.schema.json +1344 -0
- package/schemas/decision/D29Study.v3.schema.json +609 -0
- package/schemas/decision/D29Study.v4.schema.json +1133 -0
- package/schemas/decision/D29Study.v6.schema.json +1205 -0
- package/schemas/decision/D29Study.v7.schema.json +1471 -0
- package/schemas/decision/D29Study.v8.schema.json +671 -0
- package/schemas/decision/D29Study.v9.schema.json +778 -0
- package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
- package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
- package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
- package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
- package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
- package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
- package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
- package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
- package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
- package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
- package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
- package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
- package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
- package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
- package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
- package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
- package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
- package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
- package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
- package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
- package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
- package/schemas/decision/DecisionRuleset.schema.json +8 -2
- package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
- package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
- package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
- package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
- package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
- package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
- package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
- package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
- package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
- package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
- package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
- package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
- package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
- package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
- package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
- package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
- package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
- package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
- package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
- package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
- package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
- package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
- package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
- package/tools/agents/deploy-agents.mjs +1 -1
- package/tools/agents/providers/base.mjs +26 -7
- package/tools/agents/providers/factory.mjs +10 -2
- package/tools/agents/providers/muse-hooks.mjs +1 -29
- package/tools/agents/providers/muse.mjs +6 -3
|
@@ -488,12 +488,13 @@ providers:
|
|
|
488
488
|
|
|
489
489
|
muse:
|
|
490
490
|
display_name: Muse Code
|
|
491
|
-
status:
|
|
491
|
+
status: stable
|
|
492
492
|
# Meta's terminal/CI coding agent (Muse Spark-powered). Explicitly not a
|
|
493
493
|
# model-API surface: Glimmer/Image/Voice are out of scope.
|
|
494
494
|
# Skill writer (.agents/skills + XDG user root), discover-first AGENTS.md
|
|
495
495
|
# bridge, managed .muse/hooks.json, and opt-in MCP settings profile.
|
|
496
496
|
# ADR: docs/architecture/adr-muse-provider-target.md · epic #223.
|
|
497
|
+
# #231: promoted stable — Linux PUW + security review; maintainer Linux-only waiver 2026-10-02.
|
|
497
498
|
daemon_tier: unsupported
|
|
498
499
|
daemon_pty_adapter: false
|
|
499
500
|
artifact_paths:
|
|
@@ -526,22 +526,21 @@ async function handleBuild(args) {
|
|
|
526
526
|
loadUserGraphConfigs(cwd);
|
|
527
527
|
loadGlobalGraphConfigs();
|
|
528
528
|
let jsonBuilt = false;
|
|
529
|
-
|
|
529
|
+
const builtGraphs = new Set();
|
|
530
530
|
if (graph) {
|
|
531
531
|
// --graph X: build the JSON graph if X is one; otherwise X may be a
|
|
532
532
|
// research-corpus markdown view, rendered below.
|
|
533
533
|
if (graph in GRAPH_CONFIGS) {
|
|
534
534
|
await buildIndex(cwd, { force, verbose, scope, graph, explicit: true });
|
|
535
535
|
jsonBuilt = true;
|
|
536
|
-
|
|
536
|
+
builtGraphs.add(graph);
|
|
537
537
|
}
|
|
538
538
|
}
|
|
539
539
|
else if (all) {
|
|
540
540
|
// Build all known graphs — user asked for everything, but don't hard-error on missing dirs
|
|
541
541
|
for (const [name] of orderedGraphEntries(Object.entries(GRAPH_CONFIGS))) {
|
|
542
542
|
await buildIndex(cwd, { force, verbose, graph: name, explicit: false });
|
|
543
|
-
|
|
544
|
-
projectGraphBuilt = true;
|
|
543
|
+
builtGraphs.add(name);
|
|
545
544
|
}
|
|
546
545
|
jsonBuilt = true;
|
|
547
546
|
}
|
|
@@ -550,8 +549,7 @@ async function handleBuild(args) {
|
|
|
550
549
|
for (const [name, config] of orderedGraphEntries(Object.entries(GRAPH_CONFIGS))) {
|
|
551
550
|
if (config.defaultBuild) {
|
|
552
551
|
await buildIndex(cwd, { force, verbose, scope: name === Object.keys(GRAPH_CONFIGS)[0] ? scope : undefined, graph: name, explicit: false });
|
|
553
|
-
|
|
554
|
-
projectGraphBuilt = true;
|
|
552
|
+
builtGraphs.add(name);
|
|
555
553
|
}
|
|
556
554
|
}
|
|
557
555
|
jsonBuilt = true;
|
|
@@ -583,21 +581,28 @@ async function handleBuild(args) {
|
|
|
583
581
|
if (viewResults.some((r) => r.status === 'unsupported')) {
|
|
584
582
|
process.exit(1);
|
|
585
583
|
}
|
|
586
|
-
if (
|
|
587
|
-
await
|
|
584
|
+
if (builtGraphs.size > 0) {
|
|
585
|
+
await syncFortemiCoreCachesAfterBuild(cwd, builtGraphs, verbose);
|
|
588
586
|
}
|
|
589
587
|
}
|
|
590
|
-
async function
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
588
|
+
async function syncFortemiCoreCachesAfterBuild(cwd, graphs, verbose) {
|
|
589
|
+
const { syncFortemiCoreIndex } = await import('./fortemi-core-sync.js');
|
|
590
|
+
const { loadGraphIndexFile } = await import('./index-reader.js');
|
|
591
|
+
for (const graph of graphs) {
|
|
592
|
+
// Some default-build graphs skip cleanly when their scan roots are absent.
|
|
593
|
+
// Only materialize a cache when this build left an actual source index.
|
|
594
|
+
if (!loadGraphIndexFile(cwd, 'metadata.json', graph))
|
|
595
|
+
continue;
|
|
596
|
+
try {
|
|
597
|
+
const manifest = syncFortemiCoreIndex(cwd, { graph });
|
|
598
|
+
if (verbose) {
|
|
599
|
+
console.log(`Fortemi Core ${graph} sync: ${manifest.status}, ${manifest.item_count} item(s) → ${manifest.export_path}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
catch (err) {
|
|
603
|
+
console.error(`Warning: Fortemi Core ${graph} sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
604
|
+
console.error(`Run \`aiwg index sync --graph ${graph}\` before using Fortemi-backed commands for '${graph}' artifacts.`);
|
|
596
605
|
}
|
|
597
|
-
}
|
|
598
|
-
catch (err) {
|
|
599
|
-
console.error(`Warning: Fortemi Core project sync failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
600
|
-
console.error('Run `aiwg index sync --graph project` before using Fortemi-backed `aiwg discover` or `aiwg show` for project-local artifacts.');
|
|
601
606
|
}
|
|
602
607
|
}
|
|
603
608
|
/**
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { types as utilTypes } from 'node:util';
|
|
3
|
+
import { canonicalJson } from '../security/artifact-trust.js';
|
|
4
|
+
import { admitEntry } from '../decision/entry.js';
|
|
5
|
+
import { evaluateDecisionRuleset } from '../decision/evaluate.js';
|
|
6
|
+
import { validateProjectionPolicy } from '../decision/projection.js';
|
|
7
|
+
import { artifactPin, assertArtifactPin, validateAgainstSchema, validateBinding, validateDefinition, validateRuleset } from '../decision/validate.js';
|
|
8
|
+
const hash = (value) => `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`;
|
|
9
|
+
const pinEqual = (a, b) => canonicalJson(a) === canonicalJson(b);
|
|
10
|
+
const validPin = (pin) => Boolean(pin?.id && pin.version && /^sha256:[a-f0-9]{64}$/.test(pin.digest));
|
|
11
|
+
const requestFields = ['query', 'candidates', 'options', 'definition', 'binding'];
|
|
12
|
+
/** Strip only the trusted cancellation handle; reject accessors/proxies before reading data. */
|
|
13
|
+
function payloadOf(request) {
|
|
14
|
+
if (!request || typeof request !== 'object' || utilTypes.isProxy(request)
|
|
15
|
+
|| ![Object.prototype, null].includes(Object.getPrototypeOf(request)))
|
|
16
|
+
throw new Error('Invalid shadow request');
|
|
17
|
+
const descriptors = Object.getOwnPropertyDescriptors(request);
|
|
18
|
+
if (Reflect.ownKeys(request).some(key => typeof key !== 'string' || ![...requestFields, 'signal'].includes(key))) {
|
|
19
|
+
throw new Error('Unsupported shadow request field');
|
|
20
|
+
}
|
|
21
|
+
const data = {};
|
|
22
|
+
for (const key of requestFields) {
|
|
23
|
+
const descriptor = descriptors[key];
|
|
24
|
+
if (!descriptor || !('value' in descriptor) || !descriptor.enumerable)
|
|
25
|
+
throw new Error('Invalid shadow request field');
|
|
26
|
+
data[key] = descriptor.value;
|
|
27
|
+
}
|
|
28
|
+
admitEntry(data);
|
|
29
|
+
return structuredClone(data);
|
|
30
|
+
}
|
|
31
|
+
/** Concrete Decision runtime bridge, deliberately without an executor or transport shortcut.
|
|
32
|
+
* It supports one pinned option cardinality, one evaluation and one non-retrying target.
|
|
33
|
+
* Other candidate cardinalities fail closed and retain deterministic discovery. */
|
|
34
|
+
export class DiscoveryShadowEvaluator {
|
|
35
|
+
profile;
|
|
36
|
+
calibration;
|
|
37
|
+
scheduler;
|
|
38
|
+
runtime;
|
|
39
|
+
registry;
|
|
40
|
+
alias;
|
|
41
|
+
options;
|
|
42
|
+
profileDigest;
|
|
43
|
+
constructor(config) {
|
|
44
|
+
const profile = { shadowPolicy: config.shadowPolicy, definition: config.definition, ruleset: config.ruleset, binding: config.binding,
|
|
45
|
+
projectionPolicy: config.projectionPolicy, policyPin: config.policyPin, receiptProjectId: config.receiptProjectId,
|
|
46
|
+
runId: config.runId, invocationScope: config.invocationScope };
|
|
47
|
+
admitEntry(profile);
|
|
48
|
+
this.profile = structuredClone(profile);
|
|
49
|
+
const p = this.profile;
|
|
50
|
+
validateDefinition(p.definition);
|
|
51
|
+
validateRuleset(p.ruleset);
|
|
52
|
+
validateBinding(p.binding, p.ruleset);
|
|
53
|
+
assertArtifactPin(p.definition, p.shadowPolicy.definition, 'discovery definition');
|
|
54
|
+
assertArtifactPin(p.binding, p.shadowPolicy.binding, 'discovery binding');
|
|
55
|
+
if (![p.runId, p.invocationScope, p.receiptProjectId].every(value => typeof value === 'string' && value.trim())
|
|
56
|
+
|| !validPin(p.policyPin) || p.ruleset.spec.evaluations.length !== 1 || p.definition.spec.answer.kind !== 'choice') {
|
|
57
|
+
throw new Error('Shadow evaluator requires an identified single Choice profile');
|
|
58
|
+
}
|
|
59
|
+
if (!Number.isSafeInteger(p.shadowPolicy.ambiguity.maximumCandidates) || p.shadowPolicy.ambiguity.maximumCandidates < 1
|
|
60
|
+
|| p.shadowPolicy.ambiguity.maximumCandidates > 100 || !Number.isSafeInteger(p.shadowPolicy.ambiguity.maximumTextBytes)
|
|
61
|
+
|| p.shadowPolicy.ambiguity.maximumTextBytes < 1 || p.shadowPolicy.ambiguity.maximumTextBytes > 1_000_000
|
|
62
|
+
|| !Number.isSafeInteger(p.shadowPolicy.maximumLatencyMs) || p.shadowPolicy.maximumLatencyMs < 1
|
|
63
|
+
|| !Number.isSafeInteger(p.shadowPolicy.maximumTokens) || p.shadowPolicy.maximumTokens < 1
|
|
64
|
+
|| !Number.isFinite(p.shadowPolicy.maximumCostUsd) || p.shadowPolicy.maximumCostUsd < 0) {
|
|
65
|
+
throw new Error('Invalid bounded shadow profile');
|
|
66
|
+
}
|
|
67
|
+
if (p.projectionPolicy.fields.length !== 2
|
|
68
|
+
|| canonicalJson(p.projectionPolicy.fields.map(field => field.output).sort()) !== canonicalJson(['candidates', 'query'])
|
|
69
|
+
|| p.projectionPolicy.fields.some(field => field.trust !== 'untrusted' || field.pointer !== `/${field.output}`)) {
|
|
70
|
+
throw new Error('Discovery query and candidates require the untrusted D10 projection');
|
|
71
|
+
}
|
|
72
|
+
const evaluation = p.ruleset.spec.evaluations[0];
|
|
73
|
+
this.alias = evaluation.alias;
|
|
74
|
+
if (evaluation.inputPointer !== '' || !pinEqual(evaluation.decision, artifactPin(p.definition)))
|
|
75
|
+
throw new Error('Shadow evaluation definition mismatch');
|
|
76
|
+
this.options = p.definition.spec.answer.options.map(option => option.id);
|
|
77
|
+
const candidates = this.options.slice(0, -1);
|
|
78
|
+
if (candidates.length < 1 || candidates.length > p.shadowPolicy.ambiguity.maximumCandidates
|
|
79
|
+
|| this.options.at(-1) !== 'none' || candidates.some((id, i) => id !== `candidate_${i}`))
|
|
80
|
+
throw new Error('Invalid pinned shadow option domain');
|
|
81
|
+
const binding = p.binding.spec.evaluations[this.alias];
|
|
82
|
+
const target = binding.targets[0];
|
|
83
|
+
if (binding.targets.length !== 1 || binding.fallbackOn.length || target.retry.maxRetries !== 0
|
|
84
|
+
|| p.binding.spec.maxAttempts !== 1 || p.binding.spec.concurrency !== 1
|
|
85
|
+
|| p.binding.spec.totalTimeoutMs > p.shadowPolicy.maximumLatencyMs || target.timeoutMs > p.shadowPolicy.maximumLatencyMs
|
|
86
|
+
|| target.acceptance.mode !== 'primitive-policy'
|
|
87
|
+
|| !target.acceptance.compatibleUncertaintyProfiles.includes(p.shadowPolicy.uncertaintyProfile)) {
|
|
88
|
+
throw new Error('Shadow evaluator requires bounded single-target primitive acceptance');
|
|
89
|
+
}
|
|
90
|
+
if (!config.scheduler.enabled || !config.scheduler.estimate)
|
|
91
|
+
throw new Error('Shadow evaluator requires existing admission and cost/token estimates');
|
|
92
|
+
const { estimate, onEvidence, ...schedulerData } = config.scheduler;
|
|
93
|
+
admitEntry(schedulerData);
|
|
94
|
+
this.scheduler = { ...structuredClone(schedulerData), estimate, ...(onEvidence ? { onEvidence } : {}) };
|
|
95
|
+
// Shared host limits may be broader than this observer's per-invocation budget.
|
|
96
|
+
// Narrow every admission scope before resolution/dispatch, preserving stricter limits.
|
|
97
|
+
const bounded = (limits) => ({ ...limits,
|
|
98
|
+
maxTokens: Math.min(limits.maxTokens ?? p.shadowPolicy.maximumTokens, p.shadowPolicy.maximumTokens),
|
|
99
|
+
maxCostUsd: Math.min(limits.maxCostUsd ?? p.shadowPolicy.maximumCostUsd, p.shadowPolicy.maximumCostUsd),
|
|
100
|
+
allowUnknownCost: false,
|
|
101
|
+
});
|
|
102
|
+
this.scheduler.workspace.limits = bounded(this.scheduler.workspace.limits);
|
|
103
|
+
this.scheduler.principal.limits = bounded(this.scheduler.principal.limits);
|
|
104
|
+
this.scheduler.providers = Object.fromEntries(Object.entries(this.scheduler.providers).map(([id, limits]) => [id, bounded(limits)]));
|
|
105
|
+
admitEntry(config.calibration.artifact);
|
|
106
|
+
this.calibration = structuredClone(config.calibration.artifact);
|
|
107
|
+
this.registry = config.calibration.registry;
|
|
108
|
+
const registered = this.registry.artifactHistory().find(item => item.id === this.calibration.id);
|
|
109
|
+
const identity = this.calibration.identity;
|
|
110
|
+
if (!registered || hash(registered) !== hash(this.calibration) || this.calibration.digest !== p.shadowPolicy.calibrationDigest
|
|
111
|
+
|| identity.definitionDigest !== artifactPin(p.definition).digest || identity.primitive !== 'choice'
|
|
112
|
+
|| identity.provider !== target.adapter || identity.adapterVersion !== target.adapterVersion
|
|
113
|
+
|| !p.shadowPolicy.allowedModels.includes(identity.actualModel))
|
|
114
|
+
throw new Error('Shadow calibration is not the registered pinned profile');
|
|
115
|
+
this.runtime = { adapters: { ...config.adapters }, resolveCredential: config.resolveCredential, receiptStore: config.receiptStore,
|
|
116
|
+
record: config.record, ...(config.now ? { now: config.now } : {}), ...(config.onProjectionEvidence ? { onProjectionEvidence: config.onProjectionEvidence } : {}) };
|
|
117
|
+
this.profileDigest = hash({ profile: p, calibration: this.calibration.digest, scheduler: schedulerData });
|
|
118
|
+
}
|
|
119
|
+
authorize(request) {
|
|
120
|
+
try {
|
|
121
|
+
this.checkedPayload(request);
|
|
122
|
+
validateProjectionPolicy(this.profile.projectionPolicy);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
record(receipt) { this.runtime.record(structuredClone(receipt)); }
|
|
130
|
+
async evaluate(request) {
|
|
131
|
+
const payload = this.checkedPayload(request);
|
|
132
|
+
const descriptor = Object.getOwnPropertyDescriptor(request, 'signal');
|
|
133
|
+
if (!descriptor || !('value' in descriptor) || !(descriptor.value instanceof AbortSignal))
|
|
134
|
+
throw new Error('Shadow evaluation requires a cancellation signal');
|
|
135
|
+
const signal = descriptor.value;
|
|
136
|
+
if (signal.aborted)
|
|
137
|
+
throw new Error('Shadow evaluation cancelled');
|
|
138
|
+
const p = this.profile;
|
|
139
|
+
const input = { query: payload.query, candidates: payload.candidates };
|
|
140
|
+
const invocationId = `discovery-shadow:${hash({ scope: p.invocationScope, profile: this.profileDigest, input }).slice(7)}`;
|
|
141
|
+
const evaluated = await evaluateDecisionRuleset({
|
|
142
|
+
ruleset: structuredClone(p.ruleset), binding: structuredClone(p.binding), definitions: { [p.definition.metadata.id]: structuredClone(p.definition) },
|
|
143
|
+
input, runId: p.runId, invocationId, signal, adapters: this.runtime.adapters, resolveCredential: this.runtime.resolveCredential,
|
|
144
|
+
receiptStore: this.runtime.receiptStore, receiptProjectId: p.receiptProjectId, policyPin: p.policyPin,
|
|
145
|
+
calibrationPin: { id: this.calibration.id, version: this.calibration.identity.calibrator.version, digest: this.calibration.digest },
|
|
146
|
+
calibrationCompatibility: { registry: this.registry, calibrationArtifactId: this.calibration.id,
|
|
147
|
+
policy: { unknown: 'defer', incompatible: 'fail', shadowRequired: 'shadow', unusableCalibration: 'require-approval' },
|
|
148
|
+
identityFor: ({ actualModel }) => ({ ...structuredClone(this.calibration.identity), actualModel }) },
|
|
149
|
+
projection: { resolve: () => structuredClone(p.projectionPolicy), ...(this.runtime.onProjectionEvidence ? { onEvidence: this.runtime.onProjectionEvidence } : {}) },
|
|
150
|
+
scheduler: this.scheduler, ...(this.runtime.now ? { now: this.runtime.now } : {}),
|
|
151
|
+
});
|
|
152
|
+
const receipt = await this.runtime.receiptStore.read(invocationId, p.receiptProjectId);
|
|
153
|
+
const decision = evaluated.spec.evaluations[this.alias];
|
|
154
|
+
if (!receipt || receipt.state !== 'completed' || !receipt.result || !decision
|
|
155
|
+
|| !receipt.evaluations[this.alias] || hash(receipt.result) !== hash(evaluated) || hash(receipt.evaluations[this.alias]) !== hash(decision)) {
|
|
156
|
+
throw new Error('Shadow evaluation has no verified terminal receipt');
|
|
157
|
+
}
|
|
158
|
+
return { result: structuredClone(decision), receiptDigest: hash(receipt) };
|
|
159
|
+
}
|
|
160
|
+
checkedPayload(request) {
|
|
161
|
+
const payload = payloadOf(request);
|
|
162
|
+
const p = this.profile;
|
|
163
|
+
if (p.shadowPolicy.mode !== 'shadow' || !pinEqual(payload.definition, p.shadowPolicy.definition) || !pinEqual(payload.binding, p.shadowPolicy.binding)
|
|
164
|
+
|| canonicalJson(payload.options) !== canonicalJson(this.options) || !Array.isArray(payload.candidates)
|
|
165
|
+
|| payload.candidates.length !== this.options.length - 1 || typeof payload.query !== 'string' || !payload.query.trim()
|
|
166
|
+
|| payload.candidates.some((c, i) => !c || c.option !== this.options[i] || Object.keys(c).sort().join(',') !== 'capability,id,name,option,type'
|
|
167
|
+
|| [c.id, c.name, c.type, c.capability].some(value => typeof value !== 'string') || !c.id)
|
|
168
|
+
|| new Set(payload.candidates.map(c => c.id)).size !== payload.candidates.length
|
|
169
|
+
|| Buffer.byteLength(canonicalJson({ query: payload.query, candidates: payload.candidates })) > p.shadowPolicy.ambiguity.maximumTextBytes) {
|
|
170
|
+
throw new Error('Shadow request does not match pinned bounded domain');
|
|
171
|
+
}
|
|
172
|
+
validateAgainstSchema(p.ruleset.spec.inputSchema, { query: payload.query, candidates: payload.candidates }, 'shadow input');
|
|
173
|
+
validateAgainstSchema(p.definition.spec.inputSchema, { query: payload.query, candidates: payload.candidates }, 'shadow input');
|
|
174
|
+
return payload;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=discovery-shadow-evaluator.js.map
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalJson } from '../security/artifact-trust.js';
|
|
3
|
+
const finite = (n) => Number.isFinite(n) && n >= 0;
|
|
4
|
+
function validatePolicy(policy) {
|
|
5
|
+
if (policy.version !== 'discovery-ambiguity/v1' || !finite(policy.minimumScore)
|
|
6
|
+
|| !Number.isFinite(policy.minimumMargin) || policy.minimumMargin <= 0
|
|
7
|
+
|| !Number.isSafeInteger(policy.maximumCandidates) || policy.maximumCandidates < 1 || policy.maximumCandidates > 100
|
|
8
|
+
|| !Number.isSafeInteger(policy.maximumTextBytes) || policy.maximumTextBytes < 1 || policy.maximumTextBytes > 1_000_000) {
|
|
9
|
+
throw new Error('Invalid discovery ambiguity policy');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Retrieval order is authoritative, including ties; this function never sorts it. */
|
|
13
|
+
export function discoveryAmbiguity(query, candidates, policy) {
|
|
14
|
+
validatePolicy(policy);
|
|
15
|
+
const answer = (reason, eligible = false) => ({ version: policy.version, eligible, reason });
|
|
16
|
+
if (typeof query !== 'string' || !query.trim() || !Array.isArray(candidates)
|
|
17
|
+
|| candidates.some(c => !c || typeof c.id !== 'string' || !c.id || typeof c.name !== 'string'
|
|
18
|
+
|| typeof c.type !== 'string' || typeof c.capability !== 'string' || !finite(c.score))
|
|
19
|
+
|| new Set(candidates.map(c => c.id)).size !== candidates.length
|
|
20
|
+
|| candidates.some((c, i) => i > 0 && c.score > candidates[i - 1].score)
|
|
21
|
+
|| Buffer.byteLength(query + candidates.map(c => c.name + c.type + c.capability + c.id).join('')) > policy.maximumTextBytes) {
|
|
22
|
+
return answer('invalid-input');
|
|
23
|
+
}
|
|
24
|
+
if (!candidates.length)
|
|
25
|
+
return answer('no-candidates');
|
|
26
|
+
const normalized = query.trim().toLowerCase();
|
|
27
|
+
if (candidates.some(c => c.name.trim().toLowerCase() === normalized || c.id.toLowerCase() === normalized))
|
|
28
|
+
return answer('exact-name');
|
|
29
|
+
const first = candidates[0];
|
|
30
|
+
if (first.score < policy.minimumScore)
|
|
31
|
+
return answer('low-score', true);
|
|
32
|
+
const margin = first.score - (candidates[1]?.score ?? 0);
|
|
33
|
+
if (margin >= policy.minimumMargin)
|
|
34
|
+
return answer('high-margin');
|
|
35
|
+
return answer(candidates[1] && first.type !== candidates[1].type ? 'cross-type' : 'low-margin', true);
|
|
36
|
+
}
|
|
37
|
+
const digest = (value) => `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`;
|
|
38
|
+
const samePin = (a, b) => canonicalJson(a) === canonicalJson(b);
|
|
39
|
+
/** One host-owned circuit per workload. Drift/quality controls can trip it without changing
|
|
40
|
+
* any index or active decision pins. Re-enabling requires a newly constructed route. */
|
|
41
|
+
export class DiscoveryShadowRoute {
|
|
42
|
+
host;
|
|
43
|
+
policy;
|
|
44
|
+
stopped = false;
|
|
45
|
+
active = new Set();
|
|
46
|
+
constructor(policy, host) {
|
|
47
|
+
this.host = host;
|
|
48
|
+
validatePolicy(policy.ambiguity);
|
|
49
|
+
if (!['disabled', 'shadow'].includes(policy.mode) || !policy.allowedModels.length
|
|
50
|
+
|| !policy.uncertaintyProfile || !/^sha256:[0-9a-f]{64}$/.test(policy.calibrationDigest)
|
|
51
|
+
|| ![policy.minimumProbability, policy.minimumMargin].every(n => finite(n) && n <= 1)
|
|
52
|
+
|| !Number.isSafeInteger(policy.maximumLatencyMs) || policy.maximumLatencyMs < 1
|
|
53
|
+
|| !Number.isSafeInteger(policy.maximumTokens) || policy.maximumTokens < 1
|
|
54
|
+
|| !finite(policy.maximumCostUsd)
|
|
55
|
+
|| ![policy.definition, policy.binding].every(pin => pin && typeof pin.id === 'string' && pin.id.length > 0
|
|
56
|
+
&& typeof pin.version === 'string' && pin.version.length > 0 && /^sha256:[0-9a-f]{64}$/.test(pin.digest))
|
|
57
|
+
|| policy.allowedModels.some(model => typeof model !== 'string' || !model))
|
|
58
|
+
throw new Error('Invalid discovery shadow policy');
|
|
59
|
+
this.policy = structuredClone(policy);
|
|
60
|
+
}
|
|
61
|
+
disable() { this.stopped = true; for (const controller of this.active)
|
|
62
|
+
controller.abort(); }
|
|
63
|
+
get disabled() { return this.stopped || this.policy.mode === 'disabled'; }
|
|
64
|
+
/** Return metadata via the governed sink. Caller results always retain baseline order. */
|
|
65
|
+
async observe(query, baseline) {
|
|
66
|
+
const p = this.policy;
|
|
67
|
+
const gate = discoveryAmbiguity(query, baseline, p.ambiguity);
|
|
68
|
+
const selected = gate.reason === 'invalid-input' ? [] : baseline.slice(0, p.ambiguity.maximumCandidates);
|
|
69
|
+
const projection = {
|
|
70
|
+
query,
|
|
71
|
+
candidates: selected.map((c, i) => ({ option: `candidate_${i}`, id: c.id, name: c.name, type: c.type, capability: c.capability })),
|
|
72
|
+
options: [...selected.map((_, i) => `candidate_${i}`), 'none'],
|
|
73
|
+
definition: structuredClone(p.definition), binding: structuredClone(p.binding),
|
|
74
|
+
};
|
|
75
|
+
const receipt = {
|
|
76
|
+
schemaVersion: 'discovery-shadow/v1', route: 'deterministic-only', reason: 'privacy-denied',
|
|
77
|
+
ambiguityReason: gate.reason, candidatesDigest: digest(selected.map(c => c.id)), policyDigest: digest(p),
|
|
78
|
+
selection: null, resultDigest: null, servedModel: null, latencyMs: 0, usage: { tokens: null, costUsd: null },
|
|
79
|
+
};
|
|
80
|
+
const record = () => { try {
|
|
81
|
+
this.host.record(structuredClone(receipt));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
this.disable();
|
|
85
|
+
} };
|
|
86
|
+
if (this.disabled || !gate.eligible) {
|
|
87
|
+
receipt.reason = this.stopped ? 'circuit-open' : p.mode === 'disabled' ? 'disabled' : gate.reason;
|
|
88
|
+
record();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
let authorized = false;
|
|
92
|
+
try {
|
|
93
|
+
authorized = this.host.authorize(structuredClone(projection)) === true;
|
|
94
|
+
}
|
|
95
|
+
catch { /* fail closed */ }
|
|
96
|
+
if (!authorized) {
|
|
97
|
+
record();
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const clock = this.host.clock ?? Date.now;
|
|
101
|
+
const start = clock();
|
|
102
|
+
if (!Number.isFinite(start)) {
|
|
103
|
+
this.disable();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const controller = new AbortController();
|
|
107
|
+
this.active.add(controller);
|
|
108
|
+
let timedOut = false;
|
|
109
|
+
let onAbort;
|
|
110
|
+
let timer;
|
|
111
|
+
try {
|
|
112
|
+
const timeout = new Promise((_, reject) => {
|
|
113
|
+
timer = setTimeout(() => { timedOut = true; controller.abort(); reject(new Error('deadline')); }, p.maximumLatencyMs);
|
|
114
|
+
});
|
|
115
|
+
const cancelled = new Promise((_, reject) => {
|
|
116
|
+
onAbort = () => reject(new Error('cancelled'));
|
|
117
|
+
controller.signal.addEventListener('abort', onAbort, { once: true });
|
|
118
|
+
});
|
|
119
|
+
receipt.route = 'shadow';
|
|
120
|
+
const response = await Promise.race([this.host.evaluate({ ...structuredClone(projection), signal: controller.signal }), timeout, cancelled]);
|
|
121
|
+
if (this.stopped)
|
|
122
|
+
throw new Error('circuit-open');
|
|
123
|
+
const elapsed = clock() - start;
|
|
124
|
+
receipt.latencyMs = finite(elapsed) ? elapsed : p.maximumLatencyMs;
|
|
125
|
+
if (!finite(elapsed) || elapsed >= p.maximumLatencyMs) {
|
|
126
|
+
timedOut = true;
|
|
127
|
+
controller.abort();
|
|
128
|
+
throw new Error('deadline');
|
|
129
|
+
}
|
|
130
|
+
const r = response.result.spec;
|
|
131
|
+
receipt.resultDigest = /^sha256:[0-9a-f]{64}$/.test(response.receiptDigest) ? response.receiptDigest : null;
|
|
132
|
+
const attempts = r.attempts;
|
|
133
|
+
// Shared request accounting must remain at its owner; a per-item route cannot charge it again.
|
|
134
|
+
const ordinary = attempts.length > 0 && attempts.every(a => !a.batch);
|
|
135
|
+
const tokenKnown = ordinary && attempts.every(a => Number.isSafeInteger(a.usage.inputTokens) && a.usage.inputTokens >= 0
|
|
136
|
+
&& Number.isSafeInteger(a.usage.outputTokens) && a.usage.outputTokens >= 0);
|
|
137
|
+
const costKnown = ordinary && attempts.every(a => a.usage.costUsd !== null && finite(a.usage.costUsd));
|
|
138
|
+
receipt.usage.tokens = tokenKnown ? attempts.reduce((n, a) => n + a.usage.inputTokens + a.usage.outputTokens, 0) : null;
|
|
139
|
+
receipt.usage.costUsd = costKnown ? attempts.reduce((n, a) => n + a.usage.costUsd, 0) : null;
|
|
140
|
+
if (receipt.usage.tokens === null || receipt.usage.costUsd === null || !Number.isSafeInteger(receipt.usage.tokens)
|
|
141
|
+
|| receipt.usage.tokens > p.maximumTokens || receipt.usage.costUsd > p.maximumCostUsd) {
|
|
142
|
+
receipt.reason = 'budget-breach';
|
|
143
|
+
this.disable();
|
|
144
|
+
record();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const last = attempts.at(-1);
|
|
148
|
+
const uncertainty = r.uncertainty;
|
|
149
|
+
const distribution = uncertainty?.distribution;
|
|
150
|
+
const probabilities = distribution && projection.options.every(option => Object.hasOwn(distribution, option))
|
|
151
|
+
&& Object.keys(distribution).length === projection.options.length ? Object.values(distribution) : [];
|
|
152
|
+
const knownModel = last.actualModel !== null && p.allowedModels.includes(last.actualModel);
|
|
153
|
+
receipt.servedModel = knownModel ? last.actualModel : null;
|
|
154
|
+
const option = r.value;
|
|
155
|
+
let accept = r.status === 'success' && samePin(r.decision, p.definition) && samePin(r.binding, p.binding)
|
|
156
|
+
&& knownModel && receipt.resultDigest !== null && r.acceptance?.disposition === 'act'
|
|
157
|
+
&& r.calibrationCompatibility?.action === 'allow' && ['exact', 'approved-compatible'].includes(r.calibrationCompatibility.state)
|
|
158
|
+
&& r.calibrationCompatibility.artifactDigest === p.calibrationDigest
|
|
159
|
+
&& r.calibrationCompatibility.actualModel === last.actualModel
|
|
160
|
+
&& r.calibrationCompatibility.runId === `${r.runId}:${r.invocationId}:${r.alias}`
|
|
161
|
+
&& uncertainty?.source === 'provider' && uncertainty.profile === p.uncertaintyProfile
|
|
162
|
+
&& probabilities.length > 0 && probabilities.every(v => finite(v) && v <= 1)
|
|
163
|
+
&& Math.abs(probabilities.reduce((sum, v) => sum + v, 0) - 1) < 1e-6
|
|
164
|
+
&& typeof option === 'string' && projection.options.includes(option);
|
|
165
|
+
if (accept && distribution && typeof option === 'string') {
|
|
166
|
+
const probability = distribution[option];
|
|
167
|
+
const competitor = Math.max(...Object.entries(distribution).filter(([key]) => key !== option).map(([, value]) => value));
|
|
168
|
+
accept = probability > competitor && probability >= p.minimumProbability && probability - competitor >= p.minimumMargin;
|
|
169
|
+
}
|
|
170
|
+
receipt.reason = accept ? 'accepted-shadow' : 'rejected-evidence';
|
|
171
|
+
if (!accept)
|
|
172
|
+
this.disable(); // quality/model/calibration breach restores deterministic-only routing
|
|
173
|
+
if (accept)
|
|
174
|
+
receipt.selection = option === 'none' ? { kind: 'none' } : { kind: 'candidate', ordinal: projection.options.indexOf(option) };
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
receipt.reason = timedOut ? 'budget-breach' : this.stopped ? 'circuit-open' : 'evaluation-failed';
|
|
178
|
+
const elapsed = clock() - start;
|
|
179
|
+
receipt.latencyMs = timedOut ? Math.max(p.maximumLatencyMs, finite(elapsed) ? elapsed : 0) : finite(elapsed) ? elapsed : 0;
|
|
180
|
+
receipt.selection = null;
|
|
181
|
+
if (timedOut)
|
|
182
|
+
this.disable();
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
if (timer)
|
|
186
|
+
clearTimeout(timer);
|
|
187
|
+
if (onAbort)
|
|
188
|
+
controller.signal.removeEventListener('abort', onAbort);
|
|
189
|
+
this.active.delete(controller);
|
|
190
|
+
}
|
|
191
|
+
record();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export { DiscoveryShadowEvaluator } from './discovery-shadow-evaluator.js';
|
|
195
|
+
//# sourceMappingURL=discovery-shadow.js.map
|
|
@@ -168,7 +168,11 @@ export function loadFortemiCoreExport(cwd, graph = "project", sourcePaths) {
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
let status = getFortemiCoreSyncStatus(cwd, graph);
|
|
171
|
-
|
|
171
|
+
// The packaged framework index is a bootstrap fallback for an absent shared
|
|
172
|
+
// cache. Once a shared cache exists, its stale state proves that the local
|
|
173
|
+
// source index is newer; silently replacing it with an older packaged index
|
|
174
|
+
// would hide newly authored capabilities.
|
|
175
|
+
if ((!status.optedIn || !status.built) && !status.stale && graph === "framework") {
|
|
172
176
|
const prebuilt = getFortemiCorePrebuiltStatus(graph);
|
|
173
177
|
if (prebuilt.optedIn && prebuilt.built && !prebuilt.stale) {
|
|
174
178
|
status = prebuilt;
|
|
@@ -182,7 +186,7 @@ export function loadFortemiCoreExport(cwd, graph = "project", sourcePaths) {
|
|
|
182
186
|
if (!status.built || status.stale) {
|
|
183
187
|
return {
|
|
184
188
|
reason: `${status.reason ??
|
|
185
|
-
`Fortemi Core static index for graph '${graph}' is stale or incomplete`}. Re-run 'aiwg index sync' or pass '--backend local' to use the legacy local index.`,
|
|
189
|
+
`Fortemi Core static index for graph '${graph}' is stale or incomplete`}. Re-run 'aiwg index sync${graph === "project" ? "" : ` --graph ${graph}`}' or pass '--backend local' to use the legacy local index.`,
|
|
186
190
|
};
|
|
187
191
|
}
|
|
188
192
|
try {
|
|
@@ -15,6 +15,7 @@ import { createHash } from 'crypto';
|
|
|
15
15
|
import { load as loadYaml } from 'js-yaml';
|
|
16
16
|
import { validateDecisionDocument } from '../decision/validate.js';
|
|
17
17
|
import { parseDecisionJson, parseDecisionYaml } from '../decision/entry.js';
|
|
18
|
+
import { validateGateDocument } from '../gates/schema.js';
|
|
18
19
|
import { DEFAULT_INDEX_EXTENSIONS, INDEX_EXTRACTOR_VERSION, INDEX_VERSION, INDEX_DIR, OPERATIONAL_DISCOVERY_TYPES, PHASE_DIRECTORIES, GRAPH_CONFIGS, TEMPLATE_INDEX_EXTENSIONS, resolveGraphScanDir, loadUserGraphConfigs, loadGlobalGraphConfigs, } from './types.js';
|
|
19
20
|
import { parseCitationSidecar, citationResultToEdges, buildRefToPathMap } from './citation-parser.js';
|
|
20
21
|
import { writeIndexFile, resolveIndexDir, loadGraphIndexFile } from './index-reader.js';
|
|
@@ -197,6 +198,42 @@ export function parseDecisionDoc(content, relativePath) {
|
|
|
197
198
|
capability: purpose.slice(0, 240), tags: ['decision-system', value.kind], searchTerms: [...terms],
|
|
198
199
|
};
|
|
199
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Classify and schema-check authored gate-pack documents (#2830).
|
|
203
|
+
*
|
|
204
|
+
* Packs live in `<bundle>/gate-packs/*.gatepack.yaml|json` (never `gates/`,
|
|
205
|
+
* which agent-persistence HITL uses). The `.gatepack.` extension is the
|
|
206
|
+
* boundary: HITL `gates/*.yaml` files must never classify as `gate-pack`.
|
|
207
|
+
* Invalid packs return null so they are never discoverable; the bundle
|
|
208
|
+
* loader rejects them at load with a diagnostic.
|
|
209
|
+
*/
|
|
210
|
+
export function parseGatePackDoc(content, relativePath) {
|
|
211
|
+
if (!/\.gatepack\.(json|ya?ml)$/i.test(relativePath))
|
|
212
|
+
return null;
|
|
213
|
+
if (Buffer.byteLength(content, 'utf8') > 262_144)
|
|
214
|
+
return null;
|
|
215
|
+
let document;
|
|
216
|
+
try {
|
|
217
|
+
document = /\.json$/i.test(relativePath) ? parseDecisionJson(content) : parseDecisionYaml(content);
|
|
218
|
+
validateGateDocument(document);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
const value = document;
|
|
224
|
+
if (value.kind !== 'GatePack')
|
|
225
|
+
return null;
|
|
226
|
+
const description = value.metadata.description;
|
|
227
|
+
const terms = new Set([
|
|
228
|
+
value.kind, value.metadata.id, value.metadata.version, description,
|
|
229
|
+
...(value.spec.gates ?? []).map(gate => String(gate.id ?? '')).filter(Boolean),
|
|
230
|
+
...Object.keys(value.spec.metrics ?? {}),
|
|
231
|
+
]);
|
|
232
|
+
return {
|
|
233
|
+
type: 'gate-pack', kind: 'GatePack', name: value.metadata.id, description,
|
|
234
|
+
capability: description.slice(0, 240), tags: ['gates', value.kind], searchTerms: [...terms],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
200
237
|
function parseSchemaDoc(content, relativePath) {
|
|
201
238
|
if (!/\.(json|ya?ml)$/i.test(relativePath))
|
|
202
239
|
return null;
|
|
@@ -964,15 +1001,16 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
964
1001
|
// become discoverable (#1540).
|
|
965
1002
|
const flow = parseFlowDoc(content, relativePath);
|
|
966
1003
|
const decision = flow ? null : parseDecisionDoc(content, relativePath);
|
|
1004
|
+
const gatePack = flow || decision ? null : parseGatePackDoc(content, relativePath);
|
|
967
1005
|
const inferredType = inferType(data, relativePath);
|
|
968
1006
|
const physicalType = inferType({ ...data, type: undefined }, relativePath);
|
|
969
|
-
const runbook = flow || decision ? null : parseRunbookDoc(data, body, relativePath);
|
|
1007
|
+
const runbook = flow || decision || gatePack ? null : parseRunbookDoc(data, body, relativePath);
|
|
970
1008
|
const schemaDoc = inferredType === 'schema' ? parseSchemaDoc(content, relativePath) : null;
|
|
971
|
-
const title = flow?.name ?? decision?.name ?? schemaDoc?.title ?? extractTitle(data, body);
|
|
1009
|
+
const title = flow?.name ?? decision?.name ?? gatePack?.name ?? schemaDoc?.title ?? extractTitle(data, body);
|
|
972
1010
|
const phase = typeof data.phase === 'string' ? data.phase : inferPhase(relativePath);
|
|
973
|
-
const type = flow?.type ?? decision?.type ?? (runbook ? 'runbook' : inferredType);
|
|
974
|
-
const tags = flow?.tags ?? decision?.tags ?? (Array.isArray(data.tags) ? data.tags.map(String) : []);
|
|
975
|
-
const summary = flow?.description ?? decision?.description ?? schemaDoc?.capability ?? runbook?.capability ?? extractSummary(data, body);
|
|
1011
|
+
const type = flow?.type ?? decision?.type ?? gatePack?.type ?? (runbook ? 'runbook' : inferredType);
|
|
1012
|
+
const tags = flow?.tags ?? decision?.tags ?? gatePack?.tags ?? (Array.isArray(data.tags) ? data.tags.map(String) : []);
|
|
1013
|
+
const summary = flow?.description ?? decision?.description ?? gatePack?.description ?? schemaDoc?.capability ?? runbook?.capability ?? extractSummary(data, body);
|
|
976
1014
|
const dependencies = extractMentions(content);
|
|
977
1015
|
const markdownLinks = extractMarkdownLinks(content);
|
|
978
1016
|
// Discovery metadata (#1214, #1540, #1792) — meaningful for operational
|
|
@@ -982,10 +1020,10 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
982
1020
|
// Declarative processes have no trigger phrases — they rely on their
|
|
983
1021
|
// capability and structure-aware search terms.
|
|
984
1022
|
const triggers = isDiscoverable && !flow ? extractTriggers(body, data) : undefined;
|
|
985
|
-
const capability = flow?.capability ?? decision?.capability ?? schemaDoc?.capability ?? runbook?.capability ?? (isDiscoverable ? extractCapability(data, body) : undefined);
|
|
986
|
-
const kind = flow?.kind ?? decision?.kind ?? runbook?.kind;
|
|
1023
|
+
const capability = flow?.capability ?? decision?.capability ?? gatePack?.capability ?? schemaDoc?.capability ?? runbook?.capability ?? (isDiscoverable ? extractCapability(data, body) : undefined);
|
|
1024
|
+
const kind = flow?.kind ?? decision?.kind ?? gatePack?.kind ?? runbook?.kind;
|
|
987
1025
|
const sourceType = runbook && physicalType !== 'runbook' ? physicalType : undefined;
|
|
988
|
-
const searchTerms = flow?.searchTerms ?? decision?.searchTerms ?? schemaDoc?.searchTerms ?? runbook?.searchTerms;
|
|
1026
|
+
const searchTerms = flow?.searchTerms ?? decision?.searchTerms ?? gatePack?.searchTerms ?? schemaDoc?.searchTerms ?? runbook?.searchTerms;
|
|
989
1027
|
const kernel = data.kernel === true || data.kernel === 'true' ? true : undefined;
|
|
990
1028
|
// Script entrypoint metadata is meaningful for skills only (#1227).
|
|
991
1029
|
const script = type === 'skill' ? extractSkillScript(data) : undefined;
|
|
@@ -994,7 +1032,7 @@ export async function buildIndex(cwd, options = {}) {
|
|
|
994
1032
|
// Canonical short name (#1233) — used by the scorer to floor exact-name
|
|
995
1033
|
// queries to 1.0 so hyphenated kernel-skill names like `aiwg-doctor`
|
|
996
1034
|
// remain searchable even when the rendered title strips the hyphen.
|
|
997
|
-
const name = flow?.name ?? decision?.name ?? schemaDoc?.name ?? (isDiscoverable ? extractCanonicalName(data, relativePath) : undefined);
|
|
1035
|
+
const name = flow?.name ?? decision?.name ?? gatePack?.name ?? schemaDoc?.name ?? (isDiscoverable ? extractCanonicalName(data, relativePath) : undefined);
|
|
998
1036
|
entry = {
|
|
999
1037
|
path: relativePath,
|
|
1000
1038
|
type,
|