@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,58 @@
1
+ import { PINNED_JEV_MODEL } from "./constants.js";
2
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
3
+ import { normalizeCanonicalDecision } from "./normalizers/index.js";
4
+
5
+ function finite(value) { return typeof value === "number" && Number.isFinite(value); }
6
+
7
+ function normalizeAnswer(value) {
8
+ if (value === null || typeof value === "string" || typeof value === "boolean" || finite(value)) return value;
9
+ if (value && typeof value === "object" && !Array.isArray(value)) {
10
+ const result = {};
11
+ for (const [key, child] of Object.entries(value)) result[key] = normalizeAnswer(child);
12
+ return result;
13
+ }
14
+ if (Array.isArray(value)) return value.slice(0, 64).map(normalizeAnswer);
15
+ throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision answer has an unsupported shape.");
16
+ }
17
+
18
+ function normalizeAnswers(rawAnswers, questionSet) {
19
+ return Object.fromEntries(Object.keys(questionSet.questions).map((key) => {
20
+ if (!Object.prototype.hasOwnProperty.call(rawAnswers, key)) {
21
+ throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, `Semantic decision result is missing answer ${key}.`);
22
+ }
23
+ return [key, normalizeAnswer(rawAnswers[key])];
24
+ }));
25
+ }
26
+
27
+ function normalizeConfidence(confidence, questionSet) {
28
+ if (!confidence || typeof confidence !== "object" || Array.isArray(confidence)) {
29
+ throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision result confidence is invalid.");
30
+ }
31
+ return Object.fromEntries(Object.keys(questionSet.questions).map((key) => [key, confidence[key] ?? null]));
32
+ }
33
+
34
+ function normalizeUsage(raw) {
35
+ const inputTokens = raw?.input_tokens ?? raw?.inputTokens;
36
+ const outputTokens = raw?.output_tokens ?? raw?.outputTokens;
37
+ if (!Number.isInteger(inputTokens) || inputTokens < 0 || !Number.isInteger(outputTokens) || outputTokens < 0) {
38
+ throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision result usage is invalid.");
39
+ }
40
+ return { inputTokens, outputTokens, reportedBy: "PROVIDER" };
41
+ }
42
+
43
+ export function normalizeDecisionResult(raw, { questionSet, input = {}, model = PINNED_JEV_MODEL } = {}) {
44
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision result must be an object.");
45
+ if (raw.model !== model || raw.model !== PINNED_JEV_MODEL) throw decisionError(DECISION_ERROR_CODES.MODEL_UNSUPPORTED, "Semantic decision result used an unsupported model.");
46
+ if (!raw.answers || typeof raw.answers !== "object" || Array.isArray(raw.answers)) throw decisionError(DECISION_ERROR_CODES.RESULT_INVALID, "Semantic decision result is missing answers.");
47
+ const answers = normalizeAnswers(raw.answers, questionSet);
48
+ const confidence = normalizeConfidence(raw.confidence, questionSet);
49
+ const usage = normalizeUsage(raw.usage);
50
+ return {
51
+ model,
52
+ questionSetId: questionSet?.id ?? null,
53
+ answers,
54
+ confidence: Object.fromEntries(Object.keys(questionSet.questions).map((key) => [key, confidence[key] ?? null])),
55
+ decision: normalizeCanonicalDecision({ questionSet, answers, confidence, input }),
56
+ usage,
57
+ };
58
+ }
@@ -0,0 +1,156 @@
1
+ import { canonicalFingerprint } from "../artifacts.js";
2
+ import { readConfig } from "../config.js";
3
+ import { appendProtocolEvent, readEvents } from "../events.js";
4
+ import { withTaskMutation } from "../task-command.js";
5
+ import { DECISION_DEFAULT_POLICY, PINNED_JEV_MODEL, SEMANTIC_DECISION_RECORDED_EVENT } from "./constants.js";
6
+ import { buildDecisionArtifact, decisionArtifactFingerprint, readDecisionArtifact, writeDecisionArtifact } from "./artifact.js";
7
+ import { decisionEventDetails, decisionSupersededEventDetails } from "./events.js";
8
+ import { createTypesafeEngine } from "../../adapters/typesafe/engine.js";
9
+ import { normalizeDecisionPolicy, decisionPolicyFingerprint } from "./policy.js";
10
+ import { validateDecisionRequest } from "./request.js";
11
+ import { normalizeDecisionResult } from "./result.js";
12
+ import { randomUUID } from "node:crypto";
13
+ import { readCurrentDecisionBindings } from "./task-bindings.js";
14
+ import { resolveRequiredSemanticDecision } from "./resolver.js";
15
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
16
+ import { getTaskTransaction } from "../transaction.js";
17
+ import { getTestSemanticProvider } from "./test-provider.js";
18
+
19
+ function resolveProvider(provider) {
20
+ return provider ?? getTestSemanticProvider();
21
+ }
22
+
23
+ async function loadPolicy(target, packageRoot) {
24
+ try {
25
+ const config = await readConfig(target, packageRoot);
26
+ return normalizeDecisionPolicy(config.decisionEngine ?? DECISION_DEFAULT_POLICY);
27
+ } catch (error) {
28
+ if (error.code === "ARTIFACT_MISSING") return normalizeDecisionPolicy(DECISION_DEFAULT_POLICY);
29
+ throw error;
30
+ }
31
+ }
32
+
33
+ async function readCachedDecision({ target, packageRoot, taskId, decisionKind, currentBindings }) {
34
+ try {
35
+ return await resolveRequiredSemanticDecision({ target, packageRoot, taskId, decisionKind, currentBindings });
36
+ } catch (error) {
37
+ if (["E_DECISION_REQUIRED", "E_DECISION_STALE", "E_DECISION_BINDING_INVALID"].includes(error.code)) return null;
38
+ throw error;
39
+ }
40
+ }
41
+
42
+ function buildArtifact({ taskId, decisionId, policy, validated, result, request, taskBindings }) {
43
+ return buildDecisionArtifact({
44
+ taskId,
45
+ decisionId,
46
+ decisionKind: validated.decisionKind,
47
+ model: result.model ?? PINNED_JEV_MODEL,
48
+ questionSetId: validated.questionSet.id,
49
+ questionSetVersion: validated.questionSet.version,
50
+ questionSetFingerprint: validated.questionSet.fingerprint,
51
+ questionSet: validated.questionSet,
52
+ stateFingerprint: canonicalFingerprint(validated.state),
53
+ taskStateFingerprint: canonicalFingerprint(validated.state.lifecycle),
54
+ semanticStateFingerprint: canonicalFingerprint(validated.state.semantic),
55
+ policyVersion: policy.policyVersion,
56
+ policyFingerprint: decisionPolicyFingerprint(policy),
57
+ repositoryFingerprint: request.repositoryFingerprint ?? taskBindings.repositoryFingerprint,
58
+ contractFingerprint: request.contractFingerprint ?? taskBindings.contractFingerprint,
59
+ routeFingerprint: request.routeFingerprint ?? taskBindings.routeFingerprint,
60
+ verificationCycle: request.verificationCycle ?? taskBindings.verificationCycle,
61
+ candidateSetFingerprint: request.candidateSetFingerprint,
62
+ answers: result.answers,
63
+ confidence: result.confidence ?? {},
64
+ decision: result.decision,
65
+ usage: result.usage,
66
+ latencyMs: result.latencyMs ?? null,
67
+ });
68
+ }
69
+
70
+ async function readCachedSemanticDecision({ policy, decisionId, target, taskId, packageRoot, validated, taskBindings, request }) {
71
+ if (!policy.cache || decisionId || !target || !taskId || await getTaskTransaction(target)) return null;
72
+ return readCachedDecision({
73
+ target,
74
+ packageRoot,
75
+ taskId,
76
+ decisionKind: validated.decisionKind,
77
+ currentBindings: {
78
+ ...taskBindings,
79
+ stateFingerprint: canonicalFingerprint(validated.state),
80
+ taskStateFingerprint: canonicalFingerprint(validated.state.lifecycle),
81
+ semanticStateFingerprint: canonicalFingerprint(validated.state.semantic),
82
+ questionSetFingerprint: validated.questionSet.fingerprint,
83
+ policyFingerprint: decisionPolicyFingerprint(policy),
84
+ model: PINNED_JEV_MODEL,
85
+ ...(request?.candidateSetFingerprint !== undefined ? { candidateSetFingerprint: request.candidateSetFingerprint } : {}),
86
+ ...(request?.policyFingerprint !== undefined ? { policyFingerprint: request.policyFingerprint } : {}),
87
+ },
88
+ });
89
+ }
90
+
91
+ async function persistDecision(target, packageRoot, resolvedDecisionId, artifact, ctx) {
92
+ await writeDecisionArtifact(target, ctx.taskId, resolvedDecisionId, artifact, packageRoot, { taskId: ctx.taskId, operation: "semantic-decision" });
93
+ const priorEvents = await readEvents(target, packageRoot, { taskId: ctx.taskId });
94
+ const event = await appendProtocolEvent(target, { taskId: ctx.taskId, event: SEMANTIC_DECISION_RECORDED_EVENT, details: decisionEventDetails(artifact) }, packageRoot, { taskId: ctx.taskId });
95
+ const previous = priorEvents.findLast((candidate) => candidate.event === SEMANTIC_DECISION_RECORDED_EVENT
96
+ && candidate.taskId === ctx.taskId
97
+ && candidate.details?.decisionKind === artifact.decisionKind
98
+ && !priorEvents.some((superseded) => superseded.event === "SEMANTIC_DECISION_SUPERSEDED"
99
+ && superseded.details?.decisionId === candidate.details?.decisionId));
100
+ if (!previous) return { artifact, artifactFingerprint: decisionArtifactFingerprint(artifact), event, supersededEvent: null };
101
+ const previousArtifact = await readDecisionArtifact(target, ctx.taskId, previous.details.decisionId, packageRoot);
102
+ const supersededEvent = await appendProtocolEvent(target, {
103
+ taskId: ctx.taskId,
104
+ event: "SEMANTIC_DECISION_SUPERSEDED",
105
+ details: decisionSupersededEventDetails(previousArtifact.value, resolvedDecisionId),
106
+ }, packageRoot, { taskId: ctx.taskId });
107
+ return { artifact, artifactFingerprint: decisionArtifactFingerprint(artifact), event, supersededEvent };
108
+ }
109
+
110
+ export async function recordSemanticDecision({ target, packageRoot, taskId, decisionId, request, provider = null } = {}) {
111
+ const resolvedDecisionId = decisionId ?? `${String(request?.decisionKind ?? "decision").toLowerCase()}-${Date.now()}-${randomUUID().slice(0, 8)}`;
112
+ const policy = await loadPolicy(target, packageRoot);
113
+ const taskBindings = target && taskId ? await readCurrentDecisionBindings(target, packageRoot, taskId) : {};
114
+ const validated = validateDecisionRequest({
115
+ ...request,
116
+ taskId,
117
+ state: { lifecycle: taskBindings.state ?? {}, semantic: request?.state ?? {} },
118
+ });
119
+ const cached = await readCachedSemanticDecision({ policy, decisionId, target, taskId, packageRoot, validated, taskBindings, request });
120
+ if (cached) return { artifact: cached, artifactFingerprint: decisionArtifactFingerprint(cached), event: null, supersededEvent: null, cached: true, policy };
121
+ const engine = resolveProvider(provider) ?? createTypesafeEngine({ policy });
122
+ if (provider && (provider.id !== "typesafe-jev" || provider.model !== PINNED_JEV_MODEL || typeof provider.evaluate !== "function")) {
123
+ throw decisionError(DECISION_ERROR_CODES.MODEL_UNSUPPORTED, "Semantic decision provider must be the pinned ForgeLoop Jev engine.");
124
+ }
125
+ const result = normalizeDecisionResult(await engine.evaluate(validated), { questionSet: validated.questionSet, input: validated });
126
+ const artifact = buildArtifact({ taskId, decisionId: resolvedDecisionId, policy, validated, result, request, taskBindings });
127
+ const persisted = await withTaskMutation(target, { taskId, packageRoot }, "semantic-decision", (ctx) => persistDecision(target, packageRoot, resolvedDecisionId, artifact, ctx), { explicitRequired: true });
128
+ return { ...persisted, policy };
129
+ }
130
+
131
+ export async function ensureSemanticDecision({ target, packageRoot, taskId, decisionId, request, provider = null, allowNetwork = true } = {}) {
132
+ const policy = await loadPolicy(target, packageRoot);
133
+ const taskBindings = await readCurrentDecisionBindings(target, packageRoot, taskId);
134
+ const validated = validateDecisionRequest({
135
+ ...request,
136
+ taskId,
137
+ state: { lifecycle: taskBindings.state ?? {}, semantic: request?.state ?? {} },
138
+ });
139
+ const currentBindings = {
140
+ ...taskBindings,
141
+ stateFingerprint: canonicalFingerprint(validated.state),
142
+ taskStateFingerprint: canonicalFingerprint(validated.state.lifecycle),
143
+ semanticStateFingerprint: canonicalFingerprint(validated.state.semantic),
144
+ questionSetFingerprint: validated.questionSet.fingerprint,
145
+ policyFingerprint: decisionPolicyFingerprint(policy),
146
+ model: PINNED_JEV_MODEL,
147
+ ...(request?.candidateSetFingerprint !== undefined ? { candidateSetFingerprint: request.candidateSetFingerprint } : {}),
148
+ };
149
+ const cached = await getTaskTransaction(target) ? null : await readCachedDecision({
150
+ target, packageRoot, taskId, decisionKind: validated.decisionKind,
151
+ currentBindings,
152
+ });
153
+ if (cached) return { artifact: cached, artifactFingerprint: decisionArtifactFingerprint(cached), cached: true, policy };
154
+ if (!allowNetwork && !provider) throw decisionError(DECISION_ERROR_CODES.REQUIRED, `A current ${validated.decisionKind} decision is required.`);
155
+ return recordSemanticDecision({ target, packageRoot, taskId, decisionId, request: { ...request, questionSet: validated.questionSet }, provider });
156
+ }
@@ -0,0 +1,65 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { DECISION_LIMITS } from "./constants.js";
3
+ import { DECISION_ERROR_CODES, decisionError } from "./errors.js";
4
+
5
+ const SECRET_KEY = /(^|[_-])(api[_-]?key|password|passwd|secret|token|refresh|cookie|authorization|private[_-]?key|credential|database[_-]?url|connection[_-]?string|access[_-]?key|client[_-]?secret)([_-]|$)/i;
6
+ const SECRET_VALUE = /^(?:bearer\s+)?(?:sk-|pk-|ghp_|github_pat_|xox[baprs]-|AIza|AKIA)[A-Za-z0-9._-]{12,}$/i;
7
+
8
+ function isHomePath(value) {
9
+ return typeof value === "string" && (/^\/Users\/[^/]+\//.test(value) || /^\/home\/[^/]+\//.test(value));
10
+ }
11
+
12
+ function sanitize(value, depth, counters) {
13
+ if (depth > DECISION_LIMITS.maxDepth) throw decisionError(DECISION_ERROR_CODES.STATE_LIMIT, "Decision state nesting exceeds the supported limit.");
14
+ if (value === null || typeof value === "boolean" || typeof value === "number") return value;
15
+ if (typeof value === "string") {
16
+ if (/[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD]/u.test(value)) throw decisionError(DECISION_ERROR_CODES.STATE_UNSAFE, "Decision state contains unsafe control characters.");
17
+ if (SECRET_VALUE.test(value)) throw decisionError(DECISION_ERROR_CODES.STATE_UNSAFE, "Decision state contains a secret-like value.");
18
+ if (isHomePath(value)) return "<redacted-user-path>";
19
+ counters.bytes += Buffer.byteLength(value);
20
+ if (value.length > DECISION_LIMITS.maxStringChars) return `${value.slice(0, DECISION_LIMITS.maxStringChars)}…<truncated>`;
21
+ return value;
22
+ }
23
+ if (Array.isArray(value)) {
24
+ if (value.length > DECISION_LIMITS.maxItems) throw decisionError(DECISION_ERROR_CODES.STATE_LIMIT, "Decision state contains too many items.");
25
+ return value.map((item) => sanitize(item, depth + 1, counters));
26
+ }
27
+ if (typeof value === "object") {
28
+ const entries = Object.entries(value);
29
+ if (entries.length > DECISION_LIMITS.maxItems) throw decisionError(DECISION_ERROR_CODES.STATE_LIMIT, "Decision state contains too many fields.");
30
+ const result = {};
31
+ for (const [key, child] of entries) {
32
+ if (SECRET_KEY.test(key) || key.toLowerCase() === "env" || key.toLowerCase() === "environment") continue;
33
+ result[key] = sanitize(child, depth + 1, counters);
34
+ }
35
+ return result;
36
+ }
37
+ throw decisionError(DECISION_ERROR_CODES.STATE_UNSAFE, "Decision state contains an unsupported value.");
38
+ }
39
+
40
+ export function buildDecisionState(value) {
41
+ const counters = { bytes: 0 };
42
+ const sanitized = sanitize(value, 0, counters);
43
+ const bytes = Buffer.byteLength(JSON.stringify(sanitized));
44
+ if (bytes > DECISION_LIMITS.maxStateBytes) throw decisionError(DECISION_ERROR_CODES.STATE_LIMIT, "Decision state exceeds the supported byte limit.");
45
+ return sanitized;
46
+ }
47
+
48
+ export function buildLifecycleDecisionState(value = {}) {
49
+ return buildDecisionState(value ?? {});
50
+ }
51
+
52
+ export function buildSemanticDecisionState(value = {}) {
53
+ return buildDecisionState(value ?? {});
54
+ }
55
+
56
+ export function buildCombinedDecisionState({ lifecycle = {}, semantic = {} } = {}) {
57
+ return {
58
+ lifecycle: buildLifecycleDecisionState(lifecycle),
59
+ semantic: buildSemanticDecisionState(semantic),
60
+ };
61
+ }
62
+
63
+ export function decisionStateFingerprint(value, canonicalFingerprint) {
64
+ return canonicalFingerprint(buildDecisionState(value));
65
+ }
@@ -0,0 +1,30 @@
1
+ import { canonicalFingerprint } from "../artifacts.js";
2
+ import { readContract } from "../contract.js";
3
+ import { readPersistedRoute } from "../route-artifact.js";
4
+ import { readWorkState } from "../work-state.js";
5
+
6
+ async function readOptionalArtifact(reader) {
7
+ try {
8
+ return await reader();
9
+ } catch (error) {
10
+ if (error.code === "ARTIFACT_MISSING") return null;
11
+ throw error;
12
+ }
13
+ }
14
+
15
+ export async function readCurrentDecisionBindings(target, packageRoot, taskId, { candidateSetFingerprint, policyFingerprint } = {}) {
16
+ const state = await readWorkState(target, { packageRoot, taskId });
17
+ const contract = await readOptionalArtifact(() => readContract(target, packageRoot, { taskId }));
18
+ const route = await readOptionalArtifact(() => readPersistedRoute(target, packageRoot, { taskId }));
19
+ return {
20
+ state,
21
+ ...(state ? { stateFingerprint: canonicalFingerprint(state) } : {}),
22
+ ...(state ? { taskStateFingerprint: canonicalFingerprint(state) } : {}),
23
+ ...(state?.repositoryFingerprint ? { repositoryFingerprint: state.repositoryFingerprint } : {}),
24
+ ...((contract?.fingerprint ?? state?.contractFingerprint) ? { contractFingerprint: contract?.fingerprint ?? state.contractFingerprint } : {}),
25
+ ...((route?.fingerprint ?? state?.routeFingerprint) ? { routeFingerprint: route?.fingerprint ?? state.routeFingerprint } : {}),
26
+ ...(state?.verificationCycle !== undefined ? { verificationCycle: state.verificationCycle } : {}),
27
+ ...(candidateSetFingerprint !== undefined ? { candidateSetFingerprint } : {}),
28
+ ...(policyFingerprint !== undefined ? { policyFingerprint } : {}),
29
+ };
30
+ }
@@ -0,0 +1,32 @@
1
+ // This provider is deliberately reachable only through repository test injection.
2
+ // Production commands always use the pinned TypeSafe Jev engine or fail closed.
3
+ export const testSemanticProvider = Object.freeze({
4
+ id: "typesafe-jev",
5
+ model: "jev-1.13.0",
6
+ async evaluate(request) {
7
+ const answers = Object.fromEntries(Object.entries(request.questionSet.questions).map(([name, question]) => [
8
+ name,
9
+ question.type === "choice" ? Object.keys(question.criteria)[0] : { noul: true },
10
+ ]));
11
+ return {
12
+ model: "jev-1.13.0",
13
+ answers,
14
+ confidence: Object.fromEntries(Object.keys(answers).map((name) => [name, 0.95])),
15
+ usage: { inputTokens: 0, outputTokens: 0 },
16
+ };
17
+ },
18
+ });
19
+
20
+ let activeTestSemanticProvider = null;
21
+
22
+ export function installTestSemanticProvider(provider = testSemanticProvider) {
23
+ activeTestSemanticProvider = provider;
24
+ }
25
+
26
+ export function clearTestSemanticProvider() {
27
+ activeTestSemanticProvider = null;
28
+ }
29
+
30
+ export function getTestSemanticProvider() {
31
+ return activeTestSemanticProvider;
32
+ }
@@ -0,0 +1,15 @@
1
+ export const DECISION_RISK_TIERS = Object.freeze(["LOW_RISK", "NORMAL", "HIGH_RISK", "CRITICAL"]);
2
+ export const DECISION_THRESHOLDS = Object.freeze({
3
+ LOW_RISK: 0.65,
4
+ NORMAL: 0.75,
5
+ HIGH_RISK: 0.9,
6
+ CRITICAL: 0.97,
7
+ });
8
+
9
+ export function decisionConfidenceThreshold(risk = "NORMAL") {
10
+ return DECISION_THRESHOLDS[risk] ?? DECISION_THRESHOLDS.NORMAL;
11
+ }
12
+
13
+ export function requiresEscalation(confidence, risk = "NORMAL") {
14
+ return typeof confidence !== "number" || confidence < decisionConfidenceThreshold(risk);
15
+ }