ai-foreman 1.7.20 → 1.7.21

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 (189) hide show
  1. package/README.md +12 -2
  2. package/dist/activity.d.ts +3 -0
  3. package/dist/activity.js +18 -6
  4. package/dist/adapters/claude.d.ts +16 -0
  5. package/dist/adapters/claude.js +28 -3
  6. package/dist/adapters/codex.d.ts +20 -0
  7. package/dist/adapters/codex.js +99 -5
  8. package/dist/adapters/recovering.d.ts +9 -0
  9. package/dist/adapters/recovering.js +10 -0
  10. package/dist/adapters/types.d.ts +15 -0
  11. package/dist/agentRun.d.ts +7 -0
  12. package/dist/agentRun.js +24 -13
  13. package/dist/branch/currentGuard.d.ts +1 -0
  14. package/dist/branch/currentGuard.js +1 -0
  15. package/dist/branch/finalization.js +6 -1
  16. package/dist/branch/git.d.ts +1 -0
  17. package/dist/branch/git.js +15 -1
  18. package/dist/branch/runner.d.ts +2 -0
  19. package/dist/branch/runner.js +90 -4
  20. package/dist/buildAdmission.js +2 -0
  21. package/dist/buildAssignment.d.ts +33 -0
  22. package/dist/buildAssignment.js +215 -0
  23. package/dist/buildInterventions.d.ts +90 -0
  24. package/dist/buildInterventions.js +334 -0
  25. package/dist/buildOwnershipReconciliation.d.ts +19 -0
  26. package/dist/buildOwnershipReconciliation.js +41 -0
  27. package/dist/buildOwnershipRepair.d.ts +33 -0
  28. package/dist/buildOwnershipRepair.js +111 -0
  29. package/dist/buildRuns.d.ts +6 -0
  30. package/dist/buildRuns.js +23 -17
  31. package/dist/buildSyntheticWork.d.ts +2 -0
  32. package/dist/buildSyntheticWork.js +30 -0
  33. package/dist/buildWorkAdmission.d.ts +17 -0
  34. package/dist/buildWorkAdmission.js +57 -0
  35. package/dist/buildWorkAuthorization.d.ts +2 -0
  36. package/dist/buildWorkAuthorization.js +23 -0
  37. package/dist/buildWorkContext.d.ts +3 -0
  38. package/dist/buildWorkContext.js +6 -0
  39. package/dist/buildWorkMigration.d.ts +9 -0
  40. package/dist/buildWorkMigration.js +206 -0
  41. package/dist/buildWorkReconciliation.d.ts +12 -0
  42. package/dist/buildWorkReconciliation.js +70 -0
  43. package/dist/builderGuidanceFollowup.d.ts +15 -0
  44. package/dist/builderGuidanceFollowup.js +34 -0
  45. package/dist/cli/doctor.js +3 -2
  46. package/dist/cli/graph.d.ts +2 -0
  47. package/dist/cli/graph.js +75 -0
  48. package/dist/cli/manager.d.ts +8 -0
  49. package/dist/cli/manager.js +234 -12
  50. package/dist/cli/recovery.js +3 -2
  51. package/dist/cli/runtimeAuthPrompt.js +28 -8
  52. package/dist/cli/start.js +152 -48
  53. package/dist/cli/status.js +20 -5
  54. package/dist/cli/tickets.js +9 -7
  55. package/dist/continuity.d.ts +11 -0
  56. package/dist/continuity.js +22 -6
  57. package/dist/foreman.d.ts +10 -2
  58. package/dist/foreman.js +330 -39
  59. package/dist/graph/boundary.d.ts +15 -0
  60. package/dist/graph/boundary.js +50 -0
  61. package/dist/graph/bridge.d.ts +9 -0
  62. package/dist/graph/bridge.js +146 -0
  63. package/dist/graph/bridge.py +205 -0
  64. package/dist/graph/config.d.ts +11 -0
  65. package/dist/graph/config.js +40 -0
  66. package/dist/graph/corpus.d.ts +16 -0
  67. package/dist/graph/corpus.js +188 -0
  68. package/dist/graph/derived.d.ts +17 -0
  69. package/dist/graph/derived.js +73 -0
  70. package/dist/graph/host.d.ts +4 -0
  71. package/dist/graph/host.js +160 -0
  72. package/dist/graph/install.d.ts +3 -0
  73. package/dist/graph/install.js +50 -0
  74. package/dist/graph/lifecycle.d.ts +11 -0
  75. package/dist/graph/lifecycle.js +124 -0
  76. package/dist/graph/maintenance.d.ts +43 -0
  77. package/dist/graph/maintenance.js +301 -0
  78. package/dist/graph/manager.d.ts +9 -0
  79. package/dist/graph/manager.js +92 -0
  80. package/dist/graph/preparation.d.ts +41 -0
  81. package/dist/graph/preparation.js +43 -0
  82. package/dist/graph/read.d.ts +27 -0
  83. package/dist/graph/read.js +152 -0
  84. package/dist/graph/semantic.d.ts +17 -0
  85. package/dist/graph/semantic.js +27 -0
  86. package/dist/graph/session.d.ts +8 -0
  87. package/dist/graph/session.js +92 -0
  88. package/dist/graph/sources.d.ts +4 -0
  89. package/dist/graph/sources.js +11 -0
  90. package/dist/graph/storage.d.ts +38 -0
  91. package/dist/graph/storage.js +159 -0
  92. package/dist/graph/turn.d.ts +17 -0
  93. package/dist/graph/turn.js +360 -0
  94. package/dist/graph/util.d.ts +5 -0
  95. package/dist/graph/util.js +51 -0
  96. package/dist/handoffs.d.ts +1 -0
  97. package/dist/handoffs.js +5 -1
  98. package/dist/humanDecision.d.ts +21 -2
  99. package/dist/humanDecision.js +169 -11
  100. package/dist/index.js +2 -0
  101. package/dist/installOwnership.d.ts +46 -0
  102. package/dist/installOwnership.js +440 -0
  103. package/dist/linuxProbeInventory.d.ts +12 -0
  104. package/dist/linuxProbeInventory.js +48 -0
  105. package/dist/log.d.ts +1 -1
  106. package/dist/log.js +7 -1
  107. package/dist/managerEvidence.d.ts +25 -0
  108. package/dist/managerEvidence.js +326 -0
  109. package/dist/managerEvidenceArtifacts.d.ts +38 -0
  110. package/dist/managerEvidenceArtifacts.js +118 -0
  111. package/dist/managerPacket.d.ts +2 -0
  112. package/dist/managerPacket.js +23 -0
  113. package/dist/markers.d.ts +1 -1
  114. package/dist/markers.js +2 -2
  115. package/dist/observability.js +1 -1
  116. package/dist/processIdentity.js +7 -0
  117. package/dist/providerPhase.d.ts +13 -0
  118. package/dist/providerPhase.js +26 -0
  119. package/dist/qaAuthorizedDisposition.d.ts +24 -0
  120. package/dist/qaAuthorizedDisposition.js +26 -0
  121. package/dist/qaBuildGate.d.ts +4 -0
  122. package/dist/qaBuildGate.js +216 -0
  123. package/dist/qaBuilderCoverage.d.ts +5 -0
  124. package/dist/qaBuilderCoverage.js +78 -0
  125. package/dist/qaContractCoverage.d.ts +10 -0
  126. package/dist/qaContractCoverage.js +55 -0
  127. package/dist/qaContractDelivery.d.ts +23 -0
  128. package/dist/qaContractDelivery.js +145 -0
  129. package/dist/qaContractFreshness.d.ts +15 -0
  130. package/dist/qaContractFreshness.js +36 -0
  131. package/dist/qaDeliveryJournal.d.ts +10 -0
  132. package/dist/qaDeliveryJournal.js +16 -1
  133. package/dist/qaEffectiveConfig.d.ts +20 -0
  134. package/dist/qaEffectiveConfig.js +40 -0
  135. package/dist/qaEvidenceReader.d.ts +14 -0
  136. package/dist/qaEvidenceReader.js +176 -0
  137. package/dist/qaFailureDelivery.d.ts +2 -0
  138. package/dist/qaFailureDelivery.js +107 -10
  139. package/dist/qaHandbackMigration.d.ts +1 -1
  140. package/dist/qaHandbackMigration.js +3 -3
  141. package/dist/qaPreparation.d.ts +26 -0
  142. package/dist/qaPreparation.js +150 -0
  143. package/dist/qaPreparationInputs.d.ts +8 -0
  144. package/dist/qaPreparationInputs.js +47 -0
  145. package/dist/qaPreparationMetrics.d.ts +145 -0
  146. package/dist/qaPreparationMetrics.js +120 -0
  147. package/dist/qaPreparationPolicy.d.ts +5 -0
  148. package/dist/qaPreparationPolicy.js +51 -0
  149. package/dist/qaPreparationStore.d.ts +157 -0
  150. package/dist/qaPreparationStore.js +412 -0
  151. package/dist/qaProtocolV2.d.ts +28 -0
  152. package/dist/qaProtocolV2.js +12 -0
  153. package/dist/qaReview.d.ts +6 -0
  154. package/dist/qaReview.js +234 -24
  155. package/dist/qaTimeline.d.ts +18 -0
  156. package/dist/qaTimeline.js +123 -0
  157. package/dist/qaVerificationContract.d.ts +17 -0
  158. package/dist/qaVerificationContract.js +236 -0
  159. package/dist/readinessCleanup.js +12 -5
  160. package/dist/recoveryGuidance.d.ts +9 -0
  161. package/dist/recoveryGuidance.js +13 -0
  162. package/dist/runtimeAuth.d.ts +1 -0
  163. package/dist/runtimeAuth.js +5 -1
  164. package/dist/runtimeReadiness.d.ts +1 -0
  165. package/dist/runtimeReadiness.js +13 -1
  166. package/dist/sessionIdentity.d.ts +2 -0
  167. package/dist/sessionIdentity.js +14 -0
  168. package/dist/sessionLifecycle.js +3 -3
  169. package/dist/sources/sourceRegistry.js +7 -0
  170. package/dist/stateTransfer.js +50 -1
  171. package/dist/statusReporter.d.ts +1 -0
  172. package/dist/statusReporter.js +1 -0
  173. package/dist/supervisedStart.js +18 -8
  174. package/dist/terminalStyle.d.ts +21 -0
  175. package/dist/terminalStyle.js +50 -0
  176. package/dist/ticketPopulation.d.ts +1 -0
  177. package/dist/ticketPopulation.js +5 -1
  178. package/dist/tickets/commands.js +2 -2
  179. package/dist/tickets/queue.js +1 -1
  180. package/dist/tickets/ticketSchema.d.ts +198 -0
  181. package/dist/tickets/ticketSchema.js +2 -0
  182. package/dist/workflowDb.d.ts +51 -5
  183. package/dist/workflowDb.js +338 -7
  184. package/dist/workflowReader.d.ts +109 -2
  185. package/dist/workflowReader.js +48 -4
  186. package/package.json +84 -7
  187. package/scripts/copy-graph-assets.mjs +3 -0
  188. package/scripts/graph-benchmark.mjs +20 -0
  189. package/scripts/graph-evaluation.mjs +85 -0
@@ -0,0 +1,236 @@
1
+ import { Ajv } from "ajv";
2
+ import { qaContractCandidateV1Schema, qaSemanticAssessmentV1Schema, qaChallengeReceiptV1Schema } from "rafi-spec";
3
+ const contractAjv = new Ajv({ allErrors: true });
4
+ const candidateSchema = contractAjv.compile(qaContractCandidateV1Schema);
5
+ const assessmentSchema = contractAjv.compile(qaSemanticAssessmentV1Schema);
6
+ const challengeSchema = contractAjv.compile(qaChallengeReceiptV1Schema);
7
+ import { createHash } from "node:crypto";
8
+ import { canonicalJson } from "./qaProtocolV2.js";
9
+ import { depthObligations, validateDepthDecision } from "./qaPreparationPolicy.js";
10
+ export function canonicalContractJson(value) {
11
+ const serialized = JSON.stringify(value);
12
+ if (serialized === undefined)
13
+ throw new Error("Contract artifacts must have a JSON representation");
14
+ return canonicalJson(JSON.parse(serialized));
15
+ }
16
+ export function contractDigest(domain, value) { return createHash("sha256").update(`rafi:${domain}:v1\n`).update(canonicalContractJson(value)).digest("hex"); }
17
+ export function candidatePayload(contract) {
18
+ const { contentDigest: _content, draftPayloadDigest: _draft, semanticAssessmentDigest: _semantic, challengeReceiptDigest: _challenge, ...payload } = contract;
19
+ return payload;
20
+ }
21
+ export function draftDigest(candidate) { return contractDigest("qa-contract-draft", candidatePayload(candidate)); }
22
+ /** Allocate identities from meaning, replacing proposed agent IDs throughout the draft. */
23
+ export function allocateStableCheckIds(candidate) {
24
+ const draft = structuredClone(candidate), mapping = new Map(), counts = new Map();
25
+ const proposed = new Map(draft.checks.map(check => [check.id, check]));
26
+ if (proposed.size !== draft.checks.length)
27
+ throw new Error("Duplicate proposed check identity");
28
+ const fingerprints = new Map(), active = new Set();
29
+ const fingerprintFor = (id) => {
30
+ const prior = fingerprints.get(id);
31
+ if (prior)
32
+ return prior;
33
+ const check = proposed.get(id);
34
+ if (!check)
35
+ throw new Error(`Unknown proposed dependency ${id}`);
36
+ if (active.has(id))
37
+ throw new Error(`Check dependency cycle ${id}`);
38
+ active.add(id);
39
+ const { id: _id, successorOf: _successor, dependsOnChecks, ...meaning } = check;
40
+ const fingerprint = contractDigest("check-identity", { ...meaning, requirementRefs: [...meaning.requirementRefs].sort(), prerequisiteRefs: [...meaning.prerequisiteRefs].sort(), dependencies: dependsOnChecks.map(fingerprintFor).sort() });
41
+ active.delete(id);
42
+ fingerprints.set(id, fingerprint);
43
+ return fingerprint;
44
+ };
45
+ for (const check of draft.checks) {
46
+ const fingerprint = fingerprintFor(check.id), occurrence = counts.get(fingerprint) ?? 0;
47
+ counts.set(fingerprint, occurrence + 1);
48
+ mapping.set(check.id, `check-${fingerprint.slice(0, 24)}-${occurrence}`);
49
+ }
50
+ const replace = (id) => { const replacement = mapping.get(id); if (!replacement)
51
+ throw new Error(`Unknown proposed check reference ${id}`); return replacement; };
52
+ for (const check of draft.checks) {
53
+ check.id = replace(check.id);
54
+ check.dependsOnChecks = check.dependsOnChecks.map(replace);
55
+ }
56
+ for (const row of draft.coverage)
57
+ row.checkIds = row.checkIds.map(replace);
58
+ for (const observation of draft.baseline)
59
+ if (observation.checkId)
60
+ observation.checkId = replace(observation.checkId);
61
+ for (const concern of draft.unresolved)
62
+ concern.checkIds = concern.checkIds.map(replace);
63
+ return draft;
64
+ }
65
+ export function inventoryDigest(requirements) { return contractDigest("qa-requirement-inventory", requirements); }
66
+ export function authoritativeInventory(ticket, checklist, inputs) {
67
+ const result = [];
68
+ for (const [kind, entries] of [["acceptance", ticket.acceptance], ["required_tests", ticket.required_tests], ["checklist", checklist]]) {
69
+ const input = inputs.find(ref => ref.kind === (kind === "checklist" ? "checklist" : "ticket"));
70
+ if (!input)
71
+ throw new Error(`Missing authoritative ${kind} input`);
72
+ const occurrences = new Map();
73
+ entries.forEach((statement, index) => {
74
+ const fingerprint = contractDigest("requirement-statement", { kind, statement });
75
+ const occurrence = occurrences.get(fingerprint) ?? 0;
76
+ occurrences.set(fingerprint, occurrence + 1);
77
+ result.push({ id: `req-${fingerprint.slice(0, 24)}-${occurrence}`, inputRef: input.id, locator: `${kind}[${index}]`, statement,
78
+ digest: contractDigest("statement", statement), obligation: "mandatory", origin: "explicit", authority: "approved" });
79
+ });
80
+ }
81
+ return result;
82
+ }
83
+ export function parsePreparationArtifact(text, tag, maximum = 2 * 1024 * 1024) {
84
+ if (Buffer.byteLength(text) > maximum)
85
+ throw new Error("QA preparation response exceeds size limit");
86
+ const start = `${tag}_START`, end = `${tag}_END`;
87
+ if (text.split(start).length !== 2 || text.split(end).length !== 2)
88
+ throw new Error(`Expected exactly one ${tag} artifact`);
89
+ const from = text.indexOf(start) + start.length, to = text.indexOf(end);
90
+ if (to < from)
91
+ throw new Error("Malformed preparation artifact envelope");
92
+ return JSON.parse(text.slice(from, to).trim());
93
+ }
94
+ export function validateCandidate(candidate, inventory, resolveEquivalent) {
95
+ if (!candidateSchema(candidatePayload(candidate)))
96
+ return (candidateSchema.errors ?? []).map(error => `${error.instancePath} ${error.message}`);
97
+ const issues = validateDepthDecision(candidate.depthDecision);
98
+ if (candidate.version !== 1 || !candidate.contractId || !candidate.runId || !candidate.workId || !candidate.admissionDigest || !Number.isSafeInteger(candidate.revision) || candidate.revision < 1)
99
+ issues.push("Invalid contract identity");
100
+ for (const field of ["inputs", "baseline", "requirements", "checks", "coverage", "preparationEvidence", "unresolved"])
101
+ if (!Array.isArray(candidate[field]))
102
+ issues.push(`Missing ${field} array`);
103
+ if (issues.length)
104
+ return issues;
105
+ const unique = (items, label) => {
106
+ const ids = new Set();
107
+ for (const item of items) {
108
+ if (!item.id || ids.has(item.id))
109
+ issues.push(`Duplicate/missing ${label} ID ${item.id}`);
110
+ ids.add(item.id);
111
+ }
112
+ return ids;
113
+ };
114
+ const inputs = unique(candidate.inputs, "input"), requirements = unique(candidate.requirements, "requirement"), checks = unique(candidate.checks, "check");
115
+ for (const expected of inventory)
116
+ if (!candidate.requirements.some(actual => canonicalJson(actual) === canonicalJson(expected)))
117
+ issues.push(`Missing/altered authoritative requirement ${expected.id}`);
118
+ for (const input of candidate.inputs)
119
+ if (input.availability !== "available" || !/^[a-f0-9]{64}$/.test(input.digest))
120
+ issues.push(`Unavailable/invalid input ${input.id}`);
121
+ for (const req of candidate.requirements) {
122
+ if (!inputs.has(req.inputRef) || !req.statement?.trim() || req.digest !== contractDigest("statement", req.statement))
123
+ issues.push(`Invalid source/statement ${req.id}`);
124
+ if (req.conflict)
125
+ issues.push(`Unresolved requirement conflict ${req.id}`);
126
+ if (req.authority === "proposed" && req.obligation === "mandatory")
127
+ issues.push(`Proposed scope cannot be mandatory ${req.id}`);
128
+ const rows = candidate.coverage.filter(row => row.requirementId === req.id);
129
+ if (rows.length !== 1 || !rows[0].checkIds.length || rows[0].dispositionRef)
130
+ issues.push(`Requirement lacks concrete coverage ${req.id}`);
131
+ for (const id of rows[0]?.checkIds ?? [])
132
+ if (!checks.has(id) || !candidate.checks.find(check => check.id === id)?.requirementRefs.includes(req.id))
133
+ issues.push(`Invalid coverage ${req.id}/${id}`);
134
+ if (req.obligation === "mandatory" && !(rows[0]?.checkIds ?? []).some(id => candidate.checks.find(check => check.id === id)?.obligation === "mandatory"))
135
+ issues.push(`Mandatory requirement only covered by advisory checks ${req.id}`);
136
+ }
137
+ for (const row of candidate.coverage)
138
+ if (!requirements.has(row.requirementId))
139
+ issues.push(`Unknown coverage requirement ${row.requirementId}`);
140
+ for (const check of candidate.checks) {
141
+ if (!check.requirementRefs?.length || check.requirementRefs.some(id => !requirements.has(id)))
142
+ issues.push(`Unknown requirement on ${check.id}`);
143
+ if (!check.expectedBehavior?.trim() || !check.expectedEvidence?.length || !check.verification?.length)
144
+ issues.push(`Nonactionable check ${check.id}`);
145
+ if (!["mandatory", "advisory"].includes(check.obligation) || !["preimplementation", "postimplementation", "both"].includes(check.timing))
146
+ issues.push(`Illegal obligation/timing ${check.id}`);
147
+ if (!check.applicability || !["unconditional", "conditional"].includes(check.applicability.kind) || !["host", "qa"].includes(check.applicability.decisionOwner))
148
+ issues.push(`Invalid applicability ${check.id}`);
149
+ else if (check.applicability.kind === "conditional" && (!check.applicability.predicate?.trim() || !check.applicability.requiredEvidence?.length))
150
+ issues.push(`Underspecified predicate ${check.id}`);
151
+ if (check.obligation === "mandatory" && check.timing !== "postimplementation") {
152
+ const readiness = candidate.baseline.find(observation => observation.checkId === check.id);
153
+ if (!readiness || readiness.applicability === "unresolved" || !readiness.applicability || check.applicability.kind === "unconditional" && readiness.applicability !== "applicable" || readiness.applicability === "applicable" && readiness.status !== "passed" || readiness.applicability === "not-applicable" && !readiness.predicateEvidence?.length)
154
+ issues.push(`Mandatory preimplementation readiness incomplete ${check.id}`);
155
+ if (check.applicability.kind === "conditional" && readiness?.applicability === "applicable" && !readiness.predicateEvidence?.length)
156
+ issues.push(`Preimplementation predicate evidence missing ${check.id}`);
157
+ }
158
+ if (check.dispositionRef)
159
+ issues.push(`Disposition requires resolved persisted authority ${check.id}`);
160
+ for (const method of check.verification ?? []) {
161
+ if (!method.expectedOutcome?.trim() || !method.runtime?.trim() || !Number.isSafeInteger(method.timeoutMs) || method.timeoutMs < 1 || method.timeoutMs > 3_600_000 || method.cwd.startsWith("/") || method.cwd.split(/[\\/]/).includes(".."))
162
+ issues.push(`Invalid method ${check.id}`);
163
+ if (method.kind === "command" ? !method.argv?.length || method.argv.some(arg => typeof arg !== "string") : method.kind !== "procedure" || !method.steps?.length)
164
+ issues.push(`Missing verification procedure ${check.id}`);
165
+ if (method.equivalentAuthorityId && !resolveEquivalent?.(method.equivalentAuthorityId, candidate, check, method))
166
+ issues.push(`Equivalent method requires persisted authority ${check.id}`);
167
+ }
168
+ if (!Array.isArray(check.dependsOnChecks) || !Array.isArray(check.prerequisiteRefs))
169
+ issues.push(`Missing dependencies ${check.id}`);
170
+ for (const id of check.dependsOnChecks ?? [])
171
+ if (!checks.has(id))
172
+ issues.push(`Unknown dependency ${id}`);
173
+ for (const id of check.prerequisiteRefs ?? [])
174
+ if (!candidate.baseline.some(observation => observation.id === id))
175
+ issues.push(`Unknown prerequisite ${id}`);
176
+ }
177
+ const active = new Set(), visited = new Set();
178
+ const visit = (id) => { if (active.has(id)) {
179
+ issues.push(`Check dependency cycle ${id}`);
180
+ return;
181
+ } if (visited.has(id))
182
+ return; active.add(id); for (const dep of candidate.checks.find(check => check.id === id)?.dependsOnChecks ?? [])
183
+ visit(dep); active.delete(id); visited.add(id); };
184
+ for (const id of checks)
185
+ visit(id);
186
+ for (const obligation of depthObligations(candidate.depthDecision.level))
187
+ if (!candidate.preparationEvidence.some(evidence => evidence.obligation === obligation && evidence.analysis?.trim() && evidence.evidenceDigest && evidence.operationId && evidence.sessionId && evidence.references.length))
188
+ issues.push(`Missing depth evidence ${obligation}`);
189
+ if (candidate.unresolved.some(concern => concern.material))
190
+ issues.push("Unresolved material concern");
191
+ return [...new Set(issues)];
192
+ }
193
+ export function validateAssessment(candidate, assessment, authorSessionId) {
194
+ const validator = "approach" in assessment ? challengeSchema : assessmentSchema;
195
+ if (!validator(assessment))
196
+ return (validator.errors ?? []).map(error => `${error.instancePath} ${error.message}`);
197
+ const issues = [];
198
+ if (assessment.version !== 1 || assessment.draftPayloadDigest !== draftDigest(candidate) || assessment.inventoryDigest !== inventoryDigest(candidate.requirements) || assessment.policyVersion !== candidate.depthDecision.policyVersion)
199
+ issues.push("Assessment does not bind the exact draft/inventory/policy");
200
+ if (!assessment.operationId || !assessment.sessionId || assessment.sessionId === authorSessionId || assessment.authorSessionId !== authorSessionId)
201
+ issues.push("Assessment lacks independent authorship");
202
+ for (const req of candidate.requirements)
203
+ if (!assessment.assessedRequirementIds?.includes(req.id))
204
+ issues.push(`Requirement not semantically assessed ${req.id}`);
205
+ for (const obligation of depthObligations(candidate.depthDecision.level))
206
+ if (!assessment.assessedObligations?.includes(obligation))
207
+ issues.push(`Obligation not assessed ${obligation}`);
208
+ if (!Array.isArray(assessment.concerns) || assessment.concerns.some(concern => concern.material))
209
+ issues.push("Semantic concerns unresolved");
210
+ return issues;
211
+ }
212
+ export function assembleContract(candidate, inventory, assessment, authorSessionId, challenge, resolveEquivalent) {
213
+ const issues = [...validateCandidate(candidate, inventory, resolveEquivalent), ...validateAssessment(candidate, assessment, authorSessionId)];
214
+ if (candidate.depthDecision.level === 5) {
215
+ if (!challenge)
216
+ issues.push("Exceptional requires independent challenge");
217
+ else {
218
+ issues.push(...validateAssessment(candidate, challenge, authorSessionId));
219
+ if (!challenge.approach?.trim() || !["scope", "invariants", "failure-boundaries", "verification"].every(category => challenge.concernCategories.includes(category)) || challenge.approachConcerns.some(concern => concern.material))
220
+ issues.push("Material approach challenge incomplete");
221
+ }
222
+ }
223
+ if (issues.length)
224
+ throw new Error(issues.join("; "));
225
+ const body = { ...candidatePayload(candidate), draftPayloadDigest: draftDigest(candidate), semanticAssessmentDigest: contractDigest("artifact-semantic-assessment", assessment), ...(challenge ? { challengeReceiptDigest: contractDigest("artifact-approach-challenge", challenge) } : {}) };
226
+ return { ...body, contentDigest: contractDigest("qa-contract-content", body) };
227
+ }
228
+ export function verifyContractDigest(contract) {
229
+ const { contentDigest, ...body } = contract;
230
+ if (contentDigest !== contractDigest("qa-contract-content", body) || contract.draftPayloadDigest !== draftDigest(contract))
231
+ throw new Error("Corrupt verification contract");
232
+ }
233
+ export function renderVerificationContract(contract) {
234
+ return [`Verification contract ${contract.contractId}, revision ${contract.revision}`, `Work: ${contract.workId}; digest: ${contract.contentDigest}`, `Depth: ${contract.depthDecision.level}: ${contract.depthDecision.rationale}`,
235
+ ...contract.checks.map(check => [`${check.id}: ${check.obligation}; ${check.timing}; ${check.applicability.kind}`, check.expectedBehavior, check.applicability.predicate ?? "Always applicable", `Evidence: ${check.expectedEvidence.join("; ")}`, ...check.verification.map(method => JSON.stringify(method))].join("\n"))].join("\n\n");
236
+ }
@@ -47,9 +47,13 @@ export function inspectReadiness(row, deadline = Date.now() + 5000) {
47
47
  return { state: "unknown", reason: "Windows containment unavailable" };
48
48
  if (metadata.protocol === "gated-v3" && identity.state !== "dead" && !processGroupQuiescent(row.pid, remaining()))
49
49
  return { state: "unknown", reason: "authorized helper can still execute" };
50
- return processGroupQuiescent(row.pid, remaining()) && taggedProcesses(row.id, remaining())?.length === 0
51
- ? { state: "quiescent", reason: "group and inherited tag inventory empty" }
52
- : { state: "unknown", reason: "owned group or tagged descendants active or unavailable" };
50
+ if (!processGroupQuiescent(row.pid, remaining()))
51
+ return { state: "unknown", reason: "owned process group remains active or group inventory is unavailable" };
52
+ const members = taggedProcesses(row.id, remaining());
53
+ if (!members)
54
+ return { state: "unknown", reason: "inherited probe tag inventory is unavailable" };
55
+ return members.length === 0 ? { state: "quiescent", reason: "group and inherited tag inventory empty" }
56
+ : { state: "unknown", reason: "tagged probe descendants remain active" };
53
57
  }
54
58
  const cleanupInFlight = new Map();
55
59
  /** Coalesce identical observations in this process; cross-process callers use DB CAS. */
@@ -77,12 +81,15 @@ async function cleanReadiness(row, deadline) {
77
81
  catch { /* capability is already fenced */ }
78
82
  }
79
83
  let attempt = 0;
84
+ let lastEvidence = { state: "unknown", reason: "cleanup inventory unavailable" };
80
85
  do {
81
86
  const evidence = inspectReadiness(row, deadline);
82
87
  if (evidence.state === "quiescent")
83
88
  return evidence;
89
+ if (evidence.reason !== "cleanup inventory deadline exceeded")
90
+ lastEvidence = evidence;
84
91
  if (Date.now() >= deadline)
85
- return evidence;
92
+ break;
86
93
  const signal = attempt++ < 3 ? "SIGTERM" : "SIGKILL";
87
94
  if (row.pid) {
88
95
  const identity = identityOf(row.pid, row.process_start ?? undefined, row.host);
@@ -111,5 +118,5 @@ async function cleanReadiness(row, deadline) {
111
118
  break;
112
119
  await new Promise(resolve => setTimeout(resolve, 50));
113
120
  } while (Date.now() < deadline);
114
- return inspectReadiness(row, deadline);
121
+ return { state: "unknown", reason: `${lastEvidence.reason}; cleanup verification deadline exceeded` };
115
122
  }
@@ -6,3 +6,12 @@ export declare function formatRecoveryCommand(projectDir: string, family?: Recov
6
6
  id: string;
7
7
  steps: number;
8
8
  }): string;
9
+ /** Fixed choices are executable; custom answers use the owning CLI's input flow. */
10
+ export declare function formatDecisionCommands(projectDir: string, runId: string, decision: {
11
+ decisionId: string;
12
+ choices: Array<{
13
+ id: string;
14
+ label: string;
15
+ }>;
16
+ }, family?: RecoveryCommandFamily): string[];
17
+ export declare function formatExactRunRecovery(projectDir: string, runId: string, family?: RecoveryCommandFamily): string;
@@ -23,3 +23,16 @@ export function formatRecoveryCommand(projectDir, family = commandFamily.getStor
23
23
  }
24
24
  return canonical(projectDir) === canonical(cwd) ? "rafi resume" : `rafi resume ${shellArgument(resolve(projectDir))}`;
25
25
  }
26
+ /** Fixed choices are executable; custom answers use the owning CLI's input flow. */
27
+ export function formatDecisionCommands(projectDir, runId, decision, family = commandFamily.getStore() ?? "rafi") {
28
+ return decision.choices.flatMap(choice => choice.id === "custom"
29
+ ? [family === "ai-foreman"
30
+ ? ` Custom response: use ${family} build:decide ${shellArgument(resolve(projectDir))} --run ${shellArgument(runId)} --decision ${shellArgument(decision.decisionId)} --choice custom with --answer followed by your shell-quoted response.`
31
+ : ` Custom response: run ${formatExactRunRecovery(projectDir, runId, family)} in an interactive terminal.`]
32
+ : [` ${choice.label}`, ` ${family} build:decide ${shellArgument(resolve(projectDir))} --run ${shellArgument(runId)} --decision ${shellArgument(decision.decisionId)} --choice ${shellArgument(choice.id)}`]);
33
+ }
34
+ export function formatExactRunRecovery(projectDir, runId, family = commandFamily.getStore() ?? "rafi") {
35
+ if (family === "ai-foreman")
36
+ return `ai-foreman manager ${shellArgument(resolve(projectDir))} --run ${shellArgument(runId)}`;
37
+ return `rafi resume ${shellArgument(resolve(projectDir))} --run ${shellArgument(runId)}`;
38
+ }
@@ -14,6 +14,7 @@ export declare class RuntimeAuthError extends Error {
14
14
  readonly stdout: string;
15
15
  readonly stderr: string;
16
16
  readonly authLikely: boolean;
17
+ readonly cleanupUnverified: boolean;
17
18
  constructor(opts: RuntimeAuthErrorOptions);
18
19
  }
19
20
  export declare function checkRuntimeReady(projectDir: string, runtime: AgentRuntime, options?: ProbeRuntimeOptions): Promise<import("rafi-spec").RuntimeProbeResult>;
@@ -1,10 +1,11 @@
1
- import { formatRuntimeProbeFailure, probeRuntime } from "./runtimeReadiness.js";
1
+ import { formatRuntimeProbeFailure, probeRuntime, RuntimeCleanupError, runtimeCleanupRecoveryHelp } from "./runtimeReadiness.js";
2
2
  export class RuntimeAuthError extends Error {
3
3
  runtime;
4
4
  exitCode;
5
5
  stdout;
6
6
  stderr;
7
7
  authLikely;
8
+ cleanupUnverified;
8
9
  constructor(opts) {
9
10
  super(formatRuntimeAuthFailure(opts), { cause: opts.cause });
10
11
  this.name = "RuntimeAuthError";
@@ -13,6 +14,7 @@ export class RuntimeAuthError extends Error {
13
14
  this.stdout = opts.stdout ?? "";
14
15
  this.stderr = opts.stderr ?? "";
15
16
  this.authLikely = isRuntimeAuthFailure(`${this.stderr}\n${this.stdout}`);
17
+ this.cleanupUnverified = opts.cause instanceof RuntimeCleanupError;
16
18
  }
17
19
  }
18
20
  export async function checkRuntimeReady(projectDir, runtime, options = {}) {
@@ -67,6 +69,8 @@ export function runtimeRepairCommands(runtime) {
67
69
  ].join("\n");
68
70
  }
69
71
  export function formatRuntimeAuthFailure(opts) {
72
+ if (opts.cause instanceof RuntimeCleanupError)
73
+ return `${opts.cause.message}\n\n${opts.context} is paused before further provider work. This is a process cleanup problem; changing login or provider will not clear it.\n\n${runtimeCleanupRecoveryHelp()}`;
70
74
  const output = [opts.stderr, opts.stdout].filter(Boolean).join("\n").trim();
71
75
  const exit = opts.exitCode === undefined || opts.exitCode === null ? "unknown" : String(opts.exitCode);
72
76
  const authLine = isRuntimeAuthFailure(output)
@@ -13,6 +13,7 @@ export interface BuildReadinessContext {
13
13
  export declare class RuntimeCleanupError extends Error {
14
14
  constructor(message: string);
15
15
  }
16
+ export declare function runtimeCleanupRecoveryHelp(): string;
16
17
  export interface ProbeRuntimeOptions {
17
18
  /** Omit only for standalone authentication/capability commands. Build callers use probeBuildRuntime. */
18
19
  build?: BuildReadinessContext;
@@ -20,6 +20,16 @@ export function cancelOwnedRuntimeProbes() { for (const cancel of ownedProbes)
20
20
  export class RuntimeCleanupError extends Error {
21
21
  constructor(message) { super(message); this.name = "RuntimeCleanupError"; }
22
22
  }
23
+ export function runtimeCleanupRecoveryHelp() {
24
+ return [
25
+ "Keep this Rafi process open while repairing visibility in another terminal, then choose retry.",
26
+ "Linux/WSL: check that /proc is mounted and readable by this user. Use a native Linux Node and provider installation in the same WSL distribution; inspect command -v node and command -v claude (or codex).",
27
+ "macOS: check that ps is available on PATH and process inspection is permitted by the terminal or host sandbox.",
28
+ "Windows: check that Windows PowerShell and process/Job Object inspection are available to the same user.",
29
+ "Retry first reconciles the original probe; no new provider starts until cleanup is verified. Avoid restarting Rafi to bypass this check or killing unrelated processes.",
30
+ "You may cancel deliberately; generated files remain available for a later create walkthrough.",
31
+ ].join("\n");
32
+ }
23
33
  /** Resolve the executable Node will launch without involving a shell. */
24
34
  export function resolveExecutablePath(command, env = process.env, platform = process.platform) {
25
35
  const hasPathSeparator = command.includes("/") || command.includes("\\");
@@ -260,6 +270,7 @@ async function probeRuntimeInternal(cwd, runtime, opts) {
260
270
  // Probe completion and process cleanup are independent. Clean on success too.
261
271
  clearTimeout(startupTimer);
262
272
  let cleaned = Boolean(spawnError && !child.pid);
273
+ let cleanupReason = "ownership reconciliation did not verify shutdown";
263
274
  if (authority && ownedProcess) {
264
275
  let journal;
265
276
  try {
@@ -280,6 +291,7 @@ async function probeRuntimeInternal(cwd, runtime, opts) {
280
291
  else if (child.pid) {
281
292
  const row = { id: tag, run_id: "standalone", owner: "standalone", pid: child.pid, process_start: childStart ?? null, host: (await import("node:os")).hostname(), state: "running", outcome_json: JSON.stringify({ protocol: windows ? "windows-job-v1" : "tagged-v2" }) };
282
293
  const evidence = await cleanupReadiness(row);
294
+ cleanupReason = evidence.reason;
283
295
  if (evidence.state !== "quiescent") {
284
296
  pendingStandaloneCleanup.set(tag, row);
285
297
  // The trusted holder exits on disconnect; retain the record until OS
@@ -316,7 +328,7 @@ async function probeRuntimeInternal(cwd, runtime, opts) {
316
328
  if (!cleaned) {
317
329
  rejectResult(new RuntimeCleanupError(authority
318
330
  ? "Readiness process cleanup is unverified; use rafi resume in the owning project to reconcile before continuing"
319
- : "Standalone probe cleanup is unverified; restore process visibility and retry. No new probe will start until cleanup is verified."));
331
+ : `Standalone probe cleanup is unverified (${cleanupReason}); restore process visibility and retry. No new probe will start until cleanup is verified.`));
320
332
  return;
321
333
  }
322
334
  spawnError ??= providerSpawnError;
@@ -23,6 +23,8 @@ export declare function canonicalSessionPath(path: string): string;
23
23
  * administrative Git directory (not in the checked-out tree), so normal edits
24
24
  * and commits do not change it while removal/recreation cannot inherit it.
25
25
  */
26
+ /** Existing identity only: graph reads must never initialize a worktree marker. */
27
+ export declare function readWorkspaceIdentity(cwd: string): string | undefined;
26
28
  export declare function captureWorkspaceIdentity(cwd: string): string | undefined;
27
29
  export declare function createProviderSessionRef(input: CreateProviderSessionRefInput): ProviderSessionRefV1;
28
30
  /** Identity used by accounting, compaction, settings acknowledgements, and mutation leases. */
@@ -17,6 +17,20 @@ export function canonicalSessionPath(path) {
17
17
  * administrative Git directory (not in the checked-out tree), so normal edits
18
18
  * and commits do not change it while removal/recreation cannot inherit it.
19
19
  */
20
+ /** Existing identity only: graph reads must never initialize a worktree marker. */
21
+ export function readWorkspaceIdentity(cwd) {
22
+ try {
23
+ const canonicalCwd = canonicalSessionPath(cwd);
24
+ const gitDir = canonicalSessionPath(execFileSync("git", ["rev-parse", "--absolute-git-dir"], { cwd: canonicalCwd, encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] }).trim());
25
+ const token = readFileSync(join(gitDir, "rafi-workspace-id-v1"), "utf8").trim();
26
+ if (!/^[0-9a-f-]{36}$/i.test(token))
27
+ return undefined;
28
+ return createHash("sha256").update(`${canonicalCwd}\0${gitDir}\0${token}`).digest("hex");
29
+ }
30
+ catch {
31
+ return undefined;
32
+ }
33
+ }
20
34
  export function captureWorkspaceIdentity(cwd) {
21
35
  try {
22
36
  const canonicalCwd = canonicalSessionPath(cwd);
@@ -79,7 +79,7 @@ export class RoleSessionController {
79
79
  }
80
80
  effectiveThreshold() {
81
81
  if (!this.thresholdController)
82
- return this.options.settings.auto_compact_threshold_percent ?? 50;
82
+ return this.options.settings.auto_compact_threshold_percent ?? 65;
83
83
  return this.thresholdController.effectiveThreshold();
84
84
  }
85
85
  async next(durableHandoff, nextSettings) {
@@ -781,11 +781,11 @@ export class ThresholdCompactionController {
781
781
  ? effective
782
782
  : undefined;
783
783
  }
784
- threshold() { return this.runOnlyThreshold ?? this.providerEffectiveThreshold ?? this.settings.auto_compact_threshold_percent ?? 50; }
784
+ threshold() { return this.runOnlyThreshold ?? this.providerEffectiveThreshold ?? this.settings.auto_compact_threshold_percent ?? 65; }
785
785
  now() { return this.options.now?.() ?? new Date(); }
786
786
  }
787
787
  function normalizeContextSettings(settings) {
788
- const threshold = settings.auto_compact_threshold_percent ?? 50;
788
+ const threshold = settings.auto_compact_threshold_percent ?? 65;
789
789
  const maximum = settings.compact_maximum ?? 10;
790
790
  if (!Number.isInteger(threshold) || threshold < 1 || threshold > 99)
791
791
  throw new Error("auto_compact_threshold_percent must be an integer from 1 to 99");
@@ -1,3 +1,4 @@
1
+ import { reconcileGraphSourceSelection } from "../graph/sources.js";
1
2
  import { execFile, execFileSync } from "node:child_process";
2
3
  import { promisify } from "node:util";
3
4
  import { createHash } from "node:crypto";
@@ -67,6 +68,12 @@ export function saveSourceRegistry(projectDir, registry) {
67
68
  writeFileSync(configPath, stringify(raw, { lineWidth: 100 }), "utf8");
68
69
  backfillUniqueTicketSourceIds(root, registry);
69
70
  ensurePrivateCacheIgnored(root);
71
+ try {
72
+ reconcileGraphSourceSelection(root);
73
+ }
74
+ catch (error) {
75
+ console.error(`rafi graph: registered-source selection unavailable: ${String(error)}. Existing graph evidence must not substitute a later capture.`);
76
+ }
70
77
  }
71
78
  export function addPendingSourceDescription(registry, description, now = new Date()) {
72
79
  const value = description.trim();
@@ -1,9 +1,11 @@
1
+ import { registerQaGraphJournalWriter } from "./qaDeliveryJournal.js";
2
+ import { normalizeTransferredGraphState } from "./graph/storage.js";
1
3
  import { WorkflowReader } from "./workflowReader.js";
2
4
  import { classifyProcess } from "./processIdentity.js";
3
5
  import Database from "better-sqlite3";
4
6
  import { registerHandbackWriter } from "./qaHandbackMigration.js";
5
7
  import { createHash, randomUUID } from "node:crypto";
6
- import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
8
+ import { copyFileSync, cpSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, } from "node:fs";
7
9
  import { tmpdir } from "node:os";
8
10
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
9
11
  import { spawnSync } from "node:child_process";
@@ -156,6 +158,29 @@ export async function importStateBundle(projectDir, bundleFile, opts = {}) {
156
158
  mkdirSync(dirname(out), { recursive: true });
157
159
  writeFileSync(out, Buffer.from(entry.data, "base64"));
158
160
  }
161
+ const workflowPath = join(staged, WORKFLOW_DB_FILE);
162
+ if (existsSync(workflowPath)) {
163
+ const preview = new Database(workflowPath, { readonly: true, fileMustExist: true });
164
+ try {
165
+ if (preview.pragma("integrity_check", { simple: true }) !== "ok" || preview.pragma("foreign_key_check").length)
166
+ throw new Error("Imported workflow database integrity failure");
167
+ if (Number(preview.pragma("user_version", { simple: true })) > 4)
168
+ throw new Error("Imported workflow requires a newer Rafi writer");
169
+ if (tableExists(preview, "build_work_scope"))
170
+ for (const { name } of preview.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'qa_%'").all()) {
171
+ if (!/^qa_[a-z_]+$/.test(name))
172
+ throw new Error("Unexpected imported QA table");
173
+ const columns = preview.prepare(`PRAGMA table_info(${name})`).all();
174
+ if (columns.some(column => column.name === "run_id") && columns.some(column => column.name === "ticket_id") && preview.prepare(`SELECT 1 FROM ${name} q WHERE NOT EXISTS(SELECT 1 FROM build_work_scope s WHERE s.run_id=q.run_id AND s.work_id=q.ticket_id) LIMIT 1`).get())
175
+ throw new Error(`Imported QA work is outside the ownership registry: ${name}`);
176
+ }
177
+ }
178
+ finally {
179
+ preview.close();
180
+ rmSync(workflowPath + "-shm", { force: true });
181
+ rmSync(workflowPath + "-wal", { force: true });
182
+ }
183
+ }
159
184
  const backup = backupExistingState(root, opts.now ?? new Date());
160
185
  backupDir = backup?.durableBackupDir;
161
186
  rollbackDir = backup?.snapshotDir;
@@ -296,6 +321,15 @@ async function stageSqliteBackup(root, rel, dest) {
296
321
  const db = new Database(source, { readonly: true, fileMustExist: true });
297
322
  try {
298
323
  await db.backup(dest);
324
+ if (rel === WORKFLOW_DB_FILE) {
325
+ const stagedDb = new Database(dest);
326
+ try {
327
+ normalizeTransferredGraphState(stagedDb);
328
+ }
329
+ finally {
330
+ stagedDb.close();
331
+ }
332
+ }
299
333
  }
300
334
  finally {
301
335
  db.close();
@@ -406,7 +440,22 @@ function rewriteWorkflowDb(root, sourceRoot) {
406
440
  if (!existsSync(path))
407
441
  return;
408
442
  const db = new Database(path);
443
+ normalizeTransferredGraphState(db);
409
444
  registerHandbackWriter(db);
445
+ registerQaGraphJournalWriter(db);
446
+ db.function("rafi_work_authority", () => 1);
447
+ if (tableExists(db, "build_project_identity")) {
448
+ const prior = db.prepare("SELECT project_id,canonical_root FROM build_project_identity WHERE singleton=1").get();
449
+ db.transaction(() => {
450
+ db.exec("CREATE TABLE IF NOT EXISTS build_project_rebindings(sequence INTEGER PRIMARY KEY AUTOINCREMENT,project_id TEXT NOT NULL,previous_root TEXT NOT NULL,new_root TEXT NOT NULL,rebound_at TEXT NOT NULL)");
451
+ for (const action of ["INSERT", "UPDATE", "DELETE"])
452
+ db.exec(`CREATE TRIGGER IF NOT EXISTS rebinding_protocol_${action} BEFORE ${action} ON build_project_rebindings BEGIN SELECT CASE WHEN rafi_writer_protocol()<>4 THEN RAISE(ABORT,'incompatible project rebinding writer') END; END`);
453
+ for (const action of ["UPDATE", "DELETE"])
454
+ db.exec(`CREATE TRIGGER IF NOT EXISTS rebinding_immutable_${action} BEFORE ${action} ON build_project_rebindings BEGIN SELECT RAISE(ABORT,'Project rebinding history is immutable'); END`);
455
+ db.prepare("INSERT INTO build_project_rebindings(project_id,previous_root,new_root,rebound_at) VALUES(?,?,?,?)").run(prior.project_id, prior.canonical_root, realpathSync(root), new Date().toISOString());
456
+ db.prepare("UPDATE build_project_identity SET canonical_root=? WHERE singleton=1").run(realpathSync(root));
457
+ })();
458
+ }
410
459
  // This connection only rewrites an offline imported snapshot. It cannot
411
460
  // dispatch; preserve imported ownership as unknown instead of adopting it.
412
461
  const importedOwner = tableExists(db, "build_admission") ? db.prepare("SELECT record_json FROM build_admission WHERE singleton=1").get() : undefined;
@@ -90,6 +90,7 @@ export declare class RoleStatusAdapter implements BuilderAdapter {
90
90
  restoreNativeCompactions(compactions: NativeCompaction[]): void;
91
91
  contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
92
92
  contextUsage(): Promise<ContextUsage | undefined>;
93
+ graphRuntimeSettings(): Pick<import("./adapters/types.js").BuilderAdapterOptions, "model" | "effort" | "fast" | "runtimeExecutable"> | undefined;
93
94
  sessionUsage(): Promise<ProviderSessionUsage | undefined>;
94
95
  switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
95
96
  events(): AsyncIterable<BuilderEvent>;
@@ -189,6 +189,7 @@ export class RoleStatusAdapter {
189
189
  restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
190
190
  contextUsageAfterNativeCompaction(compaction) { return this.adapter.contextUsageAfterNativeCompaction?.(compaction) ?? Promise.resolve(undefined); }
191
191
  contextUsage() { return this.adapter.contextUsage?.() ?? Promise.resolve(undefined); }
192
+ graphRuntimeSettings() { return this.adapter.graphRuntimeSettings?.(); }
192
193
  sessionUsage() { return this.adapter.sessionUsage?.() ?? Promise.resolve(undefined); }
193
194
  switchSettings(settings) { return this.adapter.switchSettings?.(settings) ?? Promise.resolve({ ok: false, error: "settings switch unavailable" }); }
194
195
  events() { return this.adapter.events(); }