@aifabrix/builder 2.54.1 → 2.55.2

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 (253) hide show
  1. package/README.md +8 -0
  2. package/lib/agent-kit/apply-ops.js +82 -0
  3. package/lib/agent-kit/checksums.js +28 -0
  4. package/lib/agent-kit/classify.js +113 -0
  5. package/lib/agent-kit/claude-shim.js +61 -0
  6. package/lib/agent-kit/constants.js +55 -0
  7. package/lib/agent-kit/file-list.js +103 -0
  8. package/lib/agent-kit/host-approve.js +172 -0
  9. package/lib/agent-kit/hosting/github.js +63 -0
  10. package/lib/agent-kit/hosts.js +71 -0
  11. package/lib/agent-kit/init.js +319 -0
  12. package/lib/agent-kit/kit-root.js +17 -0
  13. package/lib/agent-kit/managed-section.js +79 -0
  14. package/lib/agent-kit/package-load.js +45 -0
  15. package/lib/agent-kit/plan-ops.js +69 -0
  16. package/lib/agent-kit/remote-prompts.js +272 -0
  17. package/lib/agent-kit/render-host-markdown.js +40 -0
  18. package/lib/agent-kit/repo-guard.js +47 -0
  19. package/lib/agent-kit/report.js +107 -0
  20. package/lib/agent-kit/run.js +82 -0
  21. package/lib/agent-kit/scm/git.js +122 -0
  22. package/lib/agent-kit/start.js +310 -0
  23. package/lib/agent-kit/state-file.js +83 -0
  24. package/lib/agent-kit/status.js +50 -0
  25. package/lib/agent-kit/v1-detect.js +81 -0
  26. package/lib/agent-kit/validate-kit.js +87 -0
  27. package/lib/agent-kit/workspace-name.js +40 -0
  28. package/lib/agent-kit/workspace-skeleton.js +103 -0
  29. package/lib/api/role-assistant-test-pack.api.js +86 -0
  30. package/lib/api/work-search.api.js +31 -4
  31. package/lib/api/work.api.js +19 -12
  32. package/lib/app/deploy.js +7 -4
  33. package/lib/app/list-external-systems.js +2 -1
  34. package/lib/app/offer-create-from-template.js +184 -0
  35. package/lib/app/show-display.js +33 -6
  36. package/lib/app/show.js +25 -2
  37. package/lib/channels/add-channel.js +1 -0
  38. package/lib/channels/approval-guides/claude.js +2 -2
  39. package/lib/channels/channel-add-failure.js +1 -0
  40. package/lib/channels/channel-add-remaining.js +15 -9
  41. package/lib/channels/channel-definition-status.js +115 -0
  42. package/lib/channels/channel-description.js +2 -1
  43. package/lib/channels/channel-install-envelope.js +4 -2
  44. package/lib/channels/channel-integration-generator.js +2 -0
  45. package/lib/channels/channel-runtime-resolver.js +14 -0
  46. package/lib/channels/channel-targets.js +31 -5
  47. package/lib/channels/claude-setup-values.js +4 -1
  48. package/lib/channels/slack-channel-add.js +59 -6
  49. package/lib/channels/slack-mcp-callback.js +51 -0
  50. package/lib/channels/slack-mcp-runtime.js +156 -0
  51. package/lib/channels/slack-provisioning/install-cli.js +14 -2
  52. package/lib/channels/slack-provisioning/with-curl-on-path.js +6 -1
  53. package/lib/cli/index.js +4 -0
  54. package/lib/cli/setup-app.js +8 -1
  55. package/lib/cli/setup-auth.js +11 -2
  56. package/lib/cli/setup-certification-commands.js +2 -2
  57. package/lib/cli/setup-certification.help.js +3 -1
  58. package/lib/commands/agent-kit-help.js +149 -0
  59. package/lib/commands/agent-kit.js +417 -0
  60. package/lib/commands/auth-status-user.js +99 -0
  61. package/lib/commands/auth-status.js +14 -4
  62. package/lib/commands/channel-add-help.js +5 -5
  63. package/lib/commands/datasource-capability-relate-cli.js +5 -4
  64. package/lib/commands/datasource-load-export-cli.js +7 -2
  65. package/lib/commands/datasource-test-trust-cli.js +5 -1
  66. package/lib/commands/governance-command-action.js +5 -4
  67. package/lib/commands/identity-membership-role-cache.js +6 -1
  68. package/lib/commands/lifecycle-command-action.js +16 -15
  69. package/lib/commands/login-credentials.js +8 -3
  70. package/lib/commands/login-device.js +14 -5
  71. package/lib/commands/login.js +14 -1
  72. package/lib/commands/logout.js +50 -15
  73. package/lib/commands/repair-auth-apply.js +1 -1
  74. package/lib/commands/repair-datasource-key-skip.js +39 -0
  75. package/lib/commands/repair-datasource-keys.js +24 -12
  76. package/lib/commands/repair-rbac.js +16 -10
  77. package/lib/commands/role-assistant-copy-output.js +61 -0
  78. package/lib/commands/role-assistant-copy.js +155 -0
  79. package/lib/commands/role-assistant-test-output.js +8 -9
  80. package/lib/commands/role-assistant-test-result-rows.js +42 -0
  81. package/lib/commands/role-assistant.js +140 -59
  82. package/lib/commands/search.js +143 -0
  83. package/lib/commands/test-trust-external.js +7 -5
  84. package/lib/commands/upload-sidecar-followups.js +4 -1
  85. package/lib/commands/verify-governance-command-action.js +9 -7
  86. package/lib/commands/verify-operations-command-action.js +9 -8
  87. package/lib/commands/verify-operations-skip-e2e.js +131 -0
  88. package/lib/commands/verify-operations-step-plan.js +6 -33
  89. package/lib/commands/verify-operations-steps.js +3 -1
  90. package/lib/commands/verify-trust-command-action.js +6 -5
  91. package/lib/datasource/capability/relate-operations.js +28 -10
  92. package/lib/datasource/capability/run-capability-relate.js +48 -25
  93. package/lib/datasource/datasource-identifier.js +13 -5
  94. package/lib/deployment/deployer-poll-run.js +117 -35
  95. package/lib/external-system/deploy-dataplane-published.js +70 -0
  96. package/lib/external-system/deploy.js +12 -2
  97. package/lib/external-system/download-role-assistant.js +41 -15
  98. package/lib/external-system/download.js +8 -3
  99. package/lib/generator/helpers.js +12 -1
  100. package/lib/generator/wizard-datasource-key.js +54 -0
  101. package/lib/generator/wizard.js +55 -54
  102. package/lib/integration-definition/apply-datasource-keys.js +75 -0
  103. package/lib/integration-definition/apply-relationships.js +195 -0
  104. package/lib/integration-definition/apply-static-enterprise.js +232 -0
  105. package/lib/integration-definition/apply-system-file.js +71 -0
  106. package/lib/integration-definition/apply-test-endpoint.js +89 -0
  107. package/lib/integration-definition/apply-viewpoint-defaults.js +157 -0
  108. package/lib/integration-definition/apply.js +78 -59
  109. package/lib/integration-definition/enrich-openapi-operations.js +36 -10
  110. package/lib/integration-definition/enrich-openapi-pick.js +87 -0
  111. package/lib/lifecycle/role-assistant-governance.js +56 -21
  112. package/lib/programmatic/capability-registry.js +1 -0
  113. package/lib/programmatic/index.js +2 -0
  114. package/lib/programmatic/openapi-apply-result-schemas.js +8 -0
  115. package/lib/programmatic/openapi-channel-install-schemas.js +4 -1
  116. package/lib/programmatic/openapi-component-schemas.js +1 -0
  117. package/lib/programmatic/openapi-descriptions.js +9 -3
  118. package/lib/programmatic/openapi-envelope-examples.js +7 -0
  119. package/lib/programmatic/openapi-operation-bindings.js +5 -0
  120. package/lib/programmatic/openapi-request-schemas.js +5 -0
  121. package/lib/programmatic/openapi-schema-descriptions-channel.js +8 -5
  122. package/lib/programmatic/openapi-schema-descriptions-operations.js +19 -0
  123. package/lib/programmatic/openapi-spec.js +1 -0
  124. package/lib/programmatic/request-timeout.js +3 -0
  125. package/lib/programmatic/route-handler-map.js +7 -0
  126. package/lib/programmatic/run-channel-catalog.js +50 -8
  127. package/lib/programmatic/run-channel-install-options.js +5 -1
  128. package/lib/programmatic/run-download.js +41 -0
  129. package/lib/protection/auth-context.js +3 -1
  130. package/lib/protection/protection-display.js +3 -2
  131. package/lib/protection/report-exit.js +50 -0
  132. package/lib/protection/run-commands.js +2 -0
  133. package/lib/role-assistant/knowledge-sync-settings-push.js +172 -0
  134. package/lib/role-assistant/knowledge-sync.js +9 -126
  135. package/lib/role-assistant/package-validate.js +175 -0
  136. package/lib/role-assistant/test-case-assertion-row.js +36 -0
  137. package/lib/role-assistant/test-case-copy-bind.js +77 -0
  138. package/lib/role-assistant/test-case-copy-expected.js +127 -0
  139. package/lib/role-assistant/test-case-copy-from-work.js +90 -0
  140. package/lib/role-assistant/test-case-copy-interactions.js +67 -0
  141. package/lib/role-assistant/test-case-copy.js +119 -0
  142. package/lib/role-assistant/test-case-expected-catalog.js +135 -0
  143. package/lib/role-assistant/test-case-validate.js +88 -0
  144. package/lib/role-assistant/test-cases-bid.js +117 -4
  145. package/lib/role-assistant/test-cases-evaluate.js +149 -31
  146. package/lib/role-assistant/test-cases-evidence.js +44 -5
  147. package/lib/role-assistant/test-cases-honesty.js +155 -6
  148. package/lib/role-assistant/test-cases-lifecycle.js +297 -0
  149. package/lib/role-assistant/test-cases-search-graph.js +122 -0
  150. package/lib/role-assistant/test-cases-search.js +20 -2
  151. package/lib/role-assistant/test-cases.js +34 -168
  152. package/lib/role-assistant/test-pack-map.js +180 -0
  153. package/lib/role-assistant/test-pack-sync.js +187 -0
  154. package/lib/role-assistant/test-runner-debug.js +54 -0
  155. package/lib/role-assistant/test-runner-respond-body.js +36 -0
  156. package/lib/role-assistant/test-runner-search.js +20 -5
  157. package/lib/role-assistant/test-runner-start-payload.js +63 -10
  158. package/lib/role-assistant/test-runner-workhub-answers.js +45 -16
  159. package/lib/role-assistant/test-runner-workhub.js +45 -21
  160. package/lib/role-assistant/test-runner.js +53 -10
  161. package/lib/schema/external-datasource.schema.json +36 -5
  162. package/lib/schema/external-system.schema.json +20 -2
  163. package/lib/schema/infra-parameter.schema.json +51 -3
  164. package/lib/schema/infra.parameter.yaml +476 -40
  165. package/lib/schema/role-assistant-test-case.schema.json +408 -0
  166. package/lib/schema/type/document-storage.json +1 -1
  167. package/lib/search/dataplane-context.js +23 -0
  168. package/lib/search/search-args.js +211 -0
  169. package/lib/search/search-display.js +238 -0
  170. package/lib/search/search-errors.js +50 -0
  171. package/lib/search/search-prompts.js +119 -0
  172. package/lib/search/search-roles.js +85 -0
  173. package/lib/search/search-run.js +241 -0
  174. package/lib/search/search-skills.js +198 -0
  175. package/lib/utils/api-required-permissions.js +271 -0
  176. package/lib/utils/api.js +16 -13
  177. package/lib/utils/authentication-slot-show.js +153 -0
  178. package/lib/utils/cli-json-exit.js +41 -0
  179. package/lib/utils/cli-utils.js +23 -5
  180. package/lib/utils/controller-url.js +78 -4
  181. package/lib/utils/credential-display.js +43 -1
  182. package/lib/utils/deployment-errors.js +15 -0
  183. package/lib/utils/deployment-validation-helpers.js +4 -1
  184. package/lib/utils/device-code.js +31 -10
  185. package/lib/utils/error-formatters/error-parser.js +16 -1
  186. package/lib/utils/error-formatters/permission-errors.js +19 -5
  187. package/lib/utils/external-system-readiness-compact-blocks.js +5 -1
  188. package/lib/utils/external-system-readiness-compact-display.js +30 -1
  189. package/lib/utils/external-system-validators.js +2 -1
  190. package/lib/utils/help-builder.js +3 -1
  191. package/lib/utils/keycloak-realm-ready.js +5 -2
  192. package/lib/utils/logger.js +1 -0
  193. package/lib/utils/paths.js +26 -5
  194. package/lib/utils/schema-loader.js +37 -0
  195. package/lib/utils/secrets-helpers.js +2 -2
  196. package/lib/utils/write-stream-and-wait.js +63 -0
  197. package/lib/validation/validate-external-file.js +5 -1
  198. package/lib/validation/validate.js +3 -2
  199. package/package.json +20 -6
  200. package/templates/agent-kit/adapters/claude/nested.CLAUDE.md +1 -0
  201. package/templates/agent-kit/adapters/claude/root.CLAUDE.md +5 -0
  202. package/templates/agent-kit/adapters/cursor/commands/cust-0-plan.md +5 -0
  203. package/templates/agent-kit/adapters/cursor/commands/cust-1-validate.md +5 -0
  204. package/templates/agent-kit/adapters/cursor/commands/cust-2-build.md +5 -0
  205. package/templates/agent-kit/adapters/cursor/commands/cust-3-push.md +5 -0
  206. package/templates/agent-kit/adapters/cursor/commands/cust-biz-1-validate.md +5 -0
  207. package/templates/agent-kit/adapters/cursor/commands/cust-biz-2-build.md +5 -0
  208. package/templates/agent-kit/adapters/cursor/commands/cust-biz-3-prove.md +5 -0
  209. package/templates/agent-kit/agent-kit.yaml +45 -0
  210. package/templates/agent-kit/instructions/AGENTKIT.md +104 -0
  211. package/templates/agent-kit/instructions/integration.AGENTS.md +25 -0
  212. package/templates/agent-kit/instructions/role-assistant.AGENTS.md +22 -0
  213. package/templates/agent-kit/instructions/root.AGENTS.md +37 -0
  214. package/templates/agent-kit/parity-inventory.yaml +188 -0
  215. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +59 -0
  216. package/templates/agent-kit/skills/aifabrix-connected-system/references/contract-checklist.md +40 -0
  217. package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +54 -0
  218. package/templates/agent-kit/skills/aifabrix-connected-system/references/production-safety.md +35 -0
  219. package/templates/agent-kit/skills/aifabrix-connected-system/scripts/reserved-system-key.js +19 -0
  220. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +52 -0
  221. package/templates/agent-kit/skills/aifabrix-plan/references/challenge-checklist.md +108 -0
  222. package/templates/agent-kit/skills/aifabrix-plan/references/interaction.md +34 -0
  223. package/templates/agent-kit/skills/aifabrix-plan/references/plan-template.md +182 -0
  224. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +51 -0
  225. package/templates/agent-kit/skills/aifabrix-prove/references/demo-template.md +65 -0
  226. package/templates/agent-kit/skills/aifabrix-prove/references/evidence-lifecycle.md +68 -0
  227. package/templates/agent-kit/skills/aifabrix-prove/references/learning-template.md +66 -0
  228. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +43 -0
  229. package/templates/agent-kit/skills/aifabrix-role-assistant/references/package-boundary.md +21 -0
  230. package/templates/agent-kit/skills/shared/hosts.md +65 -0
  231. package/templates/agent-kit/skills/shared/login.md +16 -0
  232. package/templates/agent-kit/workspace/.cursor/plans/README.md +22 -0
  233. package/templates/agent-kit/workspace/LICENSE.mit +21 -0
  234. package/templates/agent-kit/workspace/README.md +89 -0
  235. package/templates/applications/builder-api/Dockerfile +3 -1
  236. package/templates/applications/dataplane/application.yaml +2 -3
  237. package/templates/applications/dataplane/env.template +5 -5
  238. package/templates/applications/dataplane/rbac.yaml +28 -0
  239. package/templates/applications/keycloak/env.template +3 -3
  240. package/templates/applications/miso-controller/application.yaml +21 -2
  241. package/templates/applications/miso-controller/env.template +96 -25
  242. package/templates/applications/miso-controller/rbac.yaml +5 -0
  243. package/templates/channels/claude/TROUBLESHOOTING.md +3 -0
  244. package/templates/channels/claude/channel.json +1 -1
  245. package/templates/channels/claude/install.template.md.hbs +1 -0
  246. package/templates/channels/claude/integration/claude-system.json +1 -1
  247. package/templates/channels/slack/channel.json +11 -0
  248. package/templates/channels/slack/install.template.md.hbs +9 -0
  249. package/templates/channels/slack/integration/slack-system.json +11 -0
  250. package/templates/channels/slack/manifest.template.json.hbs +6 -3
  251. package/templates/channels/teams-bot/channel.json +1 -0
  252. package/templates/python/docker-compose.hbs +14 -0
  253. package/templates/typescript/docker-compose.hbs +14 -0
@@ -5,6 +5,8 @@
5
5
 
6
6
  'use strict';
7
7
 
8
+ const { skipRow } = require('./test-case-assertion-row');
9
+
8
10
  function asArray(value) {
9
11
  return Array.isArray(value) ? value : [];
10
12
  }
@@ -71,10 +73,71 @@ function collectBidSchemaAssertions(expectedBid, observed, assertions, soft) {
71
73
  }
72
74
  }
73
75
 
74
- function assertDefinitionHash(expectedBid, observed, assertions, soft) {
75
- if (expectedBid.requireDefinitionHash !== true) {
76
+ function isDatasourceRecord(value) {
77
+ const key = String(value.key || value.datasourceKey || '').trim();
78
+ return Boolean(key && (Array.isArray(value.capabilities) || value.entityType || value.resourceType));
79
+ }
80
+
81
+ function nestedBidContainers(value) {
82
+ const nested = [];
83
+ if (Array.isArray(value.datasources)) {
84
+ nested.push(value.datasources);
85
+ }
86
+ if (value.spec && typeof value.spec === 'object') {
87
+ nested.push(value.spec);
88
+ }
89
+ if (value.emitted && typeof value.emitted === 'object') {
90
+ nested.push(value.emitted);
91
+ }
92
+ if (value.bid && typeof value.bid === 'object') {
93
+ nested.push(value.bid);
94
+ }
95
+ return nested;
96
+ }
97
+
98
+ function collectBidObjects(value, acc) {
99
+ if (Array.isArray(value)) {
100
+ value.forEach((item) => collectBidObjects(item, acc));
76
101
  return;
77
102
  }
103
+ if (!value || typeof value !== 'object') {
104
+ return;
105
+ }
106
+ nestedBidContainers(value).forEach((child) => collectBidObjects(child, acc));
107
+ if (isDatasourceRecord(value)) {
108
+ acc.push(value);
109
+ }
110
+ }
111
+
112
+ function bidBlob(observed) {
113
+ return observed.emitted || observed.emitRow || observed.applyRow || {};
114
+ }
115
+
116
+ function datasourceKeysFromBid(observed) {
117
+ const objects = [];
118
+ collectBidObjects(bidBlob(observed), objects);
119
+ return new Set(objects.map((row) => String(row.key || row.datasourceKey || '').trim()).filter(Boolean));
120
+ }
121
+
122
+ function datasourceHasCapability(observed, datasource, capKey) {
123
+ const objects = [];
124
+ collectBidObjects(bidBlob(observed), objects);
125
+ const match = objects.find((row) => String(row.key || row.datasourceKey || '') === datasource);
126
+ const caps = asArray(match && match.capabilities);
127
+ return caps.some((cap) => String((cap && cap.key) || cap || '') === capKey);
128
+ }
129
+
130
+ function pushBooleanBidFlag(expectedBid, field, observed, assertions, soft, assertFn) {
131
+ if (expectedBid[field] === false) {
132
+ assertions.push(skipRow(`expected.bid.${field}`, `${field} is false — check skipped.`));
133
+ return;
134
+ }
135
+ if (expectedBid[field] === true) {
136
+ assertFn(expectedBid, observed, assertions, soft);
137
+ }
138
+ }
139
+
140
+ function assertDefinitionHash(expectedBid, observed, assertions, soft) {
78
141
  const { emitRow, applyRow, emitted } = observed;
79
142
  const hash =
80
143
  (emitted && emitted.definitionHash) ||
@@ -112,11 +175,60 @@ function assertSuiteRunId(expectedBid, observed, assertions, soft) {
112
175
  }
113
176
 
114
177
  function collectBidProvenanceAssertions(expectedBid, observed, assertions, soft) {
115
- assertDefinitionHash(expectedBid, observed, assertions, soft);
116
- assertSuiteRunId(expectedBid, observed, assertions, soft);
178
+ pushBooleanBidFlag(expectedBid, 'requireDefinitionHash', observed, assertions, soft, assertDefinitionHash);
179
+ pushBooleanBidFlag(expectedBid, 'requireSuiteRunId', observed, assertions, soft, assertSuiteRunId);
180
+ }
181
+
182
+ function collectBidDatasourceAssertions(expectedBid, observed, assertions, soft) {
183
+ if (Array.isArray(expectedBid.minDatasources)) {
184
+ collectMinDatasources(expectedBid.minDatasources, observed, assertions, soft);
185
+ }
186
+ if (expectedBid.requireCapability && typeof expectedBid.requireCapability === 'object') {
187
+ collectRequireCapability(expectedBid.requireCapability, observed, assertions, soft);
188
+ }
189
+ }
190
+
191
+ function collectMinDatasources(requiredKeys, observed, assertions, soft) {
192
+ if (requiredKeys.length === 0) {
193
+ assertions.push(skipRow('expected.bid.minDatasources', 'minDatasources is empty — nothing to assert.'));
194
+ return;
195
+ }
196
+ const actual = datasourceKeysFromBid(observed);
197
+ for (const raw of requiredKeys) {
198
+ const key = String(raw || '').trim();
199
+ const passed = hasEmitOrApply(observed) && actual.has(key);
200
+ pushBidAssertion(
201
+ assertions,
202
+ soft,
203
+ `expected.bid.minDatasources:${key}`,
204
+ passed,
205
+ `BID lists datasource "${key}".`,
206
+ `BID does not list required datasource "${key}".`
207
+ );
208
+ }
209
+ }
210
+
211
+ function collectRequireCapability(spec, observed, assertions, soft) {
212
+ const datasource = String(spec.datasource || '').trim();
213
+ const capKey = String(spec.key || '').trim();
214
+ const passed = hasEmitOrApply(observed) && datasourceHasCapability(observed, datasource, capKey);
215
+ pushBidAssertion(
216
+ assertions,
217
+ soft,
218
+ 'expected.bid.requireCapability',
219
+ passed,
220
+ `BID datasource "${datasource}" lists capability "${capKey}".`,
221
+ `BID datasource "${datasource}" does not list capability "${capKey}".`
222
+ );
117
223
  }
118
224
 
119
225
  function collectBidPathAssertions(expectedBid, observed, assertions, soft) {
226
+ if (expectedBid.forbidGoldPaths === false) {
227
+ assertions.push(
228
+ skipRow('expected.bid.forbidGoldPaths', 'forbidGoldPaths is false — gold-path check skipped.')
229
+ );
230
+ return;
231
+ }
120
232
  if (expectedBid.forbidGoldPaths === true) {
121
233
  const { emitRow, applyRow, emitted } = observed;
122
234
  const blob = JSON.stringify(emitted || emitRow || applyRow || {});
@@ -146,6 +258,7 @@ function collectBidAssertions(expectedBid, observation, options = {}) {
146
258
  const soft = options.soft === true;
147
259
  collectBidSchemaAssertions(expectedBid, observed, assertions, soft);
148
260
  collectBidProvenanceAssertions(expectedBid, observed, assertions, soft);
261
+ collectBidDatasourceAssertions(expectedBid, observed, assertions, soft);
149
262
  collectBidPathAssertions(expectedBid, observed, assertions, soft);
150
263
  return assertions;
151
264
  }
@@ -18,12 +18,41 @@ const {
18
18
  evaluateSearchExpectations,
19
19
  evaluateSkillsExpectations
20
20
  } = require('./test-cases-search');
21
+ const { notesRow, withKind } = require('./test-case-assertion-row');
22
+
23
+ const UNSAFE_FACT_SEGMENTS = new Set(['__proto__', 'prototype', 'constructor']);
24
+ const WILDCARD_FACT_SEGMENTS = new Set(['*', '**']);
25
+
26
+ function splitDottedPath(dottedPath) {
27
+ return String(dottedPath || '').split('.').filter((part) => part.length > 0);
28
+ }
29
+
30
+ function factPathSafetyMessage(parts) {
31
+ for (const part of parts) {
32
+ if (UNSAFE_FACT_SEGMENTS.has(part)) {
33
+ return `Fact path rejects prototype segment "${part}" before lookup.`;
34
+ }
35
+ if (WILDCARD_FACT_SEGMENTS.has(part)) {
36
+ return `Fact path rejects wildcard segment "${part}".`;
37
+ }
38
+ if (part === 'latest') {
39
+ return 'Fact path rejects latest-global record lookup.';
40
+ }
41
+ }
42
+ return null;
43
+ }
21
44
 
22
45
  function getValueAtPath(obj, dottedPath) {
23
- const parts = String(dottedPath || '').split('.').filter(Boolean);
46
+ const parts = splitDottedPath(dottedPath);
47
+ if (factPathSafetyMessage(parts)) {
48
+ return undefined;
49
+ }
24
50
  let cursor = obj;
25
51
  for (const key of parts) {
26
- if (!cursor || typeof cursor !== 'object' || !(key in cursor)) {
52
+ if (cursor === null || cursor === undefined || typeof cursor !== 'object') {
53
+ return undefined;
54
+ }
55
+ if (!Object.prototype.hasOwnProperty.call(cursor, key)) {
27
56
  return undefined;
28
57
  }
29
58
  cursor = cursor[key];
@@ -31,6 +60,10 @@ function getValueAtPath(obj, dottedPath) {
31
60
  return cursor;
32
61
  }
33
62
 
63
+ function stringifyObserved(value) {
64
+ return value === undefined ? 'missing' : JSON.stringify(value);
65
+ }
66
+
34
67
  function evaluateLifecycleStateExpectation(expectedState, observedState) {
35
68
  if (!expectedState) {
36
69
  return null;
@@ -46,7 +79,7 @@ function evaluateLifecycleStateExpectation(expectedState, observedState) {
46
79
  };
47
80
  }
48
81
 
49
- function evaluateCapabilityExpectations(requiredList, observedCapabilities) {
82
+ function evaluateCapabilityExpectations(kind, requiredList, observedCapabilities) {
50
83
  if (!Array.isArray(requiredList) || requiredList.length === 0) {
51
84
  return [];
52
85
  }
@@ -55,29 +88,39 @@ function evaluateCapabilityExpectations(requiredList, observedCapabilities) {
55
88
  const key = String(capability || '').trim();
56
89
  const passed = observedSet.has(key);
57
90
  return {
58
- id: `expected.capabilities.required:${key}`,
91
+ id: `expected.capabilities.${kind}:${key}`,
59
92
  passed,
60
93
  message: passed
61
- ? `Required capability "${key}" observed.`
62
- : `Required capability "${key}" was not observed.`
94
+ ? `${kind === 'allowed' ? 'Allowed' : 'Required'} capability "${key}" observed.`
95
+ : `${kind === 'allowed' ? 'Allowed' : 'Required'} capability "${key}" was not observed.`
63
96
  };
64
97
  });
65
98
  }
66
99
 
100
+ function evaluateOneFact(factPath, expectedValue, observation) {
101
+ const parts = splitDottedPath(factPath);
102
+ const safety = factPathSafetyMessage(parts);
103
+ const id = `expected.facts:${factPath}`;
104
+ if (safety) {
105
+ return { id, passed: false, message: safety };
106
+ }
107
+ const actualValue = getValueAtPath(observation, factPath);
108
+ const passed = JSON.stringify(actualValue) === JSON.stringify(expectedValue);
109
+ return {
110
+ id,
111
+ passed,
112
+ message: passed
113
+ ? `Fact "${factPath}" matched expected value.`
114
+ : `Fact "${factPath}" expected ${JSON.stringify(expectedValue)} but got ${stringifyObserved(actualValue)}.`
115
+ };
116
+ }
117
+
67
118
  function evaluateFactExpectations(expectedFacts, observation) {
68
119
  if (!expectedFacts || typeof expectedFacts !== 'object' || Array.isArray(expectedFacts)) {
69
120
  return [];
70
121
  }
71
122
  return Object.entries(expectedFacts).map(([factPath, expectedValue]) => {
72
- const actualValue = getValueAtPath(observation, factPath);
73
- const passed = JSON.stringify(actualValue) === JSON.stringify(expectedValue);
74
- return {
75
- id: `expected.facts:${factPath}`,
76
- passed,
77
- message: passed
78
- ? `Fact "${factPath}" matched expected value.`
79
- : `Fact "${factPath}" expected ${JSON.stringify(expectedValue)} but got ${JSON.stringify(actualValue)}.`
80
- };
123
+ return evaluateOneFact(factPath, expectedValue, observation);
81
124
  });
82
125
  }
83
126
 
@@ -86,6 +129,11 @@ function evaluateProhibitedExpectations(prohibited, observation) {
86
129
  return [];
87
130
  }
88
131
  return prohibited.map((factPath) => {
132
+ const parts = splitDottedPath(factPath);
133
+ const safety = factPathSafetyMessage(parts);
134
+ if (safety) {
135
+ return { id: `expected.prohibited:${factPath}`, passed: false, message: safety };
136
+ }
89
137
  const actualValue = getValueAtPath(observation, factPath);
90
138
  const passed = actualValue === undefined || actualValue === null || actualValue === false;
91
139
  return {
@@ -93,7 +141,7 @@ function evaluateProhibitedExpectations(prohibited, observation) {
93
141
  passed,
94
142
  message: passed
95
143
  ? `Prohibited marker "${factPath}" was not observed.`
96
- : `Prohibited marker "${factPath}" observed: ${JSON.stringify(actualValue)}.`
144
+ : `Prohibited marker "${factPath}" observed: ${stringifyObserved(actualValue)}.`
97
145
  };
98
146
  });
99
147
  }
@@ -152,10 +200,74 @@ function softWrapFailedAssertion(assertion, soft) {
152
200
  return softWrapLifecycleAssertion(assertion, soft);
153
201
  }
154
202
 
155
- function collectCaseAssertions(expected, observation) {
203
+ function collectSoftModifierRows(expected) {
204
+ const rows = [];
205
+ if (Object.prototype.hasOwnProperty.call(expected, 'soft')) {
206
+ rows.push(
207
+ notesRow(
208
+ 'expected.soft',
209
+ expected.soft === true
210
+ ? 'soft: true — failures become warnings'
211
+ : 'soft: false — failures are blocking'
212
+ )
213
+ );
214
+ }
215
+ const notes = Array.isArray(expected.softNotes) ? expected.softNotes : [];
216
+ notes.forEach((item, index) => {
217
+ const text = typeof item === 'string'
218
+ ? item
219
+ : String((item && (item.description || item.note)) || JSON.stringify(item));
220
+ rows.push(notesRow(`expected.softNotes[${index}]`, text));
221
+ });
222
+ return rows;
223
+ }
224
+
225
+ function collectEvidenceContextRows(spec, prefix) {
226
+ if (!spec || typeof spec !== 'object' || Array.isArray(spec)) {
227
+ return [];
228
+ }
229
+ const rows = [];
230
+ if (spec.roleKey) {
231
+ rows.push(notesRow(`${prefix}.roleKey`, `Evidence roleKey "${spec.roleKey}" (preflight context).`));
232
+ }
233
+ if (spec.resourceType) {
234
+ rows.push(
235
+ notesRow(`${prefix}.resourceType`, `Evidence resourceType "${spec.resourceType}" (preflight context).`)
236
+ );
237
+ }
238
+ if (spec.requireStatus) {
239
+ rows.push(
240
+ notesRow(`${prefix}.requireStatus`, `Evidence requireStatus "${spec.requireStatus}" (preflight context).`)
241
+ );
242
+ }
243
+ return rows;
244
+ }
245
+
246
+ function collectCapabilityAssertions(capabilities, observedCapabilities, soft) {
247
+ const spec = capabilities && typeof capabilities === 'object' ? capabilities : {};
248
+ const rows = [
249
+ ...evaluateCapabilityExpectations('required', spec.required, observedCapabilities),
250
+ ...evaluateCapabilityExpectations('allowed', spec.allowed, observedCapabilities),
251
+ ...evaluateForbiddenCapabilities(spec.forbidden, observedCapabilities)
252
+ ];
253
+ return rows.map((row) => softWrapFailedAssertion(row, soft));
254
+ }
255
+
256
+ function evaluateExecutionIntegrity(observation) {
257
+ if (!observation.workResultError) {
258
+ return null;
259
+ }
260
+ return {
261
+ id: 'execution.integrity',
262
+ passed: false,
263
+ message: `Execution did not produce a readable result: ${observation.workResultError}`
264
+ };
265
+ }
266
+
267
+ function collectCaseAssertions(expected, observation, preflight = {}) {
156
268
  const soft = expected.soft === true;
157
269
  const observedCapabilities = observation.capabilities?.observed || [];
158
- const assertions = [];
270
+ const assertions = [evaluateExecutionIntegrity(observation)].filter(Boolean);
159
271
  const lifecycleAssertion = softWrapFailedAssertion(
160
272
  evaluateLifecycleStateExpectation(expected.lifecycleState, observation.lifecycleState),
161
273
  soft
@@ -163,31 +275,35 @@ function collectCaseAssertions(expected, observation) {
163
275
  if (lifecycleAssertion) {
164
276
  assertions.push(lifecycleAssertion);
165
277
  }
278
+ assertions.push(...collectSoftModifierRows(expected));
279
+ assertions.push(...collectCapabilityAssertions(expected.capabilities, observedCapabilities, soft));
166
280
  assertions.push(
167
- ...evaluateCapabilityExpectations(
168
- expected?.capabilities?.required,
169
- observedCapabilities
170
- ).map((row) => softWrapFailedAssertion(row, soft))
281
+ ...evaluateFactExpectations(expected.facts, observation).map((row) =>
282
+ softWrapFailedAssertion(row, soft)
283
+ )
171
284
  );
172
285
  assertions.push(
173
- ...evaluateForbiddenCapabilities(
174
- expected?.capabilities?.forbidden,
175
- observedCapabilities
176
- ).map((row) => softWrapFailedAssertion(row, soft))
286
+ ...evaluateProhibitedExpectations(expected.prohibited, observation).map((row) =>
287
+ softWrapFailedAssertion(row, soft)
288
+ )
177
289
  );
178
- assertions.push(...evaluateFactExpectations(expected.facts, observation));
179
- assertions.push(...evaluateProhibitedExpectations(expected.prohibited, observation));
180
290
  assertions.push(...evaluateHonestyExpectations(expected.honesty, observation, { soft }));
181
291
  assertions.push(...collectBidAssertions(expected.bid, observation, { soft }));
182
292
  assertions.push(...evaluateEvidenceExpectations(expected.evidence, observation));
293
+ assertions.push(...collectEvidenceContextRows(expected.evidence, 'expected.evidence'));
294
+ assertions.push(...collectEvidenceContextRows(preflight.evidence, 'preflight.evidence'));
183
295
  assertions.push(...evaluateSearchExpectations(expected.search, observation));
184
296
  assertions.push(...evaluateSkillsExpectations(expected.skills, observation));
185
- return assertions;
297
+ return assertions.map(withKind);
186
298
  }
187
299
 
188
300
  function evaluateRoleAssistantTestCase(testCase, result, lifecycleVerdict, portalUrl) {
189
301
  const observation = buildCaseObservation(result, lifecycleVerdict, portalUrl);
190
- const assertions = collectCaseAssertions(testCase.expected || {}, observation);
302
+ const assertions = collectCaseAssertions(
303
+ testCase.expected || {},
304
+ observation,
305
+ testCase.preflight || {}
306
+ );
191
307
  const failed = assertions.filter((row) => !row.passed);
192
308
  return {
193
309
  caseId: testCase.caseId,
@@ -202,5 +318,7 @@ function evaluateRoleAssistantTestCase(testCase, result, lifecycleVerdict, porta
202
318
  }
203
319
 
204
320
  module.exports = {
205
- evaluateRoleAssistantTestCase
321
+ evaluateRoleAssistantTestCase,
322
+ evaluateFactExpectations,
323
+ getValueAtPath
206
324
  };
@@ -55,6 +55,39 @@ function isActiveStatus(status, requiredStatus) {
55
55
  return status === requiredStatus;
56
56
  }
57
57
 
58
+ /**
59
+ * Dataplane Evidence is append-new versioned, never mutated in place
60
+ * (dataplane 506.2): once a row's content changes, its literal `key` is
61
+ * archived permanently and a new `<key>__vN` row becomes the current
62
+ * identity. A case authored against the original literal key must still
63
+ * resolve against that current versioned successor, not report it missing.
64
+ * `rows` here are already scoped to the case's roleKey+resourceType+status
65
+ * by `loadLiveEvidenceRows`, so any row whose key is exactly `<desired>__vN`
66
+ * is unambiguously that key's current version.
67
+ */
68
+ function findVersionedSuccessor(desiredKey, byKey) {
69
+ const prefix = `${desiredKey}__v`;
70
+ let best = null;
71
+ let bestVersion = -1;
72
+ for (const [key, row] of byKey) {
73
+ if (!key.startsWith(prefix)) {
74
+ continue;
75
+ }
76
+ const suffix = key.slice(prefix.length);
77
+ const version = Number(suffix);
78
+ if (!Number.isInteger(version) || version <= bestVersion) {
79
+ continue;
80
+ }
81
+ best = row;
82
+ bestVersion = version;
83
+ }
84
+ return best;
85
+ }
86
+
87
+ function resolveEvidenceRow(desiredKey, byKey) {
88
+ return byKey.get(desiredKey) || findVersionedSuccessor(desiredKey, byKey);
89
+ }
90
+
58
91
  function findMissingEvidenceKeys(spec, rows) {
59
92
  const byKey = new Map();
60
93
  for (const row of rows) {
@@ -65,7 +98,7 @@ function findMissingEvidenceKeys(spec, rows) {
65
98
  }
66
99
  const missing = [];
67
100
  for (const key of spec.requiredKeys) {
68
- const row = byKey.get(key);
101
+ const row = resolveEvidenceRow(key, byKey);
69
102
  if (!row) {
70
103
  missing.push(`${key} (not in dataplane)`);
71
104
  continue;
@@ -133,11 +166,15 @@ function evaluateEvidenceExpectations(evidenceSpec, observation) {
133
166
  return [];
134
167
  }
135
168
  const preflight = observation?.evidencePreflight || {};
136
- const liveKeys = new Set(
137
- (Array.isArray(preflight.rows) ? preflight.rows : []).map(rowKey).filter(Boolean)
138
- );
169
+ const liveByKey = new Map();
170
+ for (const row of Array.isArray(preflight.rows) ? preflight.rows : []) {
171
+ const key = rowKey(row);
172
+ if (key) {
173
+ liveByKey.set(key, row);
174
+ }
175
+ }
139
176
  return requiredKeys.map((key) => {
140
- const passed = liveKeys.has(key);
177
+ const passed = Boolean(resolveEvidenceRow(key, liveByKey));
141
178
  return {
142
179
  id: `expected.evidence.requiredKeys:${key}`,
143
180
  passed,
@@ -168,6 +205,8 @@ function evaluateForbiddenCapabilities(forbiddenList, observedCapabilities) {
168
205
 
169
206
  module.exports = {
170
207
  evidenceSpecFromCase,
208
+ findVersionedSuccessor,
209
+ resolveEvidenceRow,
171
210
  findMissingEvidenceKeys,
172
211
  assertRoleAssistantEvidencePreflight,
173
212
  evaluateEvidenceExpectations,