@aiwg/cli 2026.9.24 → 2026.10.1
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/discovery-shadow-evaluator.js +177 -0
- package/dist/src/artifacts/discovery-shadow.js +195 -0
- package/dist/src/artifacts/index-builder.js +47 -9
- package/dist/src/artifacts/query-engine.js +94 -1
- package/dist/src/artifacts/types.js +1 -0
- package/dist/src/cli/handlers/decision.js +177 -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 +29 -3
- package/dist/src/decision/ask.js +352 -0
- 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 +432 -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 +13 -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/jev-credentials.js +121 -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/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 +228 -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 +40 -18
- 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:
|
|
@@ -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
|
|
@@ -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,
|
|
@@ -1142,6 +1142,19 @@ export async function discoverCapability(cwd, params) {
|
|
|
1142
1142
|
}
|
|
1143
1143
|
const scored = dedupeDiscoverResults(await applyFacetFusion(Array.from(combinedByPath.values()).sort(compareDiscoverResults), candidates, params.phrase)).slice(0, limit);
|
|
1144
1144
|
const relaxed = scored.some(result => !strictPaths.has(result.entry.path));
|
|
1145
|
+
if (params.shadowRoute) {
|
|
1146
|
+
try {
|
|
1147
|
+
await params.shadowRoute.observe(params.phrase, scored.map(result => ({
|
|
1148
|
+
id: discoveryIdForEntry(result.entry), name: result.entry.name ?? result.entry.title,
|
|
1149
|
+
type: result.entry.type, capability: result.entry.capability ?? result.entry.summary,
|
|
1150
|
+
score: result.score,
|
|
1151
|
+
})));
|
|
1152
|
+
}
|
|
1153
|
+
catch {
|
|
1154
|
+
// An optional observation failure must never replace deterministic discovery.
|
|
1155
|
+
params.shadowRoute.disable();
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1145
1158
|
const queryTimeMs = Date.now() - startTime;
|
|
1146
1159
|
/**
|
|
1147
1160
|
* Resolve a stored framework-graph path to an absolute AIWG_ROOT
|
|
@@ -1474,7 +1487,15 @@ function findShowMatches(entries, types, needle) {
|
|
|
1474
1487
|
const slugLayoutMatch = ((basename === 'SKILL.md' && e.type === 'skill') ||
|
|
1475
1488
|
(basename === 'BEHAVIOR.md' && e.type === 'behavior')) &&
|
|
1476
1489
|
dirStem === needle;
|
|
1477
|
-
|
|
1490
|
+
if (slugLayoutMatch || fileStem === needle || e.name === needle)
|
|
1491
|
+
return true;
|
|
1492
|
+
if (e.type === 'gate-pack' && typeof e.name === 'string') {
|
|
1493
|
+
const gateStem = fileStem.replace(/\.gatepack$/i, '');
|
|
1494
|
+
const rest = e.name.includes(':') ? e.name.slice(e.name.indexOf(':') + 1) : e.name;
|
|
1495
|
+
const short = rest.includes('/') ? rest.slice(rest.indexOf('/') + 1) : rest;
|
|
1496
|
+
return gateStem === needle || rest === needle || short === needle;
|
|
1497
|
+
}
|
|
1498
|
+
return false;
|
|
1478
1499
|
});
|
|
1479
1500
|
}
|
|
1480
1501
|
if (matches.length === 0) {
|
|
@@ -1526,6 +1547,68 @@ async function fortemiRecordForEntry(cwd, entry, sourcePaths) {
|
|
|
1526
1547
|
const id = discoveryIdForEntry(entry);
|
|
1527
1548
|
return loaded.exported.items.find((record) => record.id === id || record.source.path === entry.path) ?? null;
|
|
1528
1549
|
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Scan bundle `gate-packs/` directories for a pack matching `needle` (#2830).
|
|
1552
|
+
*
|
|
1553
|
+
* Accepts the full namespaced id, the rest-path or the short pack name and
|
|
1554
|
+
* probes `<bundle>/gate-packs/<pack>.gatepack.{yaml,yml,json}`. Never looks
|
|
1555
|
+
* under `gates/`, which agent-persistence HITL owns.
|
|
1556
|
+
*/
|
|
1557
|
+
async function findGatePackInCorpus(groups, needle) {
|
|
1558
|
+
const path = await import('node:path');
|
|
1559
|
+
const fsp = (await import('node:fs')).promises;
|
|
1560
|
+
const trimmed = needle.trim();
|
|
1561
|
+
const rest = trimmed.includes(':') ? trimmed.slice(trimmed.indexOf(':') + 1) : trimmed;
|
|
1562
|
+
const slash = rest.indexOf('/');
|
|
1563
|
+
const wantedBundle = slash >= 0 ? rest.slice(0, slash) : null;
|
|
1564
|
+
const wantedPack = slash >= 0 ? rest.slice(slash + 1) : rest;
|
|
1565
|
+
if (!wantedPack || /\s/.test(wantedPack) || wantedPack.includes(':') || wantedPack.includes('/'))
|
|
1566
|
+
return null;
|
|
1567
|
+
const extensions = ['.gatepack.yaml', '.gatepack.yml', '.gatepack.json'];
|
|
1568
|
+
const ordered = wantedBundle
|
|
1569
|
+
? groups.flatMap(group => [{ group, bundle: wantedBundle }])
|
|
1570
|
+
: null;
|
|
1571
|
+
const probe = async (group, bundle) => {
|
|
1572
|
+
for (const extension of extensions) {
|
|
1573
|
+
const candidate = path.join(group.dir, bundle, 'gate-packs', `${wantedPack}${extension}`);
|
|
1574
|
+
try {
|
|
1575
|
+
const stat = await fsp.stat(candidate);
|
|
1576
|
+
if (stat.isFile())
|
|
1577
|
+
return { path: candidate, bundleKind: group.kind, bundleId: bundle };
|
|
1578
|
+
}
|
|
1579
|
+
catch {
|
|
1580
|
+
// not present — continue
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
return null;
|
|
1584
|
+
};
|
|
1585
|
+
if (ordered) {
|
|
1586
|
+
for (const { group, bundle } of ordered) {
|
|
1587
|
+
const found = await probe(group, bundle);
|
|
1588
|
+
if (found)
|
|
1589
|
+
return found;
|
|
1590
|
+
}
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1593
|
+
for (const group of groups) {
|
|
1594
|
+
let bundles;
|
|
1595
|
+
try {
|
|
1596
|
+
bundles = (await fsp.readdir(group.dir, { withFileTypes: true }))
|
|
1597
|
+
.filter(d => d.isDirectory())
|
|
1598
|
+
.map(d => d.name)
|
|
1599
|
+
.sort();
|
|
1600
|
+
}
|
|
1601
|
+
catch {
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
for (const bundle of bundles) {
|
|
1605
|
+
const found = await probe(group, bundle);
|
|
1606
|
+
if (found)
|
|
1607
|
+
return found;
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
return null;
|
|
1611
|
+
}
|
|
1529
1612
|
/**
|
|
1530
1613
|
* Scan the AIWG_ROOT corpus for an artifact matching `name` (#1221).
|
|
1531
1614
|
*
|
|
@@ -1592,6 +1675,16 @@ async function findCorpusArtifact(aiwgRoot, name, typeFilter) {
|
|
|
1592
1675
|
}
|
|
1593
1676
|
}
|
|
1594
1677
|
}
|
|
1678
|
+
// Gate packs live in `<bundle>/gate-packs/*.gatepack.yaml|json` (never
|
|
1679
|
+
// `gates/`, which agent-persistence HITL uses). The needle may be the full
|
|
1680
|
+
// namespaced id (`aiwg:decision-engine/integrity-ceiling`), the rest-path
|
|
1681
|
+
// (`decision-engine/integrity-ceiling`) or the short pack name
|
|
1682
|
+
// (`integrity-ceiling`). (#2830)
|
|
1683
|
+
if (typeFilter.length === 0 || typeFilter.includes('gate-pack')) {
|
|
1684
|
+
const gateMatch = await findGatePackInCorpus(groups, name);
|
|
1685
|
+
if (gateMatch)
|
|
1686
|
+
return { ...gateMatch, type: 'gate-pack' };
|
|
1687
|
+
}
|
|
1595
1688
|
// Top-level agents live at `agentic/code/agents/<category>/<name>.md`
|
|
1596
1689
|
// (e.g. `personas/aiwg-writer.md`) — a flat-under-category layout the
|
|
1597
1690
|
// bundle groups above do not cover. Scan it directly so persona/agent
|