@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.
Files changed (209) hide show
  1. package/agentic/code/providers/capability-matrix.yaml +2 -1
  2. package/dist/src/artifacts/discovery-shadow-evaluator.js +177 -0
  3. package/dist/src/artifacts/discovery-shadow.js +195 -0
  4. package/dist/src/artifacts/index-builder.js +47 -9
  5. package/dist/src/artifacts/query-engine.js +94 -1
  6. package/dist/src/artifacts/types.js +1 -0
  7. package/dist/src/cli/handlers/decision.js +177 -0
  8. package/dist/src/cli/handlers/gates.js +154 -0
  9. package/dist/src/cli/handlers/index.js +5 -1
  10. package/dist/src/cli/handlers/sessions.js +10 -7
  11. package/dist/src/cli/handlers/utilities.js +20 -0
  12. package/dist/src/config/aiwg-config.js +15 -2
  13. package/dist/src/decision/acceptance.js +25 -0
  14. package/dist/src/decision/adapters/jev.js +29 -3
  15. package/dist/src/decision/ask.js +352 -0
  16. package/dist/src/decision/calibration/registry.js +9 -0
  17. package/dist/src/decision/compose.js +74 -15
  18. package/dist/src/decision/context-live-promotion.js +344 -0
  19. package/dist/src/decision/context-live-qualification.js +591 -0
  20. package/dist/src/decision/context-pruning.js +847 -0
  21. package/dist/src/decision/driver.js +432 -0
  22. package/dist/src/decision/egress-live-qualification.js +874 -0
  23. package/dist/src/decision/ensemble/contract.js +64 -1
  24. package/dist/src/decision/ensemble/index.js +1 -0
  25. package/dist/src/decision/ensemble/report.js +8 -42
  26. package/dist/src/decision/ensemble/runtime.js +571 -0
  27. package/dist/src/decision/ensemble-study/artifacts.js +85 -0
  28. package/dist/src/decision/ensemble-study/calibration.js +443 -0
  29. package/dist/src/decision/ensemble-study/corpus.js +229 -0
  30. package/dist/src/decision/ensemble-study/multifact-artifacts.js +23 -0
  31. package/dist/src/decision/ensemble-study/multifact-study.js +596 -0
  32. package/dist/src/decision/ensemble-study/multifact.js +241 -0
  33. package/dist/src/decision/ensemble-study/protocol.js +77 -0
  34. package/dist/src/decision/ensemble-study/score.js +462 -0
  35. package/dist/src/decision/ensemble-study/staged.js +87 -0
  36. package/dist/src/decision/ensemble-study/statistics.js +84 -0
  37. package/dist/src/decision/evaluate.js +227 -14
  38. package/dist/src/decision/export.js +4 -0
  39. package/dist/src/decision/feature-export.js +1074 -0
  40. package/dist/src/decision/graph-index.js +16 -0
  41. package/dist/src/decision/graph-live-qualification.js +1041 -0
  42. package/dist/src/decision/graph-live-workload.js +245 -0
  43. package/dist/src/decision/graph-skill-bridge.js +21 -3
  44. package/dist/src/decision/heldout/calibration.js +140 -0
  45. package/dist/src/decision/heldout/collector.js +430 -0
  46. package/dist/src/decision/heldout/contract.js +309 -0
  47. package/dist/src/decision/heldout/d29-generator-ids.js +12 -0
  48. package/dist/src/decision/heldout/d29-passage-baseline-v2.js +93 -0
  49. package/dist/src/decision/heldout/d29-passage-baseline-v3.js +100 -0
  50. package/dist/src/decision/heldout/d29-passage-baseline.js +66 -0
  51. package/dist/src/decision/heldout/d29-pools-v8.js +575 -0
  52. package/dist/src/decision/heldout/d29-pools.js +288 -0
  53. package/dist/src/decision/heldout/d29-v6.js +169 -0
  54. package/dist/src/decision/heldout/d29-v7.js +202 -0
  55. package/dist/src/decision/heldout/d29-v8.js +586 -0
  56. package/dist/src/decision/heldout/generator-registry.js +67 -0
  57. package/dist/src/decision/heldout/generators.js +696 -0
  58. package/dist/src/decision/heldout/index.js +8 -0
  59. package/dist/src/decision/heldout/journal.js +346 -0
  60. package/dist/src/decision/heldout/study-generators.js +64 -0
  61. package/dist/src/decision/heldout/types.js +2 -0
  62. package/dist/src/decision/host-policy.js +61 -0
  63. package/dist/src/decision/index.js +13 -0
  64. package/dist/src/decision/interop-profile.v1.json +112 -0
  65. package/dist/src/decision/interop.js +653 -0
  66. package/dist/src/decision/issue-triage/index.js +3 -0
  67. package/dist/src/decision/issue-triage/runtime.js +1069 -0
  68. package/dist/src/decision/issue-triage/types.js +2 -0
  69. package/dist/src/decision/jev-credentials.js +121 -0
  70. package/dist/src/decision/patterns/catalog.js +28 -8
  71. package/dist/src/decision/patterns/playground.js +28 -11
  72. package/dist/src/decision/patterns/specs.js +19 -10
  73. package/dist/src/decision/preprocessed-evidence.js +724 -0
  74. package/dist/src/decision/qualification/capture.js +8 -2
  75. package/dist/src/decision/qualification/gate-evidence.js +19 -4
  76. package/dist/src/decision/qualification/quality.js +57 -49
  77. package/dist/src/decision/qualification/release.js +76 -3
  78. package/dist/src/decision/qualification/runner.js +5 -2
  79. package/dist/src/decision/routing/contract.js +175 -0
  80. package/dist/src/decision/routing/index.js +5 -0
  81. package/dist/src/decision/routing/report.js +216 -0
  82. package/dist/src/decision/routing/runtime.js +685 -0
  83. package/dist/src/decision/routing/types.js +2 -0
  84. package/dist/src/decision/sdlc-screening.js +1157 -0
  85. package/dist/src/decision/sensitivity/comparative.js +154 -0
  86. package/dist/src/decision/sensitivity/contract.js +218 -0
  87. package/dist/src/decision/sensitivity/index.js +5 -0
  88. package/dist/src/decision/sensitivity/runtime.js +912 -0
  89. package/dist/src/decision/sensitivity/types.js +2 -0
  90. package/dist/src/decision/validate.js +26 -0
  91. package/dist/src/extensions/commands/definitions.js +38 -0
  92. package/dist/src/extensions/manifest.js +44 -0
  93. package/dist/src/gates/discovery.js +315 -0
  94. package/dist/src/gates/driver.js +388 -0
  95. package/dist/src/gates/evaluate.js +719 -0
  96. package/dist/src/gates/floors.js +255 -0
  97. package/dist/src/gates/index.js +17 -0
  98. package/dist/src/gates/providers/evidence.js +38 -0
  99. package/dist/src/gates/providers/index.js +21 -0
  100. package/dist/src/gates/providers/isolated-child-source.js +446 -0
  101. package/dist/src/gates/providers/loader.js +1069 -0
  102. package/dist/src/gates/providers/paired.js +52 -0
  103. package/dist/src/gates/providers/proportion.js +60 -0
  104. package/dist/src/gates/providers/registry.js +40 -0
  105. package/dist/src/gates/providers/scalar.js +41 -0
  106. package/dist/src/gates/providers/screening.js +53 -0
  107. package/dist/src/gates/providers/types.js +25 -0
  108. package/dist/src/gates/registry.js +1003 -0
  109. package/dist/src/gates/report.js +102 -0
  110. package/dist/src/gates/schema.js +56 -0
  111. package/dist/src/gates/stats/binomial.js +26 -0
  112. package/dist/src/gates/stats/bootstrap.js +62 -0
  113. package/dist/src/gates/stats/clopper-pearson.js +95 -0
  114. package/dist/src/gates/stats/digest.js +26 -0
  115. package/dist/src/gates/stats/error.js +5 -0
  116. package/dist/src/gates/stats/index.js +9 -0
  117. package/dist/src/gates/stats/normal.js +35 -0
  118. package/dist/src/gates/stats/paired.js +120 -0
  119. package/dist/src/gates/stats/splits.js +30 -0
  120. package/dist/src/gates/types.js +8 -0
  121. package/dist/src/governance/redaction.js +14 -4
  122. package/dist/src/mcp/muse-native-config.mjs +123 -0
  123. package/dist/src/mcp/tools/decision.mjs +228 -0
  124. package/dist/src/mcp/tools/subsystems.mjs +2 -0
  125. package/dist/src/providers/capability-matrix.yaml +2 -1
  126. package/dist/src/providers/provider-definitions.js +2 -1
  127. package/dist/src/sessions/adapters/muse.js +336 -39
  128. package/dist/src/sessions/batch-import.js +7 -0
  129. package/dist/src/sessions/workspace-discovery.js +13 -2
  130. package/package.json +1 -1
  131. package/schemas/decision/ConformalFrozenOpenData.v2.schema.json +371 -0
  132. package/schemas/decision/ConformalOpenDataReport.v2.schema.json +421 -0
  133. package/schemas/decision/ConformalPreregistration.v2.schema.json +529 -0
  134. package/schemas/decision/ContextPruningCandidate.v1.schema.json +47 -0
  135. package/schemas/decision/ContextPruningEvaluationReport.v1.schema.json +758 -0
  136. package/schemas/decision/ContextPruningPreregistration.v1.schema.json +175 -0
  137. package/schemas/decision/ContextPruningReceipt.v1.schema.json +72 -0
  138. package/schemas/decision/D17Multifact.v1.schema.json +1851 -0
  139. package/schemas/decision/D17StudyAnalysis.v1.schema.json +42 -0
  140. package/schemas/decision/D17StudyAnalysis.v2.schema.json +51 -0
  141. package/schemas/decision/D17StudyCalibration.v1.schema.json +819 -0
  142. package/schemas/decision/D17StudyDryRun.v1.schema.json +169 -0
  143. package/schemas/decision/D17StudyDryRun.v2.schema.json +239 -0
  144. package/schemas/decision/D17StudyGold.v1.schema.json +108 -0
  145. package/schemas/decision/D17StudyGuide.v1.schema.json +63 -0
  146. package/schemas/decision/D17StudyNativeTemplates.v1.schema.json +619 -0
  147. package/schemas/decision/D17StudyProtocol.v1.schema.json +241 -0
  148. package/schemas/decision/D17StudyReport.v1.schema.json +902 -0
  149. package/schemas/decision/D17StudyReport.v2.schema.json +1319 -0
  150. package/schemas/decision/D17StudyReview.v1.schema.json +175 -0
  151. package/schemas/decision/D17StudySplits.v1.schema.json +217 -0
  152. package/schemas/decision/D29Study.v1.schema.json +1903 -0
  153. package/schemas/decision/D29Study.v2.schema.json +1344 -0
  154. package/schemas/decision/D29Study.v3.schema.json +609 -0
  155. package/schemas/decision/D29Study.v4.schema.json +1133 -0
  156. package/schemas/decision/D29Study.v6.schema.json +1205 -0
  157. package/schemas/decision/D29Study.v7.schema.json +1471 -0
  158. package/schemas/decision/D29Study.v8.schema.json +671 -0
  159. package/schemas/decision/D29Study.v9.schema.json +778 -0
  160. package/schemas/decision/DecisionComparativeReplayAuditSample.v1.schema.json +137 -0
  161. package/schemas/decision/DecisionComparativeReplayCorpus.v1.schema.json +258 -0
  162. package/schemas/decision/DecisionComparativeReplayFreeze.v1.schema.json +74 -0
  163. package/schemas/decision/DecisionComparativeReplayGold.v1.schema.json +50 -0
  164. package/schemas/decision/DecisionComparativeReplayGoldAsset.v1.schema.json +37 -0
  165. package/schemas/decision/DecisionComparativeReplayIntegrity.v1.schema.json +81 -0
  166. package/schemas/decision/DecisionComparativeReplayMembers.v1.schema.json +52 -0
  167. package/schemas/decision/DecisionComparativeReplayPreregistration.v1.schema.json +113 -0
  168. package/schemas/decision/DecisionComparativeReplayReport.v1.schema.json +407 -0
  169. package/schemas/decision/DecisionComparativeReplayReviewPackets.v1.schema.json +260 -0
  170. package/schemas/decision/DecisionComparativeReplaySplit.v1.schema.json +84 -0
  171. package/schemas/decision/DecisionFeatureObservation.v1.schema.json +564 -0
  172. package/schemas/decision/DecisionFeatureSet.v1.schema.json +155 -0
  173. package/schemas/decision/DecisionInteropMapping.v1.schema.json +85 -0
  174. package/schemas/decision/DecisionIssueTriageEvaluationManifest.v1.schema.json +174 -0
  175. package/schemas/decision/DecisionIssueTriageEvaluationReport.v1.schema.json +280 -0
  176. package/schemas/decision/DecisionIssueTriagePilotPack.v1.schema.json +114 -0
  177. package/schemas/decision/DecisionRouteReceipt.v1.schema.json +155 -0
  178. package/schemas/decision/DecisionRoutingPolicy.v1.schema.json +132 -0
  179. package/schemas/decision/DecisionRoutingShadowReport.v1.schema.json +170 -0
  180. package/schemas/decision/DecisionRoutingTask.v1.schema.json +49 -0
  181. package/schemas/decision/DecisionRuleset.schema.json +8 -2
  182. package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +8 -2
  183. package/schemas/decision/DecisionSensitivityPlan.v1.schema.json +191 -0
  184. package/schemas/decision/DecisionSensitivityReport.v1.schema.json +163 -0
  185. package/schemas/decision/HeldoutApproval.v1.schema.json +312 -0
  186. package/schemas/decision/HeldoutAttempt.v1.schema.json +211 -0
  187. package/schemas/decision/HeldoutBaseline.v1.schema.json +90 -0
  188. package/schemas/decision/HeldoutCalibrationPhase.v1.schema.json +87 -0
  189. package/schemas/decision/HeldoutCorpus.v1.schema.json +167 -0
  190. package/schemas/decision/HeldoutEvent.v1.schema.json +41 -0
  191. package/schemas/decision/HeldoutFrozen.v1.schema.json +142 -0
  192. package/schemas/decision/HeldoutPreregistration.v1.schema.json +222 -0
  193. package/schemas/decision/HeldoutSpendEvent.v1.schema.json +122 -0
  194. package/schemas/decision/HeldoutSpendHead.v1.schema.json +25 -0
  195. package/schemas/decision/HeldoutSpendHead.v2.schema.json +46 -0
  196. package/schemas/decision/HeldoutSummary.v1.schema.json +101 -0
  197. package/schemas/decision/OpaDecisionLogExport.v1.schema.json +65 -0
  198. package/schemas/decision/PreprocessedEvidence.v1.schema.json +306 -0
  199. package/schemas/decision/RulesetResult.v1alpha2.schema.json +175 -0
  200. package/schemas/decision/SdlcEvidenceScreening.v1.schema.json +499 -0
  201. package/schemas/decision/SdlcScreeningPreregistration.v1.schema.json +132 -0
  202. package/schemas/decision/SdlcScreeningPreregistration.v2.schema.json +138 -0
  203. package/schemas/decision/SdlcScreeningRelease.v1.schema.json +654 -0
  204. package/schemas/decision/SdlcScreeningRelease.v2.schema.json +654 -0
  205. package/tools/agents/deploy-agents.mjs +1 -1
  206. package/tools/agents/providers/base.mjs +40 -18
  207. package/tools/agents/providers/factory.mjs +10 -2
  208. package/tools/agents/providers/muse-hooks.mjs +1 -29
  209. package/tools/agents/providers/muse.mjs +6 -3
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from 'node:fs';
3
3
  import { dirname, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import Ajv2020 from 'ajv/dist/2020.js';
6
+ import addFormats from 'ajv-formats';
6
7
  import { canonicalJson } from '../security/artifact-trust.js';
7
8
  import { admitEntry, EntryAdmissionError } from './entry.js';
8
9
  import { DECISION_API_VERSION, DECISION_API_VERSION_STRUCTURED } from './types.js';
@@ -29,6 +30,8 @@ export class DecisionValidationError extends Error {
29
30
  }
30
31
  }
31
32
  let validators = null;
33
+ let decisionAjv = null;
34
+ let preprocessedEvidenceValidator = null;
32
35
  function schemaRoot() {
33
36
  const here = dirname(fileURLToPath(import.meta.url));
34
37
  const candidates = [resolve(here, '../../schemas/decision'), resolve(here, '../../../schemas/decision')];
@@ -47,6 +50,7 @@ function getValidators() {
47
50
  ajv.addSchema(admissionSchema);
48
51
  ajv.addSchema(contextEvidenceSchema);
49
52
  ajv.addSchema(providerPrefixSchema);
53
+ decisionAjv = ajv;
50
54
  validators = new Map();
51
55
  for (const [kind, filename] of Object.entries(schemaFiles)) {
52
56
  const schema = JSON.parse(readFileSync(resolve(schemaRoot(), filename), 'utf8'));
@@ -58,6 +62,26 @@ function getValidators() {
58
62
  }
59
63
  return validators;
60
64
  }
65
+ /**
66
+ * D24: closed-schema errors for a PreprocessedEvidence manifest, with formats enforced, or
67
+ * null when valid. The runtime resolver runs this so it can never accept what the schema rejects.
68
+ */
69
+ export function preprocessedEvidenceSchemaErrors(value) {
70
+ if (!preprocessedEvidenceValidator) {
71
+ const ajv = new Ajv2020({ strict: true, allErrors: true });
72
+ addFormats(ajv);
73
+ preprocessedEvidenceValidator = ajv.compile(JSON.parse(readFileSync(resolve(schemaRoot(), 'PreprocessedEvidence.v1.schema.json'), 'utf8')));
74
+ }
75
+ return preprocessedEvidenceValidator(value) ? null : ajvMessage(preprocessedEvidenceValidator.errors);
76
+ }
77
+ /** D24: RulesetResult v1alpha2 `preprocessingLineage` shape errors, or null when valid. */
78
+ export function preprocessingLineageSchemaErrors(value) {
79
+ getValidators();
80
+ const validate = decisionAjv.getSchema('https://aiwg.io/schemas/decision/RulesetResult.v1alpha2.schema.json#/$defs/preprocessingLineage');
81
+ if (!validate)
82
+ throw new DecisionValidationError('preprocessing lineage schema is unavailable');
83
+ return validate(value) ? null : ajvMessage(validate.errors);
84
+ }
61
85
  export function artifactDigest(value) {
62
86
  const started = performance.now();
63
87
  admitEntry(value);
@@ -116,6 +140,7 @@ export function assertDecisionWriterVersion(value, semantic) {
116
140
  */
117
141
  export const DECISION_RESULT_V1ALPHA2_FIELDS = [
118
142
  { field: 'batchResult', scope: 'decision', owner: 'D07', semantic: 'batch-receipt' },
143
+ { field: 'batchRequests', scope: 'ruleset', owner: 'D04', semantic: 'batch-request-usage' },
119
144
  { field: 'batch', scope: 'attempt', owner: 'D04', semantic: 'batch-provenance' },
120
145
  { field: 'admission', scope: 'attempt', owner: 'D05', semantic: 'admission-evidence' },
121
146
  { field: 'context', scope: 'decision', owner: 'D06', semantic: 'context-evidence' },
@@ -126,6 +151,7 @@ export const DECISION_RESULT_V1ALPHA2_FIELDS = [
126
151
  { field: 'calibrationCompatibility', scope: 'decision', owner: 'D09', semantic: 'calibration-pin' },
127
152
  { field: 'cache', scope: 'ruleset', owner: 'D15', semantic: 'result-cache-receipt' },
128
153
  { field: 'projection', scope: 'ruleset', owner: 'D10', semantic: 'projection-opt-out' },
154
+ { field: 'preprocessingLineage', scope: 'ruleset', owner: 'D24', semantic: 'trust-projection' },
129
155
  ];
130
156
  /** JSON paths of every v1alpha2-only field present in a DecisionResult or RulesetResult. */
131
157
  export function decisionResultV1Alpha2Fields(value) {
@@ -182,6 +182,42 @@ export const installationCommand = {
182
182
  allowedTools: ['Read', 'Write'],
183
183
  },
184
184
  };
185
+ export const decisionCommand = {
186
+ id: 'decision',
187
+ type: 'command',
188
+ name: 'Decision Driver',
189
+ description: 'Inspect, validate and run governed decision classification workflows',
190
+ version: '1.0.0',
191
+ capabilities: ['cli', 'decision', 'classification', 'mcp', 'validation'],
192
+ keywords: ['decision', 'classification', 'patterns', 'mcp', 'evaluate', 'validate'],
193
+ category: 'utility',
194
+ platforms: { claude: 'full', generic: 'full' },
195
+ deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
196
+ metadata: {
197
+ type: 'command',
198
+ template: 'utility',
199
+ argumentHint: '<capabilities|patterns|validate|evaluate|setup> [options]',
200
+ allowedTools: ['Read', 'Write', 'Bash'],
201
+ },
202
+ };
203
+ export const gatesCommand = {
204
+ id: 'gates',
205
+ type: 'command',
206
+ name: 'Gates',
207
+ description: 'Validate, evaluate and inspect declarative gate packs offline',
208
+ version: '1.0.0',
209
+ capabilities: ['cli', 'gates', 'validation', 'evaluation'],
210
+ keywords: ['gates', 'gate pack', 'validate', 'evaluate', 'promotion', 'hold', 'rollback'],
211
+ category: 'utility',
212
+ platforms: { claude: 'full', generic: 'full' },
213
+ deployment: { pathTemplate: '.{platform}/commands/{id}.md', core: true },
214
+ metadata: {
215
+ type: 'command',
216
+ template: 'utility',
217
+ argumentHint: '<validate|evaluate|show|list> [options]',
218
+ allowedTools: ['Read'],
219
+ },
220
+ };
185
221
  // Renamed from `refreshCommand` as part of #694 (avoid collision with git sync
186
222
  // semantics) and re-linked to `refreshHandler` in #919. Users who type
187
223
  // `aiwg sync` still reach this handler via its 'sync' alias and see a
@@ -3715,6 +3751,8 @@ export const commandDefinitions = [
3715
3751
  contextFirewallCommand,
3716
3752
  updateCommand,
3717
3753
  installationCommand,
3754
+ decisionCommand,
3755
+ gatesCommand,
3718
3756
  refreshCommand,
3719
3757
  regenerateCommand,
3720
3758
  workspaceContextCommand,
@@ -40,6 +40,7 @@ export const ProjectLocalTypeSchema = z.enum([
40
40
  */
41
41
  const safeRelativePath = z.string().regex(/^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*\/?$/, 'must be a relative path (alphanumeric + _-, no leading slash, no ..)');
42
42
  const safeModuleFile = z.string().regex(/^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*\.mjs$/, 'must be a relative .mjs path (alphanumeric + _-, no leading slash, no ..)');
43
+ const safeJsonFile = z.string().regex(/^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*\.json$/, 'must be a relative .json path (alphanumeric + _-, no leading slash, no ..)');
43
44
  // Single-char ids are allowed; multi-char ids must end with alphanumeric
44
45
  // (no trailing hyphen). This pattern: `[a-z0-9]([a-z0-9-]*[a-z0-9])?`
45
46
  const bundleNamePattern = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
@@ -55,6 +56,7 @@ const ArtifactListsSchema = z.object({
55
56
  hooks: z.array(z.string()).max(50).optional(),
56
57
  commands: z.array(z.string()).max(200).optional(),
57
58
  behaviors: z.array(z.string()).max(50).optional(),
59
+ gatePacks: z.array(z.string().min(1).max(128)).max(50).optional(),
58
60
  });
59
61
  const EntryPathsSchema = z.object({
60
62
  agents: safeRelativePath.optional(),
@@ -65,6 +67,7 @@ const EntryPathsSchema = z.object({
65
67
  hooks: safeRelativePath.optional(),
66
68
  commands: safeRelativePath.optional(),
67
69
  behaviors: safeRelativePath.optional(),
70
+ gatePacks: safeRelativePath.optional(),
68
71
  }).strict();
69
72
  export const AddonConfigSchema = z.object({
70
73
  entry: EntryPathsSchema.optional(),
@@ -76,6 +79,10 @@ export const FrameworkConfigSchema = z.object({
76
79
  path: safeRelativePath.optional(),
77
80
  files: z.array(safeRelativePath).max(100).optional(),
78
81
  ignore: z.array(safeRelativePath).max(100).optional(),
82
+ gatePacks: z.array(z.string().min(1).max(128)).max(50).optional(),
83
+ entry: z.object({
84
+ gatePacks: safeRelativePath.optional(),
85
+ }).strict().optional(),
79
86
  contextContributions: z.object({
80
87
  hookFragment: safeRelativePath.optional(),
81
88
  sectionsDir: safeRelativePath.optional(),
@@ -136,6 +143,37 @@ export const ProviderConfigSchema = z.object({
136
143
  aliases: z.array(z.string().min(1).max(64)).max(20).optional(),
137
144
  capabilities: ProviderCapabilityOverridesSchema.optional(),
138
145
  }).strict();
146
+ /**
147
+ * Review attestation for one gate metric provider (#2831).
148
+ *
149
+ * A reviewer records who reviewed which exact provider bytes and when. The
150
+ * `codeDigest` must equal the loader-computed code digest over the provider
151
+ * module plus its resolved local imports (and lockfile-pinned externals);
152
+ * registration refuses absent attestations and digest mismatches. Null or
153
+ * unknown digests never verify: they fail closed.
154
+ */
155
+ export const GateProviderReviewSchema = z.object({
156
+ reviewer: z.string().min(1).max(128),
157
+ reviewedAt: z.string().datetime(),
158
+ codeDigest: z.string().regex(/^sha256:[0-9a-f]{64}$/, 'must be a sha256 digest'),
159
+ }).strict();
160
+ /**
161
+ * One addon/extension gate metric provider declared by a bundle manifest
162
+ * (#2831). The module is loaded ONLY through the gates registry from this
163
+ * declaration, inside the bundle root (no traversal, no escaping symlinks,
164
+ * no absolute paths, size-limited), never by arbitrary import. Core
165
+ * providers remain the default; this field is optional and disabled unless
166
+ * the caller explicitly enables bundle providers.
167
+ */
168
+ export const GateProviderEntrySchema = z.object({
169
+ id: z.string().min(1).max(128).regex(/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/, 'must be a namespaced provider id <scope>/<name> (alphanumeric plus . _ -)'),
170
+ version: z.string().regex(/^\d+\.\d+\.\d+/, 'CalVer or SemVer (X.Y.Z[...])'),
171
+ module: safeModuleFile,
172
+ description: z.string().min(1).max(512),
173
+ review: GateProviderReviewSchema.optional(),
174
+ reviewFile: safeJsonFile.optional(),
175
+ }).strict().refine((entry) => (entry.review !== undefined) !== (entry.reviewFile !== undefined), { message: 'exactly one of review or reviewFile is required' });
176
+ export const GateProvidersSchema = z.array(GateProviderEntrySchema).max(20).refine((entries) => new Set(entries.map((entry) => entry.id)).size === entries.length, { message: 'gate provider ids must be unique within a bundle' }).refine((entries) => new Set(entries.map((entry) => entry.module)).size === entries.length, { message: 'gate provider modules must be unique within a bundle' });
139
177
  export const CliCommandsSchema = z.object({
140
178
  namespace: z.string()
141
179
  .min(1)
@@ -197,6 +235,12 @@ export const BundleManifestSchema = z.object({
197
235
  // Expandable CLI namespace contributed by an addon-shaped bundle.
198
236
  // The same block is accepted by bundled addon manifests.
199
237
  cli_commands: CliCommandsSchema.optional(),
238
+ // Addon/extension gate metric providers (#2831). Optional and disabled by
239
+ // default: the gates loader resolves each `module` inside the bundle root
240
+ // only, verifies the review attestation digest against the computed code
241
+ // digest, and registers the provider through the gates registry. Core
242
+ // providers remain the default when this field is absent.
243
+ gateProviders: GateProvidersSchema.optional(),
200
244
  })
201
245
  .strict()
202
246
  .refine((m) => {
@@ -0,0 +1,315 @@
1
+ import { lstatSync, readdirSync, readFileSync, existsSync, realpathSync, statSync } from 'node:fs';
2
+ import { basename, join, resolve, sep } from 'node:path';
3
+ import { parseDecisionJson, parseDecisionYaml } from '../decision/entry.js';
4
+ import { MANIFEST_MAX_BYTES } from '../extensions/manifest.js';
5
+ import { validateGateDocument } from './schema.js';
6
+ export const GATE_PACKS_DIR = 'gate-packs';
7
+ /** Admission cap for one gate-pack authoring file, checked before reading (same as the artifact index). */
8
+ export const GATE_PACK_MAX_BYTES = 256 * 1024;
9
+ /** Manifest-declared bundle subdirs stay inside the bundle: segments only, no `..`, no leading `/`. */
10
+ const SAFE_RELATIVE_DIR = /^[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)*\/?$/;
11
+ const realpathIfExists = (candidate) => {
12
+ try {
13
+ return realpathSync(candidate);
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ };
19
+ /**
20
+ * Resolve a manifest-declared gate-packs dir inside its bundle. A raw
21
+ * `entry.gatePacks` value is validated as a safe relative path and contained
22
+ * by realpath: `../../../outside` and symlink escapes are load errors, never
23
+ * followed. A missing dir resolves (callers treat it as empty); a hostile
24
+ * declaration throws even when the dir is absent.
25
+ */
26
+ export function resolveGatePacksDir(bundleDir, declared) {
27
+ const subdir = declared ?? GATE_PACKS_DIR;
28
+ if (!SAFE_RELATIVE_DIR.test(subdir)) {
29
+ throw new Error(`gatePacks dir '${subdir}' must be a relative path inside the bundle`
30
+ + ` (alphanumeric + _-, no leading slash, no ..): ${bundleDir}`);
31
+ }
32
+ const absolute = resolve(bundleDir, subdir);
33
+ const realBundle = realpathIfExists(bundleDir);
34
+ const realTarget = realpathIfExists(absolute);
35
+ if (realBundle !== null && realTarget !== null
36
+ && realTarget !== realBundle && !realTarget.startsWith(realBundle + sep)) {
37
+ throw new Error(`gatePacks dir '${subdir}' escapes its bundle: ${bundleDir}`);
38
+ }
39
+ return absolute;
40
+ }
41
+ const GATE_PACK_SUFFIXES = ['.gatepack.yaml', '.gatepack.yml', '.gatepack.json'];
42
+ /** True when the filename is a gate-pack authoring file (never HITL `gates/`). */
43
+ export function isGatePackFile(filename) {
44
+ const lower = filename.toLowerCase();
45
+ return GATE_PACK_SUFFIXES.some(suffix => lower.endsWith(suffix));
46
+ }
47
+ /**
48
+ * Sorted gate-pack authoring files directly under the bundle's gate-packs
49
+ * dir. Every match is lstat-checked before it is returned: symlinks are
50
+ * rejected (never followed), only regular files qualify, the 256 KiB cap is
51
+ * enforced before any read, and the realpath must stay inside the dir.
52
+ */
53
+ export function listGatePackFiles(bundleDir, gatePacksDir = GATE_PACKS_DIR) {
54
+ const dir = resolveGatePacksDir(bundleDir, gatePacksDir);
55
+ const realDir = realpathIfExists(dir);
56
+ let entries;
57
+ try {
58
+ entries = readdirSync(dir);
59
+ }
60
+ catch {
61
+ return [];
62
+ }
63
+ const files = [];
64
+ for (const entry of entries.filter(isGatePackFile).sort()) {
65
+ const full = join(dir, entry);
66
+ let status;
67
+ try {
68
+ status = lstatSync(full);
69
+ }
70
+ catch (error) {
71
+ throw new Error(`gate pack unreadable: ${full}: ${error instanceof Error ? error.message : String(error)}`);
72
+ }
73
+ if (status.isSymbolicLink())
74
+ throw new Error(`gate pack must not be a symlink: ${full}`);
75
+ if (!status.isFile())
76
+ throw new Error(`gate pack is not a regular file: ${full}`);
77
+ if (status.size > GATE_PACK_MAX_BYTES) {
78
+ throw new Error(`gate pack exceeds 256 KiB before reading: ${full}`);
79
+ }
80
+ const real = realpathIfExists(full);
81
+ if (realDir !== null && real !== null && real !== realDir && !real.startsWith(realDir + sep)) {
82
+ throw new Error(`gate pack escapes its bundle: ${full}`);
83
+ }
84
+ files.push(full);
85
+ }
86
+ return files;
87
+ }
88
+ /** Read the bundle manifest's gate-pack declaration when present (both legacy and Zod shapes). */
89
+ export function readBundleGatePacksDeclaration(bundleDir) {
90
+ const manifestPath = join(resolve(bundleDir), 'manifest.json');
91
+ if (!existsSync(manifestPath))
92
+ return {};
93
+ let manifest;
94
+ try {
95
+ const status = statSync(manifestPath);
96
+ if (!status.isFile() || status.size > MANIFEST_MAX_BYTES)
97
+ return {};
98
+ manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
99
+ }
100
+ catch {
101
+ return {};
102
+ }
103
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest))
104
+ return {};
105
+ const root = manifest;
106
+ const entry = root.entry;
107
+ const dir = typeof entry?.gatePacks === 'string' ? entry.gatePacks
108
+ : typeof entry?.['gate-packs'] === 'string' ? String(entry['gate-packs'])
109
+ : undefined;
110
+ const topNames = Array.isArray(root.gatePacks)
111
+ ? root.gatePacks.filter((name) => typeof name === 'string')
112
+ : undefined;
113
+ for (const configKey of ['addonConfig', 'extensionConfig', 'frameworkConfig']) {
114
+ const config = root[configKey];
115
+ if (!config || typeof config !== 'object')
116
+ continue;
117
+ if (topNames === undefined && Array.isArray(config.gatePacks)) {
118
+ const names = config.gatePacks.filter((name) => typeof name === 'string');
119
+ return { ...(dir ? { dir } : {}), names };
120
+ }
121
+ const configEntry = config.entry;
122
+ if (dir === undefined && typeof configEntry?.gatePacks === 'string') {
123
+ return { dir: configEntry.gatePacks, ...(topNames ? { names: topNames } : {}) };
124
+ }
125
+ }
126
+ if (root.frameworkConfig && typeof root.frameworkConfig === 'object') {
127
+ const framework = root.frameworkConfig;
128
+ if (Array.isArray(framework.gatePacks)) {
129
+ const names = framework.gatePacks.filter((name) => typeof name === 'string');
130
+ const frameworkEntry = framework.entry;
131
+ const frameworkDir = typeof frameworkEntry?.gatePacks === 'string' ? frameworkEntry.gatePacks : dir;
132
+ return { ...(frameworkDir ? { dir: frameworkDir } : {}), names };
133
+ }
134
+ }
135
+ return { ...(dir ? { dir } : {}), ...(topNames ? { names: topNames } : {}) };
136
+ }
137
+ /**
138
+ * Parse one authoring file with admission limits and closed-schema validation.
139
+ * Never returns an invalid pack. The file is lstat-checked before any read:
140
+ * symlinks are rejected, only regular files qualify, and the 256 KiB cap is
141
+ * enforced up front so an unbounded read can never happen here.
142
+ */
143
+ export function loadGatePackFile(filePath) {
144
+ let status;
145
+ try {
146
+ status = lstatSync(filePath);
147
+ }
148
+ catch (error) {
149
+ throw new Error(`gate pack unreadable: ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
150
+ }
151
+ if (status.isSymbolicLink())
152
+ throw new Error(`gate pack must not be a symlink: ${filePath}`);
153
+ if (!status.isFile())
154
+ throw new Error(`gate pack is not a regular file: ${filePath}`);
155
+ if (status.size > GATE_PACK_MAX_BYTES)
156
+ throw new Error(`gate pack exceeds 256 KiB before reading: ${filePath}`);
157
+ let content;
158
+ try {
159
+ content = readFileSync(filePath, 'utf8');
160
+ }
161
+ catch (error) {
162
+ throw new Error(`gate pack unreadable: ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
163
+ }
164
+ let document;
165
+ try {
166
+ document = filePath.toLowerCase().endsWith('.json') ? parseDecisionJson(content) : parseDecisionYaml(content);
167
+ }
168
+ catch (error) {
169
+ throw new Error(`gate pack unparseable: ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
170
+ }
171
+ try {
172
+ const pack = validateGateDocument(document);
173
+ if (pack.kind !== 'GatePack')
174
+ throw new Error(`expected kind GatePack, got '${String(document.kind)}'`);
175
+ return pack;
176
+ }
177
+ catch (error) {
178
+ throw new Error(`gate pack invalid: ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
179
+ }
180
+ }
181
+ /**
182
+ * Register every pack in one bundle directory. Invalid packs are rejected
183
+ * with a file-pathed diagnostic; nothing invalid enters the registry.
184
+ */
185
+ export function registerBundleGatePacks(registry, bundleDir, origin) {
186
+ const declaration = readBundleGatePacksDeclaration(bundleDir);
187
+ // The declared gate-packs dir itself must be a real directory (checked
188
+ // before realpath resolution): a symlinked dir would load packs from
189
+ // outside the bundle under the bundle's origin.
190
+ try {
191
+ if (lstatSync(join(resolve(bundleDir), declaration.dir ?? GATE_PACKS_DIR)).isSymbolicLink()) {
192
+ throw new Error(`bundle gate-packs dir must not be a symlink: ${bundleDir}`);
193
+ }
194
+ }
195
+ catch (error) {
196
+ if (error instanceof Error && /symlink/.test(error.message))
197
+ throw error;
198
+ /* absent dir: listGatePackFiles reports nothing */
199
+ }
200
+ // Containment validation (throws on traversal escapes).
201
+ const gatePacksDir = resolveGatePacksDir(bundleDir, declaration.dir);
202
+ const files = listGatePackFiles(bundleDir, declaration.dir ?? GATE_PACKS_DIR);
203
+ const wanted = declaration.names ? new Set(declaration.names) : null;
204
+ const loaded = [];
205
+ for (const file of files) {
206
+ const short = basename(file).replace(/\.gatepack\.(yaml|yml|json)$/i, '');
207
+ if (wanted && !wanted.has(short))
208
+ continue;
209
+ const pack = loadGatePackFile(file);
210
+ try {
211
+ registry.registerPack(pack, origin.bundle === undefined ? { namespace: origin.namespace } : origin);
212
+ }
213
+ catch (error) {
214
+ throw new Error(`gate pack rejected: ${file}: ${error instanceof Error ? error.message : String(error)}`);
215
+ }
216
+ loaded.push(pack.metadata.id);
217
+ }
218
+ if (wanted) {
219
+ for (const name of wanted) {
220
+ if (!files.some(file => basename(file).replace(/\.gatepack\.(yaml|yml|json)$/i, '') === name)) {
221
+ throw new Error(`gate pack missing: ${gatePacksDir}/${name}.gatepack.yaml (declared by manifest)`);
222
+ }
223
+ }
224
+ }
225
+ return loaded;
226
+ }
227
+ /**
228
+ * Derive a `--pack-dir` bundle's contribution origin from its manifest, never
229
+ * from its path. Unverified `--pack-dir` bundles are ALWAYS project-scoped:
230
+ * only the installed tree (via `discoverShippedGatePacks`) contributes
231
+ * `aiwg:` packs. A manifest whose `{id, type}` claims a shipped bundle
232
+ * (under `<aiwgRoot>/agentic/code/...`) or an installed project bundle
233
+ * (under `<cwd>/.aiwg/...`) that is not its own directory refuses:
234
+ * impersonating a trusted bundle id through an unverified path is a load
235
+ * error. Path substrings such as `/addons/` are never consulted.
236
+ */
237
+ export function originForPackDir(bundleDir, options) {
238
+ const manifestPath = join(resolve(bundleDir), 'manifest.json');
239
+ let claimed = null;
240
+ try {
241
+ const status = statSync(manifestPath);
242
+ if (!status.isFile() || status.size > MANIFEST_MAX_BYTES)
243
+ return { namespace: 'project' };
244
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
245
+ if (typeof manifest?.id === 'string' && manifest.id.trim() !== ''
246
+ && (manifest.type === 'framework' || manifest.type === 'addon' || manifest.type === 'extension')) {
247
+ claimed = { id: manifest.id, type: manifest.type };
248
+ }
249
+ }
250
+ catch {
251
+ return { namespace: 'project' };
252
+ }
253
+ if (claimed !== null) {
254
+ const plural = claimed.type === 'framework' ? 'frameworks' : claimed.type === 'addon' ? 'addons' : 'extensions';
255
+ const candidates = [];
256
+ if (options?.aiwgRoot)
257
+ candidates.push({ root: join(resolve(options.aiwgRoot), 'agentic/code', plural, claimed.id), label: 'shipped' });
258
+ if (options?.cwd)
259
+ candidates.push({ root: join(resolve(options.cwd), '.aiwg', plural, claimed.id), label: 'installed' });
260
+ const own = realpathIfExists(resolve(bundleDir));
261
+ for (const candidate of candidates) {
262
+ const trusted = realpathIfExists(candidate.root);
263
+ if (trusted === null)
264
+ continue;
265
+ if (own === null || own !== trusted) {
266
+ throw new Error(`pack-dir bundle claims a ${candidate.label} bundle id '${claimed.id}'`
267
+ + ` that is not its own directory: ${bundleDir}`);
268
+ }
269
+ }
270
+ }
271
+ return { namespace: 'project' };
272
+ }
273
+ const BUNDLE_GROUPS = [
274
+ { kind: 'framework', sub: 'frameworks' },
275
+ { kind: 'addon', sub: 'addons' },
276
+ { kind: 'extension', sub: 'extensions' },
277
+ ];
278
+ /**
279
+ * Register shipped packs under `agentic/code/<group>/<bundle>/gate-packs/`.
280
+ * Shipped packs use the `aiwg:` namespace (`aiwg:<bundle>/<name>`); the
281
+ * bundle kind is only an install hint. Project-local bundles register under
282
+ * their own `framework:`/`addon:`/`extension:` namespace via
283
+ * `registerBundleGatePacks` directly.
284
+ */
285
+ export function discoverShippedGatePacks(aiwgRoot, registry) {
286
+ const loaded = [];
287
+ for (const group of BUNDLE_GROUPS) {
288
+ const groupDir = resolve(aiwgRoot, 'agentic/code', group.sub);
289
+ let bundles;
290
+ try {
291
+ bundles = readdirSync(groupDir).filter(name => {
292
+ try {
293
+ return statSync(join(groupDir, name)).isDirectory();
294
+ }
295
+ catch {
296
+ return false;
297
+ }
298
+ }).sort();
299
+ }
300
+ catch {
301
+ continue;
302
+ }
303
+ for (const bundle of bundles) {
304
+ const bundleDir = join(groupDir, bundle);
305
+ const files = listGatePackFiles(bundleDir);
306
+ if (!files.length && !existsSync(join(bundleDir, 'manifest.json')))
307
+ continue;
308
+ if (!files.length)
309
+ continue;
310
+ loaded.push(...registerBundleGatePacks(registry, bundleDir, { namespace: 'aiwg', bundle }));
311
+ }
312
+ }
313
+ return loaded;
314
+ }
315
+ //# sourceMappingURL=discovery.js.map