@cassiomc1/forgeloop 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/AGENT_COMPATIBILITY.md +8 -0
  2. package/DOCS_INDEX.md +35 -3
  3. package/ENG/nodejs-backend-development-eng.md +2 -2
  4. package/ENG/sec-code-eng.md +7 -7
  5. package/EXECUTION_STATE.md +12 -0
  6. package/LOOP_ENGINEERING.md +28 -2
  7. package/ORCHESTRATOR_INTEGRATION.md +9 -5
  8. package/PROTOCOL_INTEGRATION.md +55 -2
  9. package/README.md +40 -25
  10. package/TERMINOLOGY.md +2 -0
  11. package/THREAT_MODEL.md +140 -1
  12. package/completions/_forgeloop +19 -1
  13. package/completions/forgeloop.bash +37 -1
  14. package/completions/forgeloop.fish +123 -1
  15. package/docs/ADVISORY_CONTEXT.md +25 -0
  16. package/docs/AGENT_BROWSER_ADAPTER.md +81 -0
  17. package/docs/AGENT_BROWSER_VERIFICATION.md +6 -0
  18. package/docs/AGENT_PROTOCOL_SUMMARY.md +27 -2
  19. package/docs/AGENT_SKILL.md +66 -0
  20. package/docs/ARTIFACT_REFERENCE.md +123 -0
  21. package/docs/AUDIT_UX.md +46 -0
  22. package/docs/BROWSER_VERIFICATION.md +136 -0
  23. package/docs/CLI_REFERENCE.md +366 -6
  24. package/docs/CODE_ATTESTATION.md +2 -2
  25. package/docs/DOCUMENTATION_GUIDE.md +32 -11
  26. package/docs/JEV_BENCHMARKS.md +31 -0
  27. package/docs/MODEL_ROUTING.md +37 -0
  28. package/docs/OPENSRC_ADAPTER.md +241 -0
  29. package/docs/PACKAGE_CONTENTS.md +35 -8
  30. package/docs/PROVIDERS.md +126 -0
  31. package/docs/PROVIDER_ARCHITECTURE.md +199 -0
  32. package/docs/RECIPES.md +9 -0
  33. package/docs/RELEASE_CHECKLIST.md +38 -5
  34. package/docs/SECURITY_REVIEW.md +71 -0
  35. package/docs/SEMANTIC_DECISION_PLANE.md +71 -0
  36. package/docs/TEST_INTELLIGENCE.md +29 -0
  37. package/docs/TEST_PRUNING.md +14 -0
  38. package/docs/TROUBLESHOOTING.md +198 -1
  39. package/docs/UNIVERSAL_INTEGRATION.md +31 -0
  40. package/docs/assets/diagrams/forgeloop-code-attestation-flow.html +2 -2
  41. package/docs/assets/diagrams/forgeloop-code-attestation-flow.receipt.json +5 -5
  42. package/docs/assets/diagrams/forgeloop-code-attestation-flow.svg +1 -1
  43. package/docs/assets/diagrams/forgeloop-engineering-flow.html +39 -26
  44. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +6 -6
  45. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +26 -26
  46. package/docs/assets/diagrams/forgeloop-verification-trust-flow.html +2 -1
  47. package/docs/assets/diagrams/forgeloop-verification-trust-flow.receipt.json +5 -5
  48. package/docs/assets/diagrams/forgeloop-verification-trust-flow.svg +1 -1
  49. package/docs/diagrams/README.md +13 -9
  50. package/docs/diagrams/forgeloop-code-attestation-flow.workflow.json +1 -1
  51. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +24 -19
  52. package/docs/diagrams/forgeloop-verification-trust-flow.workflow.json +1 -0
  53. package/docs/diagrams/reviews/forgeloop-code-attestation-flow.review.json +4 -4
  54. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +4 -4
  55. package/docs/diagrams/reviews/forgeloop-verification-trust-flow.review.json +4 -4
  56. package/docs/documentation-manifest.json +750 -5
  57. package/docs/protocol-requirements.json +24 -0
  58. package/package.json +30 -3
  59. package/schemas/config.schema.json +14 -0
  60. package/schemas/context-plan.schema.json +18 -0
  61. package/schemas/semantic-decision.schema.json +46 -0
  62. package/schemas/test-utility.schema.json +44 -0
  63. package/scripts/CI_VALIDATORS.md +6 -6
  64. package/scripts/benchmark-jev.mjs +5 -0
  65. package/scripts/benchmark-test-intelligence.mjs +4 -0
  66. package/scripts/generate-agent-protocol-summary.mjs +4 -1
  67. package/scripts/generate-forgeloop-skill.mjs +133 -0
  68. package/scripts/jev-smoke.mjs +19 -0
  69. package/skills/forgeloop/README.md +9 -0
  70. package/skills/forgeloop/SKILL.md +77 -0
  71. package/skills/forgeloop/references/lifecycle.md +9 -0
  72. package/skills/forgeloop/references/recovery.md +7 -0
  73. package/skills/forgeloop/references/verification.md +7 -0
  74. package/src/adapters/agent-browser/assertions.js +47 -0
  75. package/src/adapters/agent-browser/commands.js +54 -0
  76. package/src/adapters/agent-browser/index.js +3 -0
  77. package/src/adapters/agent-browser/locator.js +40 -0
  78. package/src/adapters/agent-browser/process.js +215 -0
  79. package/src/adapters/agent-browser/provider.js +313 -0
  80. package/src/adapters/emulated-services/constants.js +24 -0
  81. package/src/adapters/emulated-services/index.js +7 -0
  82. package/src/adapters/emulated-services/process.js +162 -0
  83. package/src/adapters/emulated-services/provider.js +282 -0
  84. package/src/adapters/opensrc/normalize.js +90 -0
  85. package/src/adapters/opensrc/process.js +248 -0
  86. package/src/adapters/opensrc/provider.js +338 -0
  87. package/src/adapters/opensrc/search.js +264 -0
  88. package/src/adapters/typesafe/client.js +28 -0
  89. package/src/adapters/typesafe/engine.js +63 -0
  90. package/src/adapters/typesafe/normalize.js +41 -0
  91. package/src/cli.js +108 -0
  92. package/src/commands/checkpoint-revalidate.js +176 -0
  93. package/src/commands/context-plan.js +38 -0
  94. package/src/commands/contract-create.js +264 -0
  95. package/src/commands/contract-revise.js +236 -0
  96. package/src/commands/decision-show.js +14 -0
  97. package/src/commands/decision-status.js +22 -0
  98. package/src/commands/discover.js +41 -0
  99. package/src/commands/doctor.js +15 -0
  100. package/src/commands/gate-record.js +205 -0
  101. package/src/commands/gate-revalidate.js +137 -0
  102. package/src/commands/model-route.js +32 -0
  103. package/src/commands/route.js +146 -18
  104. package/src/commands/semantic-plan.js +17 -0
  105. package/src/commands/task-abandon.js +224 -0
  106. package/src/commands/task-migrate-contract-bootstrap-repair.js +288 -0
  107. package/src/commands/task-repair-contract-bootstrap.js +263 -0
  108. package/src/commands/test-inventory.js +5 -0
  109. package/src/commands/test-prune-plan.js +5 -0
  110. package/src/commands/test-prune-probe.js +5 -0
  111. package/src/commands/test-utility.js +5 -0
  112. package/src/commands/validate-protocol.js +10 -1
  113. package/src/core/artifact-registry.js +24 -0
  114. package/src/core/audit-ux.js +514 -0
  115. package/src/core/browser-verification/constants.js +149 -0
  116. package/src/core/browser-verification/normalize.js +254 -0
  117. package/src/core/browser-verification/provider.js +519 -0
  118. package/src/core/browser-verification/service.js +115 -0
  119. package/src/core/checkpoint-revalidation.js +319 -0
  120. package/src/core/cli-command-definitions.js +241 -0
  121. package/src/core/command-executors.js +110 -0
  122. package/src/core/command-input.js +115 -43
  123. package/src/core/completion-artifacts.js +14 -5
  124. package/src/core/completion.js +4 -6
  125. package/src/core/config.js +3 -0
  126. package/src/core/context-compiler/budget.js +9 -0
  127. package/src/core/context-compiler/candidates.js +39 -0
  128. package/src/core/context-compiler/compiler.js +63 -0
  129. package/src/core/context-compiler/fingerprint.js +11 -0
  130. package/src/core/context-compiler/policy.js +13 -0
  131. package/src/core/context-compiler/result.js +23 -0
  132. package/src/core/contract-bootstrap-recovery.js +655 -0
  133. package/src/core/contract-revision.js +210 -0
  134. package/src/core/decision/artifact.js +69 -0
  135. package/src/core/decision/benchmarks.js +103 -0
  136. package/src/core/decision/cache.js +27 -0
  137. package/src/core/decision/constants.js +58 -0
  138. package/src/core/decision/cutover.js +34 -0
  139. package/src/core/decision/engine.js +22 -0
  140. package/src/core/decision/errors.js +68 -0
  141. package/src/core/decision/events.js +101 -0
  142. package/src/core/decision/freshness.js +19 -0
  143. package/src/core/decision/normalizers/index.js +115 -0
  144. package/src/core/decision/policy.js +18 -0
  145. package/src/core/decision/projection.js +16 -0
  146. package/src/core/decision/question-registry.js +201 -0
  147. package/src/core/decision/request.js +26 -0
  148. package/src/core/decision/resolver.js +130 -0
  149. package/src/core/decision/result.js +58 -0
  150. package/src/core/decision/service.js +156 -0
  151. package/src/core/decision/state-builder.js +65 -0
  152. package/src/core/decision/task-bindings.js +30 -0
  153. package/src/core/decision/test-provider.js +32 -0
  154. package/src/core/decision/thresholds.js +15 -0
  155. package/src/core/error-codes.js +278 -0
  156. package/src/core/events.js +226 -57
  157. package/src/core/evidence-readiness.js +9 -0
  158. package/src/core/execution-prerequisites.js +14 -0
  159. package/src/core/execution-profile.js +63 -38
  160. package/src/core/gate-provenance.js +124 -0
  161. package/src/core/integration-invocation-policy.js +27 -4
  162. package/src/core/integration-resources.js +86 -61
  163. package/src/core/model-router/constants.js +10 -0
  164. package/src/core/model-router/policy.js +103 -0
  165. package/src/core/model-router/router.js +37 -0
  166. package/src/core/next-action-model.js +58 -0
  167. package/src/core/next-action-phases.js +130 -42
  168. package/src/core/next-action-refresh.js +43 -9
  169. package/src/core/next-action-review-phase.js +7 -2
  170. package/src/core/next-action.js +35 -7
  171. package/src/core/phase.js +128 -10
  172. package/src/core/preflight-consistency.js +23 -9
  173. package/src/core/preflight-loaders.js +37 -5
  174. package/src/core/protocol-info.js +65 -0
  175. package/src/core/protocol.js +20 -0
  176. package/src/core/reconcile-closure.js +128 -52
  177. package/src/core/recovery-history.js +1 -0
  178. package/src/core/resumability.js +154 -44
  179. package/src/core/route-artifact.js +15 -1
  180. package/src/core/router.js +67 -1
  181. package/src/core/runtime-context.js +118 -61
  182. package/src/core/schema-validation.js +3 -0
  183. package/src/core/security-review/constants.js +64 -0
  184. package/src/core/security-review/normalize.js +245 -0
  185. package/src/core/security-review/provider.js +204 -0
  186. package/src/core/security-review/service.js +134 -0
  187. package/src/core/semantic-planning/constants.js +19 -0
  188. package/src/core/semantic-planning/projection.js +94 -0
  189. package/src/core/semantic-planning/service.js +15 -0
  190. package/src/core/sources.js +37 -0
  191. package/src/core/task-claim-state.js +201 -1
  192. package/src/core/task-conflict-inspection.js +31 -5
  193. package/src/core/task-paths.js +13 -0
  194. package/src/core/task-recovery.js +1 -0
  195. package/src/core/templates.js +3 -0
  196. package/src/core/test-intelligence/benchmarks.js +68 -0
  197. package/src/core/test-intelligence/inventory.js +73 -0
  198. package/src/core/test-intelligence/prune.js +90 -0
  199. package/src/core/test-intelligence/semantic-state.js +15 -0
  200. package/src/core/test-intelligence/service.js +40 -0
  201. package/src/core/test-intelligence/utility.js +50 -0
  202. package/src/core/trace.js +11 -7
  203. package/src/core/transaction.js +1 -0
  204. package/src/integration.d.ts +492 -0
  205. package/src/integration.js +54 -0
  206. package/src/providers/README.md +47 -0
  207. package/src/providers/capabilities.js +46 -0
  208. package/src/providers/errors.js +15 -0
  209. package/src/providers/index.js +29 -0
  210. package/src/providers/json-snapshot.js +105 -0
  211. package/src/providers/registry.js +152 -0
@@ -0,0 +1,514 @@
1
+ import { runNext } from "../commands/next.js";
2
+ import { runReport } from "../commands/report.js";
3
+ import { runStatus } from "../commands/status.js";
4
+ import { evaluateAudit } from "./audit.js";
5
+ import { listApprovals } from "./approvals.js";
6
+ import { buildTaskHistory } from "./history.js";
7
+ import { INTEGRATION_LIMITS } from "./integration-limits.js";
8
+ import { resolveTaskClaimState } from "./task-claim-state.js";
9
+ import { findTaskById } from "./task-discovery.js";
10
+ import { buildTaskTrace } from "./trace.js";
11
+
12
+ export const AUDIT_UX_SCHEMA_VERSION = 1;
13
+
14
+ export const AUDIT_UX_CATEGORIES = Object.freeze([
15
+ "LIFECYCLE",
16
+ "CONTRACT",
17
+ "ROUTING",
18
+ "GATE",
19
+ "CHECK",
20
+ "DIAGNOSTIC",
21
+ "ACTION",
22
+ "APPROVAL",
23
+ "OWNERSHIP",
24
+ "COMPLETION",
25
+ "RECOVERY",
26
+ "INTEGRITY",
27
+ ]);
28
+
29
+ export const AUDIT_UX_LIMITS = Object.freeze({
30
+ maxTimelineItems: 200,
31
+ maxChecks: 64,
32
+ maxAttemptsPerCheck: 32,
33
+ maxDiagnostics: 64,
34
+ maxActions: 64,
35
+ maxApprovals: 64,
36
+ maxStringLength: 512,
37
+ maxOutputBytes: 256 * 1024,
38
+ });
39
+
40
+ const PHASES = new Set([
41
+ "RECEIVED", "DISCOVERING", "CONTRACT_READY", "ROUTED", "DESIGNING", "PLANNED",
42
+ "EXECUTING", "VERIFYING", "DIAGNOSING", "CORRECTING", "REVIEWING", "COMPLETE",
43
+ ]);
44
+
45
+ const SAFE_STATUSES = new Set([
46
+ "PASSED", "FAILED", "BLOCKED", "PENDING", "CURRENT", "STALE", "VALID", "INVALID",
47
+ "COMPLETE", "INCOMPLETE", "NOT_VERIFIED", "NOT_APPLICABLE", "UNKNOWN",
48
+ ]);
49
+
50
+ const EVENT_TITLES = Object.freeze({
51
+ TASK_RECEIVED: "Task received",
52
+ CONTRACT_VALIDATED: "Contract validated",
53
+ ROUTE_VALIDATED: "Route validated",
54
+ GATE_SATISFIED: "Gate satisfied",
55
+ PREFLIGHT_READY: "Preflight READY",
56
+ PREFLIGHT_BLOCKED: "Preflight BLOCKED",
57
+ DESIGN_GATE_STARTED: "Design phase started",
58
+ PLAN_RECORDED: "Plan recorded",
59
+ EXECUTION_STARTED: "Execution started",
60
+ VERIFICATION_STARTED: "Verification started",
61
+ VERIFICATION_RECORDED: "Verification recorded",
62
+ CHECK_RECORDED: "Check recorded",
63
+ TERMINAL_RESULT_RECORDED: "Terminal result recorded",
64
+ REVIEW_STARTED: "Review started",
65
+ DIAGNOSIS_RECORDED: "Diagnosis recorded",
66
+ DIAGNOSTIC_CASE_RECORDED: "Diagnostic case recorded",
67
+ HYPOTHESIS_DISPOSITION_RECORDED: "Hypothesis disposition recorded",
68
+ INTERVENTION_RECORDED: "Intervention recorded",
69
+ CONTINUITY_RECORDED: "Continuity recorded",
70
+ CHECKPOINT_RECONCILED: "Checkpoint reconciled",
71
+ TASK_RECOVERY_RECORDED: "Recovery recorded",
72
+ TASK_RECOVERY_RESUMED: "Recovery resumed",
73
+ OPERATOR_RECOVERY_RECORDED: "Operator recovery recorded",
74
+ LEGACY_RECOVERY_MIGRATION_RECORDED: "Legacy recovery migration recorded",
75
+ COMPLETION_VALIDATED: "Completion validated",
76
+ COMPLETION_REJECTED: "Completion rejected",
77
+ TRANSACTION_COMMITTED: "Transaction committed",
78
+ });
79
+
80
+ function auditUxError(code, message) {
81
+ const error = new Error(message);
82
+ error.code = code;
83
+ return error;
84
+ }
85
+
86
+ function boundedText(value, fallback = null) {
87
+ if (typeof value !== "string") return fallback;
88
+ const normalized = value.replace(/\s+/gu, " ").trim();
89
+ if (!normalized) return fallback;
90
+ return normalized.length > AUDIT_UX_LIMITS.maxStringLength
91
+ ? `${normalized.slice(0, AUDIT_UX_LIMITS.maxStringLength - 1)}…`
92
+ : normalized;
93
+ }
94
+
95
+ const URL_USERINFO_PATTERN = /\b([A-Za-z][A-Za-z0-9+.-]*):\/\/[^/\s@?#]+@/gu;
96
+ const COOKIE_HEADER_PATTERN = /\b(Cookie|Set-Cookie)\s*:\s*[^\r\n]*/giu;
97
+ const AUTHORIZATION_HEADER_PATTERN = /\b(Authorization|Proxy-Authorization)\s*:\s*[^\r\n]*/giu;
98
+ const CREDENTIAL_ASSIGNMENT_PATTERN = /\b(password|passwd|token|access_token|refresh_token|secret|client_secret|api_key|apikey)\s*=\s*[^\s,;]+/giu;
99
+ const ENVIRONMENT_PATTERN = /\b(?!(?:PASSWORD|PASSWD|TOKEN|ACCESS_TOKEN|REFRESH_TOKEN|SECRET|CLIENT_SECRET|API_KEY|APIKEY)\b)[A-Z_][A-Z0-9_]{2,}=[^\s,;]+/gu;
100
+ const FILE_URL_PATTERN = /\bfile:\/\/[^\s,;)]*/giu;
101
+ const WINDOWS_PATH_PATTERN = /(?:\b[A-Za-z]:[\\/]|\\\\)[^\s,;)]*/gu;
102
+ const POSIX_PATH_PATTERN = /(^|[^\w])\/(?:[A-Za-z0-9._~-]+(?:\/[A-Za-z0-9._~-]+)*)/gu;
103
+ const URL_PATTERN = /\b[A-Za-z][A-Za-z0-9+.-]*:\/\/[^\s,;)]*/gu;
104
+
105
+ function safeText(value, fallback = null) {
106
+ if (typeof value !== "string") return fallback;
107
+ const bounded = value.length > AUDIT_UX_LIMITS.maxStringLength
108
+ ? `${value.slice(0, AUDIT_UX_LIMITS.maxStringLength - 1)}…`
109
+ : value;
110
+ const text = bounded.trim();
111
+ if (!text) return fallback;
112
+ let redacted = text
113
+ .replace(URL_USERINFO_PATTERN, "$1://<credential>@")
114
+ .replace(COOKIE_HEADER_PATTERN, "$1: <credential>")
115
+ .replace(AUTHORIZATION_HEADER_PATTERN, "$1: <credential>")
116
+ .replace(CREDENTIAL_ASSIGNMENT_PATTERN, "$1=<credential>")
117
+ .replace(ENVIRONMENT_PATTERN, "<environment>")
118
+ .replace(FILE_URL_PATTERN, "<path>")
119
+ .replace(WINDOWS_PATH_PATTERN, "<path>");
120
+
121
+ const urls = [];
122
+ redacted = redacted.replace(URL_PATTERN, (url) => {
123
+ const marker = `__AUDIT_UX_URL_${urls.length}__`;
124
+ urls.push(url);
125
+ return marker;
126
+ });
127
+ redacted = redacted.replace(POSIX_PATH_PATTERN, "$1<path>");
128
+ return boundedText(
129
+ redacted.replace(/__AUDIT_UX_URL_(\d+)__/gu, (_, index) => urls[Number(index)]),
130
+ fallback,
131
+ );
132
+ }
133
+
134
+ function safeIdentifier(value) {
135
+ const text = boundedText(value);
136
+ return text && /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/u.test(text) ? text : null;
137
+ }
138
+
139
+ function safeStatus(value) {
140
+ const normalized = typeof value === "string" ? value.toUpperCase() : "UNKNOWN";
141
+ return SAFE_STATUSES.has(normalized) ? normalized : "UNKNOWN";
142
+ }
143
+
144
+ function safePhase(value) {
145
+ return typeof value === "string" && PHASES.has(value) ? value : null;
146
+ }
147
+
148
+ function uniqueStrings(values) {
149
+ return [...new Set(values.filter((value) => typeof value === "string" && value))].sort();
150
+ }
151
+
152
+ function projectErrors(errors = []) {
153
+ return errors.map((error) => ({
154
+ code: safeIdentifier(error?.code) ?? "E_AUDIT_UX_INVALID_ERROR",
155
+ message: safeText(error?.message, "Canonical validation reported an error."),
156
+ }));
157
+ }
158
+
159
+ function categoryForEvent(event) {
160
+ const type = event?.type ?? "";
161
+ if (type.startsWith("ACTION_")) return "ACTION";
162
+ if (type.startsWith("APPROVAL_")) return "APPROVAL";
163
+ if (type.includes("RECOVERY") || type === "CHECKPOINT_RECONCILED") return "RECOVERY";
164
+ if (type.startsWith("DIAGNOSTIC") || type.startsWith("DIAGNOSIS")
165
+ || type === "INTERVENTION_RECORDED" || type === "HYPOTHESIS_DISPOSITION_RECORDED") return "DIAGNOSTIC";
166
+ if (type === "GATE_SATISFIED") return "GATE";
167
+ if (type === "VERIFICATION_RECORDED" || type === "CHECK_RECORDED" || type === "TERMINAL_RESULT_RECORDED") return "CHECK";
168
+ if (type === "COMPLETION_VALIDATED" || type === "COMPLETION_REJECTED") return "COMPLETION";
169
+ if (type === "CONTRACT_VALIDATED" || type === "PLAN_RECORDED" || type === "DECISION_CRITERION_RECORDED") return "CONTRACT";
170
+ if (type === "ROUTE_VALIDATED") return "ROUTING";
171
+ if (type === "TRANSACTION_COMMITTED") return "INTEGRITY";
172
+ return "LIFECYCLE";
173
+ }
174
+
175
+ function eventLabel(event) {
176
+ const data = event?.data ?? {};
177
+ const title = EVENT_TITLES[event?.type] ?? "Protocol event";
178
+ if (event?.type === "GATE_SATISFIED") {
179
+ const gate = safeIdentifier(data.gate);
180
+ return gate ? `${title}: ${gate}` : title;
181
+ }
182
+ if (["VERIFICATION_RECORDED", "CHECK_RECORDED"].includes(event?.type)) {
183
+ const check = safeIdentifier(data.id ?? data.checkId ?? data.requirement);
184
+ const status = safeStatus(data.status);
185
+ return check ? `${title}: ${check} ${status}` : `${title}: ${status}`;
186
+ }
187
+ if (event?.type?.startsWith("ACTION_") || event?.type?.startsWith("APPROVAL_")) {
188
+ const id = safeIdentifier(data.actionId ?? data.approvalId);
189
+ return id ? `${title}: ${id}` : title;
190
+ }
191
+ return title;
192
+ }
193
+
194
+ function timelineItem(event) {
195
+ const data = event?.data ?? {};
196
+ const category = categoryForEvent(event);
197
+ const status = typeof data.status === "string" ? safeStatus(data.status) : null;
198
+ const phase = safePhase(event?.phase);
199
+ return {
200
+ id: `event-${event.sequence}`,
201
+ sequence: event.sequence,
202
+ timestamp: typeof event.timestamp === "string" ? event.timestamp : null,
203
+ timestampQuality: event.timestampQuality === "authoritative" ? "authoritative" : "unknown",
204
+ kind: safeIdentifier(event.type) ?? "UNKNOWN_EVENT",
205
+ phase,
206
+ status,
207
+ title: eventLabel(event),
208
+ summary: safeText(event.summary, "Protocol event recorded."),
209
+ category,
210
+ ...(event.phaseQuality ? { phaseQuality: event.phaseQuality } : {}),
211
+ };
212
+ }
213
+
214
+ function normalizeOptions(options = {}) {
215
+ const limit = options.limit === undefined ? 100 : options.limit;
216
+ if (!Number.isInteger(limit) || limit < 1 || limit > AUDIT_UX_LIMITS.maxTimelineItems) {
217
+ throw auditUxError("E_AUDIT_UX_INPUT_INVALID", `limit must be an integer between 1 and ${AUDIT_UX_LIMITS.maxTimelineItems}`);
218
+ }
219
+ for (const key of ["beforeSequence", "afterSequence"]) {
220
+ if (options[key] !== undefined && (!Number.isInteger(options[key]) || options[key] < 0)) {
221
+ throw auditUxError("E_AUDIT_UX_INPUT_INVALID", `${key} must be a non-negative integer`);
222
+ }
223
+ }
224
+ if (options.beforeSequence !== undefined && options.afterSequence !== undefined) {
225
+ throw auditUxError("E_AUDIT_UX_INPUT_INVALID", "beforeSequence and afterSequence cannot be combined");
226
+ }
227
+ let categories = options.categories ?? [];
228
+ if (typeof categories === "string") categories = categories.split(",").map((value) => value.trim()).filter(Boolean);
229
+ if (!Array.isArray(categories) || categories.length > AUDIT_UX_CATEGORIES.length) {
230
+ throw auditUxError("E_AUDIT_UX_INPUT_INVALID", "categories must be a bounded array of category names");
231
+ }
232
+ const normalizedCategories = uniqueStrings(categories.map((value) => String(value).toUpperCase()));
233
+ if (normalizedCategories.some((category) => !AUDIT_UX_CATEGORIES.includes(category))) {
234
+ throw auditUxError("E_AUDIT_UX_INPUT_INVALID", "categories contains an unknown audit timeline category");
235
+ }
236
+ return {
237
+ limit,
238
+ beforeSequence: options.beforeSequence,
239
+ afterSequence: options.afterSequence,
240
+ categories: normalizedCategories,
241
+ };
242
+ }
243
+
244
+ function projectTimeline(events, options) {
245
+ const filtered = events
246
+ .filter((event) => options.beforeSequence === undefined || event.sequence < options.beforeSequence)
247
+ .filter((event) => options.afterSequence === undefined || event.sequence > options.afterSequence)
248
+ .filter((event) => options.categories.length === 0 || options.categories.includes(categoryForEvent(event)))
249
+ .sort((left, right) => left.sequence - right.sequence);
250
+ const hasMore = filtered.length > options.limit;
251
+ const page = hasMore && options.afterSequence === undefined
252
+ ? filtered.slice(-options.limit)
253
+ : filtered.slice(0, options.limit);
254
+ const items = page.map(timelineItem);
255
+ return {
256
+ items,
257
+ totalAvailable: filtered.length,
258
+ truncated: hasMore,
259
+ cursor: {
260
+ beforeSequence: options.beforeSequence ?? null,
261
+ afterSequence: options.afterSequence ?? null,
262
+ nextBeforeSequence: hasMore && options.afterSequence === undefined ? items[0]?.sequence ?? null : null,
263
+ nextAfterSequence: hasMore && options.afterSequence !== undefined ? items.at(-1)?.sequence ?? null : null,
264
+ },
265
+ };
266
+ }
267
+
268
+ function safeCountMap(value) {
269
+ return Object.fromEntries(Object.entries(value ?? {})
270
+ .map(([key, count]) => [safeIdentifier(key) ?? "UNKNOWN", Number.isInteger(count) ? count : 0]));
271
+ }
272
+
273
+ function projectActions(actions) {
274
+ return {
275
+ total: Number.isInteger(actions.total) ? actions.total : 0,
276
+ byState: safeCountMap(actions.byState),
277
+ byCapability: safeCountMap(actions.byCapability),
278
+ required: Number.isInteger(actions.required) ? actions.required : 0,
279
+ ambiguous: Number.isInteger(actions.ambiguous) ? actions.ambiguous : 0,
280
+ failed: Number.isInteger(actions.failed) ? actions.failed : 0,
281
+ verified: Number.isInteger(actions.verified) ? actions.verified : 0,
282
+ trustedSatisfied: Number.isInteger(actions.trustedSatisfied) ? actions.trustedSatisfied : 0,
283
+ untrustedRequired: Number.isInteger(actions.untrustedRequired) ? actions.untrustedRequired : 0,
284
+ repeatedIdempotencyAttempts: Number.isInteger(actions.repeatedIdempotencyAttempts) ? actions.repeatedIdempotencyAttempts : 0,
285
+ reconciliationCount: Number.isInteger(actions.reconciliationCount) ? actions.reconciliationCount : 0,
286
+ eventCount: Number.isInteger(actions.eventCount) ? actions.eventCount : 0,
287
+ };
288
+ }
289
+
290
+ function projectChecks(trace) {
291
+ return trace.checks.slice(0, AUDIT_UX_LIMITS.maxChecks).map((check) => ({
292
+ id: safeIdentifier(check.id) ?? "unknown-check",
293
+ requirement: safeText(check.requirement),
294
+ attemptCount: check.attemptCount,
295
+ failedAttempts: check.failedAttempts,
296
+ currentResult: check.currentResult ? safeStatus(check.currentResult) : null,
297
+ attempts: check.attempts.slice(-AUDIT_UX_LIMITS.maxAttemptsPerCheck).map((attempt) => ({
298
+ sequence: Number.isInteger(attempt.sequence) ? attempt.sequence : null,
299
+ timestamp: typeof attempt.at === "string" ? attempt.at : null,
300
+ status: safeStatus(attempt.status),
301
+ exitCode: Number.isInteger(attempt.exitCode) ? attempt.exitCode : null,
302
+ requirement: safeText(attempt.requirement),
303
+ verificationCycle: Number.isInteger(attempt.verificationCycle) ? attempt.verificationCycle : null,
304
+ provenance: safeIdentifier(attempt.provenance),
305
+ executionMode: safeIdentifier(attempt.executionMode),
306
+ })),
307
+ }));
308
+ }
309
+
310
+ function projectDiagnostics(trace) {
311
+ const cases = trace.diagnostics.cases.slice(0, AUDIT_UX_LIMITS.maxDiagnostics).map((entry) => ({
312
+ sequence: entry.sequence,
313
+ timestamp: typeof entry.at === "string" ? entry.at : null,
314
+ verificationCycle: Number.isInteger(entry.verificationCycle) ? entry.verificationCycle : null,
315
+ diagnosticRevision: Number.isInteger(entry.diagnosticRevision) ? entry.diagnosticRevision : null,
316
+ failureClass: safeIdentifier(entry.failureClass),
317
+ informationGain: safeIdentifier(entry.informationGain),
318
+ }));
319
+ return {
320
+ legacyDiagnosisCount: trace.diagnostics.legacyDiagnoses.length,
321
+ cases,
322
+ caseCount: trace.diagnostics.cases.length,
323
+ interventionCount: trace.diagnostics.interventions.length,
324
+ dispositionCount: trace.diagnostics.dispositions.length,
325
+ invalidRevisionCount: trace.diagnostics.invalidRevisions.length,
326
+ };
327
+ }
328
+
329
+ function projectApprovals(approvals) {
330
+ return approvals.slice(0, AUDIT_UX_LIMITS.maxApprovals).map((approval) => ({
331
+ approvalId: safeIdentifier(approval.approvalId) ?? "unknown-approval",
332
+ actionId: safeIdentifier(approval.actionId),
333
+ status: safeStatus(approval.status),
334
+ decision: safeIdentifier(approval.decision),
335
+ requestedAt: typeof approval.requestedAt === "string" ? approval.requestedAt : null,
336
+ resolvedAt: typeof approval.resolvedAt === "string" ? approval.resolvedAt : null,
337
+ authorityKind: safeIdentifier(approval.authorityKind),
338
+ }));
339
+ }
340
+
341
+ function projectCompletion(audit, ownership) {
342
+ const completion = audit.completion ?? {};
343
+ const coverage = Array.isArray(completion.coverage) ? completion.coverage : [];
344
+ const requirementsSatisfied = coverage.length > 0 && coverage.every((item) => item.status === "COVERED");
345
+ const valid = completion.status === "VALID";
346
+ return {
347
+ state: valid ? "COMPLETE" : "INCOMPLETE",
348
+ valid,
349
+ requirementsSatisfied,
350
+ claimsReleased: ownership.claimState === "RELEASED_BY_COMPLETION",
351
+ publicationStatus: safeIdentifier(audit.publicationStatus),
352
+ productionReadiness: safeIdentifier(audit.productionReadiness),
353
+ reasonCodes: uniqueStrings(projectErrors(audit.errors).map((error) => error.code)),
354
+ coverage: coverage.slice(0, AUDIT_UX_LIMITS.maxChecks).map((item) => ({
355
+ requirement: safeText(item.requirement),
356
+ status: safeStatus(item.status),
357
+ })),
358
+ };
359
+ }
360
+
361
+ function projectReasonCodes(...collections) {
362
+ return uniqueStrings(collections.flat().map((value) => {
363
+ if (typeof value === "string") return value;
364
+ return value?.code;
365
+ }).map((value) => safeIdentifier(value)).filter(Boolean));
366
+ }
367
+
368
+ function buildProjection({ status, audit, report, history, trace, ownership, next, approvals, timeline }) {
369
+ const integrityErrors = [
370
+ ...projectErrors(audit.errors),
371
+ ...projectErrors(trace.integrity.errors),
372
+ ...projectErrors(history.integrity.errors),
373
+ ...projectErrors(ownership.ownershipErrors),
374
+ ];
375
+ const checks = projectChecks(trace);
376
+ const completion = projectCompletion(audit, ownership);
377
+ const reasonCodes = projectReasonCodes(
378
+ status.reasonCodes,
379
+ audit.errors,
380
+ trace.integrity.errors,
381
+ ownership.reasonCodes,
382
+ next.reasonCodes,
383
+ );
384
+ const auditStatus = safeIdentifier(audit.status) ?? "INVALID";
385
+ const reportVerdict = safeIdentifier(report.verdict);
386
+ const traceValid = trace.integrity.valid === true;
387
+ const historyValid = history.integrity.valid === true;
388
+ return {
389
+ schemaVersion: AUDIT_UX_SCHEMA_VERSION,
390
+ protocolVersion: trace.protocolVersion,
391
+ taskId: trace.task.id,
392
+ readOnly: true,
393
+ authority: {
394
+ readOnly: true,
395
+ lifecycleAuthority: false,
396
+ evidenceAuthority: false,
397
+ completionAuthority: false,
398
+ mutationAuthority: false,
399
+ externalExecution: false,
400
+ },
401
+ health: {
402
+ phase: safePhase(status.phase ?? trace.task.phase),
403
+ protocolValid: status.protocol?.status === "VALID",
404
+ auditStatus,
405
+ reportVerdict,
406
+ completionStatus: completion.valid ? "VALID" : "INCOMPLETE",
407
+ ownershipValid: ownership.ownershipValid === true,
408
+ claimState: safeIdentifier(ownership.claimState),
409
+ mutationAllowed: ownership.mutationAllowed === true,
410
+ recoveryStatus: safeIdentifier(ownership.recoveryStatus),
411
+ currentNextAction: safeIdentifier(next.nextAction),
412
+ historyQuality: safeIdentifier(history.historyQuality.level),
413
+ traceIntegrity: traceValid,
414
+ reasonCodes,
415
+ },
416
+ lifecycle: {
417
+ phase: safePhase(trace.task.phase),
418
+ status: safeIdentifier(trace.task.status),
419
+ revision: Number.isInteger(trace.task.revision) ? trace.task.revision : null,
420
+ verificationCycle: Number.isInteger(trace.task.verificationCycle) ? trace.task.verificationCycle : null,
421
+ nextAction: safeIdentifier(next.nextAction),
422
+ terminal: next.terminal === true,
423
+ },
424
+ timeline,
425
+ verification: {
426
+ checks,
427
+ checkCount: trace.checks.length,
428
+ totalAttempts: trace.checks.reduce((total, check) => total + check.attemptCount, 0),
429
+ failedAttempts: trace.checks.reduce((total, check) => total + check.failedAttempts, 0),
430
+ coverage: completion.coverage,
431
+ },
432
+ ownership: {
433
+ claimState: safeIdentifier(ownership.claimState),
434
+ ownershipValid: ownership.ownershipValid === true,
435
+ mutationAllowed: ownership.mutationAllowed === true,
436
+ recoveryStatus: safeIdentifier(ownership.recoveryStatus),
437
+ historicalWriteClaims: ownership.historicalWriteClaims.slice(0, AUDIT_UX_LIMITS.maxActions).map(safeText).filter(Boolean),
438
+ effectiveWriteClaims: ownership.effectiveWriteClaims.slice(0, AUDIT_UX_LIMITS.maxActions).map(safeText).filter(Boolean),
439
+ reasonCodes: projectReasonCodes(ownership.reasonCodes, ownership.ownershipErrors),
440
+ },
441
+ diagnostics: projectDiagnostics(trace),
442
+ completion,
443
+ integrity: {
444
+ valid: auditStatus === "VALID" && traceValid && historyValid,
445
+ auditStatus,
446
+ traceValid,
447
+ historyValid,
448
+ reasonCodes: projectReasonCodes(integrityErrors),
449
+ },
450
+ history: {
451
+ quality: safeIdentifier(history.historyQuality.level),
452
+ totalEventCount: history.summary.totalEventCount,
453
+ returnedEventCount: timeline.items.length,
454
+ truncated: timeline.truncated,
455
+ },
456
+ actions: {
457
+ ...projectActions(trace.actions),
458
+ bounded: trace.actions.total > AUDIT_UX_LIMITS.maxActions,
459
+ },
460
+ approvals: projectApprovals(approvals),
461
+ recovery: {
462
+ active: ownership.recoveryStatus === "RELEASED_BY_RECOVERY",
463
+ status: safeIdentifier(ownership.recoveryStatus),
464
+ events: trace.recovery.slice(0, AUDIT_UX_LIMITS.maxDiagnostics).map((event) => ({
465
+ sequence: event.sequence,
466
+ timestamp: typeof event.at === "string" ? event.at : null,
467
+ kind: safeIdentifier(event.type),
468
+ })),
469
+ },
470
+ links: {
471
+ status: "task/status",
472
+ ownership: "task/ownership",
473
+ contract: "task/contract",
474
+ auditView: "task/audit-view",
475
+ },
476
+ };
477
+ }
478
+
479
+ export async function buildAuditUxView({
480
+ target,
481
+ packageRoot,
482
+ taskId,
483
+ limit,
484
+ beforeSequence,
485
+ afterSequence,
486
+ categories,
487
+ authorityContext,
488
+ runtimeContext,
489
+ } = {}) {
490
+ if (typeof taskId !== "string" || !taskId.trim()) {
491
+ throw auditUxError("E_TASK_REQUIRED", "Resource task/audit-view requires a taskId");
492
+ }
493
+ const options = normalizeOptions({ limit, beforeSequence, afterSequence, categories });
494
+ const task = await findTaskById(target, taskId, packageRoot);
495
+ if (!task) throw auditUxError("E_TASK_NOT_FOUND", `Task ${taskId} does not exist`);
496
+
497
+ const [status, audit, report, history, trace, ownership, next, approvals] = await Promise.all([
498
+ runStatus({ target, packageRoot, taskId }),
499
+ evaluateAudit({ target, packageRoot, taskId, authorityContext, runtimeContext }),
500
+ runReport({ target, packageRoot, taskId }),
501
+ buildTaskHistory({ target, packageRoot, taskId }),
502
+ buildTaskTrace({ target, packageRoot, taskId }),
503
+ resolveTaskClaimState(target, { packageRoot, taskId }),
504
+ runNext({ target, packageRoot, taskId, authorityContext, runtimeContext, compact: false }),
505
+ listApprovals(target, { packageRoot, taskId }),
506
+ ]);
507
+ const timeline = projectTimeline(trace.events, options);
508
+ const projection = buildProjection({ status, audit, report, history, trace, ownership, next, approvals, timeline });
509
+ const serializedBytes = Buffer.byteLength(JSON.stringify(projection), "utf8");
510
+ if (serializedBytes > AUDIT_UX_LIMITS.maxOutputBytes || serializedBytes > INTEGRATION_LIMITS.maxOutputBytes) {
511
+ throw auditUxError("E_AUDIT_UX_OUTPUT_LIMIT", "Audit UX projection exceeds its bounded output limit");
512
+ }
513
+ return projection;
514
+ }
@@ -0,0 +1,149 @@
1
+ import {
2
+ E_BROWSER_VERIFICATION_REQUEST_INVALID,
3
+ } from "../error-codes.js";
4
+
5
+ /**
6
+ * Browser verification contract constants.
7
+ *
8
+ * Browser verification is an integration-only observation: bounded request
9
+ * (origins, steps, assertions), untrusted provider output normalized under
10
+ * strict budgets, constant trust fields, no clock reading, no network in
11
+ * core. Providers are host-injected through runtime context.
12
+ */
13
+
14
+ export const BROWSER_VERIFICATION_LIMITS = Object.freeze({
15
+ maxVerificationIdChars: 128,
16
+ maxRequirementChars: 2000,
17
+ maxAllowedOrigins: 8,
18
+ maxSteps: 64,
19
+ maxAssertions: 64,
20
+ maxStepIdChars: 128,
21
+ maxAssertionIdChars: 128,
22
+ maxUrlChars: 2048,
23
+ maxViewportWidth: 4096,
24
+ maxViewportHeight: 4096,
25
+ maxLocatorChars: 512,
26
+ maxInputChars: 4096,
27
+ maxAttributeChars: 128,
28
+ maxExpectedChars: 4096,
29
+ maxActualChars: 8192,
30
+ maxSnapshotChars: 131072,
31
+ maxDiagnosticChars: 4000,
32
+ maxDiagnostics: 32,
33
+ maxArtifacts: 16,
34
+ maxArtifactRefChars: 1024,
35
+ maxResultChars: 524288,
36
+ maxNavigations: 128,
37
+ maxSnapshots: 16,
38
+ maxArtifactBytes: 50_000_000,
39
+ defaultTimeoutMs: 30_000,
40
+ maxTimeoutMs: 120_000,
41
+ });
42
+
43
+ export const BROWSER_VERIFICATION_STEP_KINDS = Object.freeze([
44
+ "NAVIGATE",
45
+ "CLICK",
46
+ "FILL",
47
+ "PRESS",
48
+ "WAIT_FOR",
49
+ ]);
50
+
51
+ export const BROWSER_VERIFICATION_ASSERTION_KINDS = Object.freeze([
52
+ "VISIBLE",
53
+ "HIDDEN",
54
+ "TEXT_CONTAINS",
55
+ "TEXT_EQUALS",
56
+ "VALUE_EQUALS",
57
+ "ATTRIBUTE_EQUALS",
58
+ "URL_IS",
59
+ "URL_PREFIX",
60
+ "TITLE_EQUALS",
61
+ ]);
62
+
63
+ export const BROWSER_VERIFICATION_ASSERTION_STATUSES = Object.freeze([
64
+ "PASS",
65
+ "FAIL",
66
+ "BLOCKED",
67
+ ]);
68
+
69
+ export const BROWSER_VERIFICATION_RESULT_STATUSES = Object.freeze([
70
+ "PASS",
71
+ "FAIL",
72
+ "BLOCKED",
73
+ ]);
74
+
75
+ export const BROWSER_VERIFICATION_LOCATOR_KINDS = Object.freeze([
76
+ "ROLE",
77
+ "LABEL",
78
+ "TEXT",
79
+ "CSS",
80
+ ]);
81
+
82
+ export const BROWSER_VERIFICATION_WAIT_CONDITIONS = Object.freeze([
83
+ "VISIBLE",
84
+ "HIDDEN",
85
+ "TEXT_CONTAINS",
86
+ "URL_IS",
87
+ "URL_PREFIX",
88
+ ]);
89
+
90
+ export const BROWSER_VERIFICATION_CAPTURE_POLICIES = Object.freeze([
91
+ "NEVER",
92
+ "ON_FAILURE",
93
+ "ALWAYS",
94
+ ]);
95
+
96
+ export const BROWSER_VERIFICATION_TRUST = Object.freeze({
97
+ authority: "OBSERVATION",
98
+ evidenceAuthority: "NONE",
99
+ actionability: "NON_EXECUTABLE",
100
+ trustRole: "NON_EVIDENCE_BROWSER_VERIFICATION",
101
+ persisted: false,
102
+ lifecycleAuthority: false,
103
+ completionAuthority: false,
104
+ evidenceRequiresForgeLoopValidation: true,
105
+ });
106
+
107
+ export const BROWSER_VERIFICATION_RESULT_FIELDS = Object.freeze([
108
+ "status", "assertions", "finalUrl", "navigations", "diagnostics", "snapshots", "artifacts",
109
+ ]);
110
+
111
+ export const BROWSER_VERIFICATION_BLOCKED_RESULT_FIELDS = Object.freeze([
112
+ "complete", "completed", "nextAction", "releaseClaims", "mutationAllowed", "taskPhase",
113
+ "lifecycleState", "receipt", "evidence", "evidenceKind", "provenance", "authority",
114
+ "lifecycleAuthority", "completionAuthority", "evidenceAuthority", "requiredForCompletion",
115
+ ]);
116
+
117
+ function verificationError(message, details) {
118
+ const error = new Error(message);
119
+ error.code = E_BROWSER_VERIFICATION_REQUEST_INVALID;
120
+ error.details = details ?? null;
121
+ return error;
122
+ }
123
+
124
+ /**
125
+ * Normalize explicit timeout input. Valid finite values pass through;
126
+ * oversized finite values clamp to max; missing/invalid values fall back
127
+ * to default. Non-finite values fail closed.
128
+ */
129
+ export function normalizeBrowserVerificationRequestOptions(input) {
130
+ const source = input ?? {};
131
+ const candidate = source.timeoutMs;
132
+ if (candidate === undefined || candidate === null) {
133
+ return Object.freeze({ timeoutMs: BROWSER_VERIFICATION_LIMITS.defaultTimeoutMs });
134
+ }
135
+ if (typeof candidate !== "number" || !Number.isFinite(candidate)) {
136
+ throw verificationError("Browser verification timeoutMs must be a finite number.", {
137
+ timeoutMs: candidate,
138
+ });
139
+ }
140
+ if (!Number.isInteger(candidate) || candidate <= 0) {
141
+ throw verificationError("Browser verification timeoutMs must be a positive integer.", {
142
+ timeoutMs: candidate,
143
+ });
144
+ }
145
+ if (candidate > BROWSER_VERIFICATION_LIMITS.maxTimeoutMs) {
146
+ return Object.freeze({ timeoutMs: BROWSER_VERIFICATION_LIMITS.maxTimeoutMs });
147
+ }
148
+ return Object.freeze({ timeoutMs: candidate });
149
+ }