ai-foreman 1.7.29 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/adapters/claude.d.ts +23 -12
- package/dist/adapters/claude.js +231 -26
- package/dist/adapters/codex.d.ts +19 -7
- package/dist/adapters/codex.js +202 -19
- package/dist/adapters/recovering.d.ts +2 -0
- package/dist/adapters/recovering.js +5 -1
- package/dist/adapters/structured.d.ts +71 -0
- package/dist/adapters/structured.js +213 -0
- package/dist/adapters/types.d.ts +85 -9
- package/dist/agentRun.d.ts +2 -0
- package/dist/agentRun.js +2 -1
- package/dist/boundedTurnEvidence.d.ts +18 -0
- package/dist/boundedTurnEvidence.js +76 -0
- package/dist/branch/currentGuard.d.ts +2 -0
- package/dist/branch/currentGuard.js +2 -0
- package/dist/branch/git.js +3 -8
- package/dist/branch/planner.js +1 -4
- package/dist/branch/runner.d.ts +2 -1
- package/dist/branch/runner.js +10 -7
- package/dist/buildAdmission.js +4 -0
- package/dist/buildApproval.d.ts +15 -0
- package/dist/buildApproval.js +28 -9
- package/dist/buildAssignment.d.ts +1 -0
- package/dist/buildAssignment.js +82 -23
- package/dist/buildInterventions.d.ts +2 -2
- package/dist/buildInterventions.js +11 -4
- package/dist/buildLaunchGate.js +3 -2
- package/dist/buildRuns.d.ts +1 -0
- package/dist/buildRuns.js +6 -1
- package/dist/builderGuidanceFollowup.js +17 -6
- package/dist/cli/doctor.js +3 -1
- package/dist/cli/manager.js +22 -20
- package/dist/cli/start.js +85 -39
- package/dist/cli/tickets.d.ts +0 -8
- package/dist/cli/tickets.js +50 -60
- package/dist/continuity.d.ts +10 -16
- package/dist/continuity.js +223 -208
- package/dist/continuityOrigin.d.ts +13 -0
- package/dist/continuityOrigin.js +91 -0
- package/dist/dispatchNotSent.d.ts +4 -0
- package/dist/dispatchNotSent.js +10 -0
- package/dist/foreman.d.ts +15 -16
- package/dist/foreman.js +70 -278
- package/dist/gitExclude.d.ts +16 -0
- package/dist/gitExclude.js +46 -0
- package/dist/graph/boundary.d.ts +1 -1
- package/dist/graph/boundary.js +1 -1
- package/dist/graph/host.js +8 -19
- package/dist/graph/manager.d.ts +1 -1
- package/dist/graph/manager.js +1 -1
- package/dist/graph/preparation.d.ts +38 -0
- package/dist/graph/preparation.js +7 -6
- package/dist/graph/semantic.d.ts +1 -0
- package/dist/graph/semantic.js +4 -1
- package/dist/graph/session.js +7 -2
- package/dist/graph/turn.d.ts +3 -4
- package/dist/graph/turn.js +29 -41
- package/dist/handoffProtocol.js +2 -2
- package/dist/handoffs.d.ts +5 -3
- package/dist/handoffs.js +92 -88
- package/dist/hostGraphApplicability.d.ts +2 -1
- package/dist/hostGraphApplicability.js +17 -19
- package/dist/hostQaAugmentation.js +21 -4
- package/dist/hostQaComparison.d.ts +21 -2
- package/dist/hostQaComparison.js +43 -29
- package/dist/hostSourcePreservation.d.ts +38 -0
- package/dist/hostSourcePreservation.js +72 -0
- package/dist/humanDecision.d.ts +7 -0
- package/dist/humanDecision.js +11 -0
- package/dist/interviews.d.ts +7 -0
- package/dist/interviews.js +18 -1
- package/dist/legacyCommunicationDisplay.d.ts +2 -0
- package/dist/legacyCommunicationDisplay.js +2 -0
- package/dist/managerPacket.js +3 -3
- package/dist/markers.d.ts +4 -3
- package/dist/markers.js +4 -20
- package/dist/observability.js +3 -8
- package/dist/probeIpc.d.ts +14 -0
- package/dist/probeIpc.js +29 -0
- package/dist/qaBuildGate.d.ts +21 -0
- package/dist/qaBuildGate.js +60 -13
- package/dist/qaBuilderCoverage.js +22 -13
- package/dist/qaBuilderEvidence.d.ts +16 -1
- package/dist/qaBuilderEvidence.js +48 -8
- package/dist/qaContractCoverage.d.ts +2 -2
- package/dist/qaContractCoverage.js +4 -9
- package/dist/qaContractDelivery.js +34 -20
- package/dist/qaCoverageAssembly.d.ts +22 -0
- package/dist/qaCoverageAssembly.js +16 -0
- package/dist/qaDeliveryJournal.d.ts +2 -0
- package/dist/qaDeliveryJournal.js +19 -0
- package/dist/qaFailureDelivery.js +97 -85
- package/dist/qaHostTransformation.d.ts +26 -0
- package/dist/qaHostTransformation.js +100 -0
- package/dist/qaPreparation.d.ts +8 -20
- package/dist/qaPreparation.js +153 -254
- package/dist/qaPreparationInputs.js +19 -0
- package/dist/qaPreparationStore.d.ts +11 -0
- package/dist/qaPreparationStore.js +44 -4
- package/dist/qaPrerequisites.d.ts +2 -1
- package/dist/qaPrerequisites.js +21 -1
- package/dist/qaProtocolV2.d.ts +15 -0
- package/dist/qaRecovery.d.ts +0 -3
- package/dist/qaRecovery.js +21 -27
- package/dist/qaReview.js +241 -406
- package/dist/qaReviewEvidence.d.ts +7 -3
- package/dist/qaReviewEvidence.js +15 -4
- package/dist/qaRuntime.js +1 -1
- package/dist/qaSnapshot.js +76 -21
- package/dist/qaStructuredResponse.d.ts +18 -0
- package/dist/qaStructuredResponse.js +81 -0
- package/dist/qaTimeline.js +2 -2
- package/dist/qaVerificationContract.d.ts +1 -2
- package/dist/qaVerificationContract.js +24 -35
- package/dist/readinessGate.js +5 -4
- package/dist/recoveryOwners.d.ts +32 -0
- package/dist/recoveryOwners.js +87 -0
- package/dist/runtimePreflight.d.ts +11 -0
- package/dist/runtimePreflight.js +77 -0
- package/dist/runtimeReadiness.js +23 -15
- package/dist/sources/sourceRegistry.d.ts +0 -3
- package/dist/sources/sourceRegistry.js +0 -42
- package/dist/statusReporter.d.ts +2 -0
- package/dist/statusReporter.js +2 -0
- package/dist/structuredCutover.d.ts +31 -0
- package/dist/structuredCutover.js +510 -0
- package/dist/structuredCutoverFiles.d.ts +10 -0
- package/dist/structuredCutoverFiles.js +181 -0
- package/dist/structuredDispatchAuthority.d.ts +22 -0
- package/dist/structuredDispatchAuthority.js +110 -0
- package/dist/structuredDispatchRecord.d.ts +29 -0
- package/dist/structuredDispatchRecord.js +36 -0
- package/dist/structuredLiveBudget.d.ts +2 -0
- package/dist/structuredLiveBudget.js +23 -0
- package/dist/structuredOperation.d.ts +18 -0
- package/dist/structuredOperation.js +279 -0
- package/dist/supervisedStart.js +35 -21
- package/dist/ticketPlanning.d.ts +1 -376
- package/dist/ticketPlanning.js +2 -49
- package/dist/ticketPopulation.d.ts +0 -3
- package/dist/ticketPopulation.js +4 -21
- package/dist/ticketPopulationPageAuthority.d.ts +13 -0
- package/dist/ticketPopulationPageAuthority.js +30 -0
- package/dist/ticketPopulationPaging.d.ts +27 -0
- package/dist/ticketPopulationPaging.js +127 -0
- package/dist/tickets/renderMarkdown.d.ts +14 -0
- package/dist/tickets/renderMarkdown.js +39 -5
- package/dist/tickets/ticketLoader.js +10 -0
- package/dist/tickets/ticketSchema.d.ts +107 -0
- package/dist/tickets/ticketSchema.js +4 -1
- package/dist/workflowDb.d.ts +116 -3
- package/dist/workflowDb.js +852 -61
- package/dist/workflowReader.d.ts +1 -0
- package/dist/workflowReader.js +9 -0
- package/package.json +23 -7
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { canonicalJson, structuredDigest, ticketPopulationPageOutput } from "rafi-spec";
|
|
3
|
+
import { WorkflowDb } from "./workflowDb.js";
|
|
4
|
+
import { operationPayload } from "./structuredOperation.js";
|
|
5
|
+
import { assertTicketPopulationPageBinding, withTicketPopulationPage } from "./ticketPopulationPageAuthority.js";
|
|
6
|
+
/** Each provider page is sealed and durably appended before aggregate interpretation. */
|
|
7
|
+
export async function streamTicketPopulation(input) {
|
|
8
|
+
const runId = `ticket-population:${input.interviewId}`;
|
|
9
|
+
const db = new WorkflowDb(input.projectDir);
|
|
10
|
+
try {
|
|
11
|
+
db.enableStructuredCommunication();
|
|
12
|
+
const artifactId = restartableArtifactId(db, runId, `${runId}:artifact:${input.correction}`);
|
|
13
|
+
const prior = db.operation(artifactId);
|
|
14
|
+
if (prior) {
|
|
15
|
+
const retained = prior.intent;
|
|
16
|
+
const header = db.populationPageLedger(artifactId);
|
|
17
|
+
if (retained.instructionDigest !== structuredDigest(input.instruction) || retained.projectDir !== input.projectDir ||
|
|
18
|
+
retained.runId !== runId || !header || header.state !== "terminal")
|
|
19
|
+
throw new Error("Ticket population artifact already exists; reconcile retained pages before a fresh lineage");
|
|
20
|
+
const pages = db.populationPageRecords(artifactId);
|
|
21
|
+
const tickets = [], retirements = [];
|
|
22
|
+
let last;
|
|
23
|
+
let identity;
|
|
24
|
+
for (const [ordinal, content] of pages.entries()) {
|
|
25
|
+
const stored = JSON.parse(content);
|
|
26
|
+
last = db.retainedStructuredTurn(stored.dispatchId);
|
|
27
|
+
assertTicketPopulationPageBinding(db, retained, last.selectedDispatch);
|
|
28
|
+
const page = operationPayload(last, ticketPopulationPageOutput);
|
|
29
|
+
if (last.selectedDispatch?.digest !== stored.selectedDispatchDigest ||
|
|
30
|
+
canonicalJson({ ...page, selectedDispatchDigest: stored.selectedDispatchDigest, dispatchId: stored.dispatchId }) !== content ||
|
|
31
|
+
page.terminal !== (ordinal === pages.length - 1) || identity && (identity.plan_id !== page.plan_id || identity.revision !== page.revision))
|
|
32
|
+
throw new Error("Retained ticket page failed native replay");
|
|
33
|
+
identity = { plan_id: page.plan_id, revision: page.revision };
|
|
34
|
+
tickets.push(...page.tickets);
|
|
35
|
+
retirements.push(...page.retirements);
|
|
36
|
+
}
|
|
37
|
+
if (!last || !identity)
|
|
38
|
+
throw new Error("Terminal ticket population has no native pages");
|
|
39
|
+
const proposal = { version: 1, ...identity, tickets, retirements };
|
|
40
|
+
if (Buffer.byteLength(canonicalJson(proposal)) > 6 * 1024 * 1024 || new Set(tickets.map(ticket => ticket.slice_ref)).size !== tickets.length)
|
|
41
|
+
throw new Error("Retained ticket population aggregate is invalid");
|
|
42
|
+
if (prior.status !== "confirmed")
|
|
43
|
+
db.updateOperation(artifactId, "confirmed", { result: { pageCount: pages.length, recovered: true } });
|
|
44
|
+
return { proposal, turn: { result: last, status: { kind: "done" } } };
|
|
45
|
+
}
|
|
46
|
+
const scope = { projectDir: input.projectDir, artifactId, runId,
|
|
47
|
+
lineageId: `${artifactId}:${randomUUID()}`, leaseId: randomUUID() };
|
|
48
|
+
db.ensureRun(runId);
|
|
49
|
+
db.planOperation({ runId, idempotencyKey: artifactId, kind: "ticket-population-artifact", intent: { ...scope, instructionDigest: structuredDigest(input.instruction) } });
|
|
50
|
+
db.updateOperation(artifactId, "in_progress");
|
|
51
|
+
input.checkpoint(scope); // Durable interview owner exists before provider contact.
|
|
52
|
+
const tickets = [], retirements = [];
|
|
53
|
+
let identity;
|
|
54
|
+
let predecessorDigest = null;
|
|
55
|
+
let last;
|
|
56
|
+
try {
|
|
57
|
+
for (let ordinal = 0; ordinal < 60; ordinal++) {
|
|
58
|
+
last = await withTicketPopulationPage(scope, () => input.dispatch(`${input.instruction}\n\n` +
|
|
59
|
+
`Return page ${ordinal + 1} of at most 60 using the native ticket-population-page contract. ` +
|
|
60
|
+
`At most 150 tickets and 750 KiB canonical bytes per page; all pages together must fit 6 MiB. ` +
|
|
61
|
+
`Do not repeat prior tickets or retirements. terminal must be true only when the complete proposal has been supplied. ` +
|
|
62
|
+
`Keep plan_id and revision identical across pages. Previous page digest: ${predecessorDigest ?? "none"}. ` +
|
|
63
|
+
`Already retained ticket slice references: ${JSON.stringify(tickets.map(ticket => ticket.slice_ref))}.`, { output: ticketPopulationPageOutput, journalRunId: runId }));
|
|
64
|
+
if (last.result.isError || last.result.failure)
|
|
65
|
+
throw new Error(last.status.reason ?? last.status.error ?? "Ticket population page was rejected");
|
|
66
|
+
const page = operationPayload(last.result, ticketPopulationPageOutput);
|
|
67
|
+
const tuple = last.result.selectedDispatch;
|
|
68
|
+
if (!tuple)
|
|
69
|
+
throw new Error("Ticket page has no native tuple");
|
|
70
|
+
assertTicketPopulationPageBinding(db, scope, tuple);
|
|
71
|
+
db.replayStructuredDispatch(tuple.dispatchId, ticketPopulationPageOutput);
|
|
72
|
+
const header = db.populationPageLedger(artifactId);
|
|
73
|
+
if (!header)
|
|
74
|
+
throw new Error("Ticket page was sent before its artifact was sealed");
|
|
75
|
+
const content = canonicalJson({ ...page, selectedDispatchDigest: tuple.digest, dispatchId: tuple.dispatchId });
|
|
76
|
+
const appended = db.appendPageLedger({ ...scope, tupleDigest: header.tupleDigest, ordinal,
|
|
77
|
+
predecessorDigest, content, terminal: page.terminal });
|
|
78
|
+
// Domain interpretation begins only after the immutable page is durable.
|
|
79
|
+
if (identity && (identity.plan_id !== page.plan_id || identity.revision !== page.revision))
|
|
80
|
+
throw new Error("Ticket page changed plan identity");
|
|
81
|
+
identity = { plan_id: page.plan_id, revision: page.revision };
|
|
82
|
+
tickets.push(...page.tickets);
|
|
83
|
+
retirements.push(...page.retirements);
|
|
84
|
+
predecessorDigest = appended.digest;
|
|
85
|
+
if (!page.terminal)
|
|
86
|
+
continue;
|
|
87
|
+
const proposal = { version: 1, ...identity, tickets, retirements };
|
|
88
|
+
if (Buffer.byteLength(canonicalJson(proposal)) > 6 * 1024 * 1024)
|
|
89
|
+
throw new Error("Ticket population aggregate exceeds 6 MiB");
|
|
90
|
+
if (new Set(tickets.map(ticket => ticket.slice_ref)).size !== tickets.length)
|
|
91
|
+
throw new Error("Ticket pages contain duplicate slices");
|
|
92
|
+
db.updateOperation(artifactId, "confirmed", { result: { pageCount: ordinal + 1, terminalDigest: appended.digest } });
|
|
93
|
+
return { proposal, turn: last };
|
|
94
|
+
}
|
|
95
|
+
throw new Error("Ticket population did not terminate within 60 pages");
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
db.updateOperation(artifactId, "uncertain", { error: String(error).slice(0, 4000) });
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
db.close();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* An earlier population attempt that never reached its terminal page (a bad
|
|
108
|
+
* answer, a provider crash or a stalled turn, or Rafi itself stopping) is
|
|
109
|
+
* retired with its unanswered page calls, and population restarts from its
|
|
110
|
+
* first page under a new lineage with the same instruction. A terminal
|
|
111
|
+
* lineage is kept so its retained pages can be replayed.
|
|
112
|
+
*/
|
|
113
|
+
function restartableArtifactId(db, runId, baseId) {
|
|
114
|
+
for (let restart = 0;; restart++) {
|
|
115
|
+
const artifactId = restart ? `${baseId}:restart-${restart}` : baseId;
|
|
116
|
+
const prior = db.operation(artifactId);
|
|
117
|
+
if (!prior || db.populationPageLedger(artifactId)?.state === "terminal")
|
|
118
|
+
return artifactId;
|
|
119
|
+
if (prior.status === "failed")
|
|
120
|
+
continue;
|
|
121
|
+
db.atomic(() => {
|
|
122
|
+
for (const dispatch of db.operations(runId).filter(op => op.kind === "provider-dispatch" && ["in_progress", "uncertain"].includes(op.status)))
|
|
123
|
+
db.updateOperation(dispatch.idempotencyKey, "failed", { result: { recovery: "population-restart", outcomeUnknown: true, providerReplayAuthorized: false, priorResult: dispatch.result }, error: "Unfinished ticket population page; population restarts from its first page" });
|
|
124
|
+
db.updateOperation(artifactId, "failed", { result: { recovery: "population-restart", priorResult: prior.result }, error: "Unfinished ticket population lineage retired; population restarts from its first page" });
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -2,6 +2,20 @@ import type { TicketsConfig } from "./config.js";
|
|
|
2
2
|
import type { TicketDef } from "./ticketSchema.js";
|
|
3
3
|
import type { StateDb, TicketState } from "./stateDb.js";
|
|
4
4
|
export declare function renderTrackerRules(progressDoc?: string): string;
|
|
5
|
+
export type TrackerRulesMigration = {
|
|
6
|
+
disposition: "replaced-legacy" | "updated-managed-block";
|
|
7
|
+
content: string;
|
|
8
|
+
} | {
|
|
9
|
+
disposition: "unchanged";
|
|
10
|
+
content: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Upgrade only Rafi text that can be identified exactly. Historical whole-file
|
|
14
|
+
* rules are replaced only when unmodified; otherwise just an exact historical
|
|
15
|
+
* native-result section can be upgraded. User additions and altered text stay
|
|
16
|
+
* byte-for-byte intact.
|
|
17
|
+
*/
|
|
18
|
+
export declare function migrateTrackerRules(content: string, progressDoc?: string): TrackerRulesMigration;
|
|
5
19
|
export declare const DEFAULT_TRACKER_RULES: string;
|
|
6
20
|
export interface RenderInput {
|
|
7
21
|
config: TicketsConfig;
|
|
@@ -18,7 +18,17 @@ function section(marker, content) {
|
|
|
18
18
|
return `<!-- ${marker}_START -->\n${content}\n<!-- ${marker}_END -->`;
|
|
19
19
|
}
|
|
20
20
|
// ── Default tracker-rules content ─────────────────────────────────────────────
|
|
21
|
-
|
|
21
|
+
const RAFI_TRACKER_RULES_START = "<!-- rafi:tracker-rules:start -->";
|
|
22
|
+
const RAFI_TRACKER_RULES_END = "<!-- rafi:tracker-rules:end -->";
|
|
23
|
+
const LEGACY_NATIVE_RESULT_PROTOCOL = `## Native result protocol
|
|
24
|
+
When Rafi is running this project, submit the host-selected structured JSON result.
|
|
25
|
+
Use its declared summary, evidence, coverage, and continuity fields; do not append separate status markers.
|
|
26
|
+
Rafi binds the assigned ticket identity and controls completion after independent QA.
|
|
27
|
+
Use \`foreman tickets discover\` for newly discovered work during a build run.`;
|
|
28
|
+
function managedNativeResultProtocol() {
|
|
29
|
+
return `${RAFI_TRACKER_RULES_START}\n${LEGACY_NATIVE_RESULT_PROTOCOL}\n${RAFI_TRACKER_RULES_END}`;
|
|
30
|
+
}
|
|
31
|
+
function renderLegacyTrackerRules(progressDoc) {
|
|
22
32
|
return `# Tracker Rules
|
|
23
33
|
|
|
24
34
|
## Purpose
|
|
@@ -50,12 +60,36 @@ Use \`foreman tickets discover --summary "..." --rationale "..."\`.
|
|
|
50
60
|
Do not manually edit generated sections of \`${progressDoc}\`.
|
|
51
61
|
Use the \`foreman tickets\` commands instead.
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
When Foreman is running this project, include the ticket ID in your done marker:
|
|
55
|
-
STEP_STATUS: done | ticket="T001" summary="implemented the feature"
|
|
56
|
-
Use \`foreman tickets discover\` for newly discovered work during a build run.
|
|
63
|
+
${LEGACY_NATIVE_RESULT_PROTOCOL}
|
|
57
64
|
`;
|
|
58
65
|
}
|
|
66
|
+
export function renderTrackerRules(progressDoc = "docs/ticket-progress.md") {
|
|
67
|
+
return renderLegacyTrackerRules(progressDoc).replace(LEGACY_NATIVE_RESULT_PROTOCOL, managedNativeResultProtocol());
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Upgrade only Rafi text that can be identified exactly. Historical whole-file
|
|
71
|
+
* rules are replaced only when unmodified; otherwise just an exact historical
|
|
72
|
+
* native-result section can be upgraded. User additions and altered text stay
|
|
73
|
+
* byte-for-byte intact.
|
|
74
|
+
*/
|
|
75
|
+
export function migrateTrackerRules(content, progressDoc = "docs/ticket-progress.md") {
|
|
76
|
+
const current = renderTrackerRules(progressDoc);
|
|
77
|
+
if (content.trim() === renderLegacyTrackerRules(progressDoc).trim()) {
|
|
78
|
+
return { disposition: "replaced-legacy", content: current };
|
|
79
|
+
}
|
|
80
|
+
const managed = new RegExp(`${escapeRegExp(RAFI_TRACKER_RULES_START)}[\\s\\S]*?${escapeRegExp(RAFI_TRACKER_RULES_END)}`);
|
|
81
|
+
if (managed.test(content)) {
|
|
82
|
+
const next = content.replace(managed, managedNativeResultProtocol());
|
|
83
|
+
return next === content ? { disposition: "unchanged", content } : { disposition: "updated-managed-block", content: next };
|
|
84
|
+
}
|
|
85
|
+
if (content.includes(LEGACY_NATIVE_RESULT_PROTOCOL)) {
|
|
86
|
+
return { disposition: "updated-managed-block", content: content.replace(LEGACY_NATIVE_RESULT_PROTOCOL, managedNativeResultProtocol()) };
|
|
87
|
+
}
|
|
88
|
+
return { disposition: "unchanged", content };
|
|
89
|
+
}
|
|
90
|
+
function escapeRegExp(value) {
|
|
91
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
92
|
+
}
|
|
59
93
|
export const DEFAULT_TRACKER_RULES = renderTrackerRules();
|
|
60
94
|
export function renderProgressDoc(input) {
|
|
61
95
|
const { config, projectDir, ticketDefs, states, db } = input;
|
|
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { parse, stringify } from "yaml";
|
|
4
4
|
import { Ajv } from "ajv";
|
|
5
5
|
import { TICKET_JSON_SCHEMA } from "./ticketSchema.js";
|
|
6
|
+
import { verificationRequirementV2Schema } from "rafi-spec";
|
|
6
7
|
const ajv = new Ajv({ allErrors: true });
|
|
7
8
|
const validateTicketSchema = ajv.compile(TICKET_JSON_SCHEMA);
|
|
8
9
|
export function loadTickets(ticketsPath) {
|
|
@@ -38,6 +39,15 @@ export function validateTicketDefs(tickets) {
|
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
41
|
}
|
|
42
|
+
const hasV2 = ticket.verification_version !== undefined || ticket.verification_requirements !== undefined;
|
|
43
|
+
if (hasV2 && (ticket.verification_version !== 2 || !Array.isArray(ticket.verification_requirements) || ticket.verification_requirements.length === 0)) {
|
|
44
|
+
errors.push({ path: `tickets[${ticket.id ?? "?"}].verification_requirements`, message: "V2 verification requires version 2 and at least one typed requirement" });
|
|
45
|
+
}
|
|
46
|
+
for (const [index, requirement] of (ticket.verification_requirements ?? []).entries()) {
|
|
47
|
+
const parsed = verificationRequirementV2Schema.safeParse(requirement);
|
|
48
|
+
if (!parsed.success)
|
|
49
|
+
errors.push({ path: `tickets[${ticket.id ?? "?"}].verification_requirements[${index}]`, message: parsed.error.issues.map(issue => `${issue.path.join(".")}: ${issue.message}`).join("; ") });
|
|
50
|
+
}
|
|
41
51
|
}
|
|
42
52
|
const ids = new Set();
|
|
43
53
|
for (const t of tickets) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DocumentationPolicyV1, type QaPreparationDepthDecisionV1 } from "rafi-spec";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
export type TicketPriority = "P0" | "P1" | "P2" | "P3";
|
|
3
4
|
export type TicketSize = "XS" | "S" | "M" | "L" | "XL";
|
|
4
5
|
export type TicketRisk = "Low" | "Medium" | "High";
|
|
@@ -36,6 +37,8 @@ export interface TicketDef {
|
|
|
36
37
|
summary: string;
|
|
37
38
|
acceptance: string[];
|
|
38
39
|
required_tests: string[];
|
|
40
|
+
verification_version?: 2;
|
|
41
|
+
verification_requirements?: import("rafi-spec").VerificationRequirementV2[];
|
|
39
42
|
likely_files: string[];
|
|
40
43
|
rollback?: string | null;
|
|
41
44
|
notes?: string | null;
|
|
@@ -206,6 +209,58 @@ export declare const TICKET_JSON_SCHEMA: {
|
|
|
206
209
|
};
|
|
207
210
|
readonly minItems: 1;
|
|
208
211
|
};
|
|
212
|
+
readonly verification_version: {
|
|
213
|
+
readonly const: 2;
|
|
214
|
+
};
|
|
215
|
+
readonly verification_requirements: {
|
|
216
|
+
readonly type: "array";
|
|
217
|
+
readonly minItems: 1;
|
|
218
|
+
readonly items: z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
|
|
219
|
+
id: z.ZodString;
|
|
220
|
+
summary: z.ZodString;
|
|
221
|
+
timing: z.ZodEnum<{
|
|
222
|
+
both: "both";
|
|
223
|
+
preimplementation: "preimplementation";
|
|
224
|
+
postimplementation: "postimplementation";
|
|
225
|
+
}>;
|
|
226
|
+
applicability: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
227
|
+
kind: z.ZodLiteral<"always">;
|
|
228
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
229
|
+
kind: z.ZodLiteral<"host-executable-available">;
|
|
230
|
+
executable: z.ZodEnum<{
|
|
231
|
+
node: "node";
|
|
232
|
+
pnpm: "pnpm";
|
|
233
|
+
npm: "npm";
|
|
234
|
+
yarn: "yarn";
|
|
235
|
+
bun: "bun";
|
|
236
|
+
python3: "python3";
|
|
237
|
+
pytest: "pytest";
|
|
238
|
+
cargo: "cargo";
|
|
239
|
+
go: "go";
|
|
240
|
+
}>;
|
|
241
|
+
expected: z.ZodBoolean;
|
|
242
|
+
phase: z.ZodEnum<{
|
|
243
|
+
both: "both";
|
|
244
|
+
preimplementation: "preimplementation";
|
|
245
|
+
postimplementation: "postimplementation";
|
|
246
|
+
}>;
|
|
247
|
+
}, z.core.$strict>], "kind">;
|
|
248
|
+
methods: z.ZodArray<z.ZodObject<{
|
|
249
|
+
kind: z.ZodEnum<{
|
|
250
|
+
command: "command";
|
|
251
|
+
procedure: "procedure";
|
|
252
|
+
}>;
|
|
253
|
+
argv: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
254
|
+
steps: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
255
|
+
cwd: z.ZodString;
|
|
256
|
+
runtime: z.ZodString;
|
|
257
|
+
timeout_ms: z.ZodNumber;
|
|
258
|
+
expected_outcome: z.ZodString;
|
|
259
|
+
}, z.core.$strict>>;
|
|
260
|
+
expected_evidence: z.ZodArray<z.ZodString>;
|
|
261
|
+
depends_on: z.ZodArray<z.ZodString>;
|
|
262
|
+
}, z.core.$strict>>;
|
|
263
|
+
};
|
|
209
264
|
readonly likely_files: {
|
|
210
265
|
readonly type: "array";
|
|
211
266
|
readonly items: {
|
|
@@ -475,6 +530,58 @@ export declare const TICKETS_FILE_SCHEMA: {
|
|
|
475
530
|
};
|
|
476
531
|
readonly minItems: 1;
|
|
477
532
|
};
|
|
533
|
+
readonly verification_version: {
|
|
534
|
+
readonly const: 2;
|
|
535
|
+
};
|
|
536
|
+
readonly verification_requirements: {
|
|
537
|
+
readonly type: "array";
|
|
538
|
+
readonly minItems: 1;
|
|
539
|
+
readonly items: z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
|
|
540
|
+
id: z.ZodString;
|
|
541
|
+
summary: z.ZodString;
|
|
542
|
+
timing: z.ZodEnum<{
|
|
543
|
+
both: "both";
|
|
544
|
+
preimplementation: "preimplementation";
|
|
545
|
+
postimplementation: "postimplementation";
|
|
546
|
+
}>;
|
|
547
|
+
applicability: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
548
|
+
kind: z.ZodLiteral<"always">;
|
|
549
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
550
|
+
kind: z.ZodLiteral<"host-executable-available">;
|
|
551
|
+
executable: z.ZodEnum<{
|
|
552
|
+
node: "node";
|
|
553
|
+
pnpm: "pnpm";
|
|
554
|
+
npm: "npm";
|
|
555
|
+
yarn: "yarn";
|
|
556
|
+
bun: "bun";
|
|
557
|
+
python3: "python3";
|
|
558
|
+
pytest: "pytest";
|
|
559
|
+
cargo: "cargo";
|
|
560
|
+
go: "go";
|
|
561
|
+
}>;
|
|
562
|
+
expected: z.ZodBoolean;
|
|
563
|
+
phase: z.ZodEnum<{
|
|
564
|
+
both: "both";
|
|
565
|
+
preimplementation: "preimplementation";
|
|
566
|
+
postimplementation: "postimplementation";
|
|
567
|
+
}>;
|
|
568
|
+
}, z.core.$strict>], "kind">;
|
|
569
|
+
methods: z.ZodArray<z.ZodObject<{
|
|
570
|
+
kind: z.ZodEnum<{
|
|
571
|
+
command: "command";
|
|
572
|
+
procedure: "procedure";
|
|
573
|
+
}>;
|
|
574
|
+
argv: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
575
|
+
steps: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
576
|
+
cwd: z.ZodString;
|
|
577
|
+
runtime: z.ZodString;
|
|
578
|
+
timeout_ms: z.ZodNumber;
|
|
579
|
+
expected_outcome: z.ZodString;
|
|
580
|
+
}, z.core.$strict>>;
|
|
581
|
+
expected_evidence: z.ZodArray<z.ZodString>;
|
|
582
|
+
depends_on: z.ZodArray<z.ZodString>;
|
|
583
|
+
}, z.core.$strict>>;
|
|
584
|
+
};
|
|
478
585
|
readonly likely_files: {
|
|
479
586
|
readonly type: "array";
|
|
480
587
|
readonly items: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { documentationPolicyV1Schema, qaPreparationDepthDecisionV1Schema } from "rafi-spec";
|
|
1
|
+
import { documentationPolicyV1Schema, qaPreparationDepthDecisionV1Schema, verificationRequirementV2Schema } from "rafi-spec";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
export const TICKET_JSON_SCHEMA = {
|
|
3
4
|
type: "object",
|
|
4
5
|
required: [
|
|
@@ -20,6 +21,8 @@ export const TICKET_JSON_SCHEMA = {
|
|
|
20
21
|
summary: { type: "string", minLength: 1 },
|
|
21
22
|
acceptance: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
22
23
|
required_tests: { type: "array", items: { type: "string" }, minItems: 1 },
|
|
24
|
+
verification_version: { const: 2 },
|
|
25
|
+
verification_requirements: { type: "array", minItems: 1, items: z.toJSONSchema(verificationRequirementV2Schema, { target: "draft-7", io: "input" }) },
|
|
23
26
|
likely_files: { type: "array", items: { type: "string" } },
|
|
24
27
|
rollback: { type: ["string", "null"] },
|
|
25
28
|
notes: { type: ["string", "null"] },
|
package/dist/workflowDb.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type CheckpointOrigin } from "./continuityOrigin.js";
|
|
2
|
+
import { type SealedDispatchRecordV2 } from "./structuredDispatchRecord.js";
|
|
1
3
|
import { type GraphDerivedAccess } from "./graph/derived.js";
|
|
2
4
|
import { QaPreparationStore } from "./qaPreparationStore.js";
|
|
3
5
|
import { GraphStore } from "./graph/storage.js";
|
|
@@ -11,7 +13,10 @@ import type { QaDeliveryTurn, QaDeliveryOutcome, QaDeliveryInvocationV3 } from "
|
|
|
11
13
|
import type { BranchResumeSession } from "./branch/resume.js";
|
|
12
14
|
import { type BuilderRemediationReceiptV2, type BuilderRemediationReceiptV3, type FrozenQaSourceStateV2, type HandoffAcceptanceReceiptV2, type QaPassCertificateV2, type QaReducerEventV2, type QaReducerStateV2, type QaReportDisposition, type QaReviewBasisV2, type QaTurnIntentV2, type QaTurnReceiptV2 } from "./qaProtocolV2.js";
|
|
13
15
|
export declare const WORKFLOW_DB_FILE = ".rafi/recovery.sqlite3";
|
|
14
|
-
export type WorkflowKind = "plan" | "ticket-plan" | "ticket-populate" | "uninstall" | "build" | "qa-remediation" | "recovery" | "legacy";
|
|
16
|
+
export type WorkflowKind = "plan" | "ticket-plan" | "ticket-populate" | "uninstall" | "build" | "qa-remediation" | "recovery" | "legacy" | "operation";
|
|
17
|
+
/** Journal runs for agent calls outside a build use these run-id prefixes. */
|
|
18
|
+
export declare const NON_BUILD_RUN_PREFIXES: readonly ["operation:", "ticket-population:", "qa-"];
|
|
19
|
+
export declare function isNonBuildRunId(runId: string): boolean;
|
|
15
20
|
export type WorkflowRunStatus = "running" | "paused" | "blocked" | "completed" | "failed" | "cancelled" | "superseded";
|
|
16
21
|
export interface WorkflowRunSnapshot {
|
|
17
22
|
runId: string;
|
|
@@ -84,6 +89,10 @@ export interface RoleMutationLease {
|
|
|
84
89
|
sessionKey?: string;
|
|
85
90
|
movedAt: string;
|
|
86
91
|
}
|
|
92
|
+
export interface FreshRecoveryValidation {
|
|
93
|
+
decisionDigest: string;
|
|
94
|
+
predecessor: RoleMutationLease;
|
|
95
|
+
}
|
|
87
96
|
export interface QaRecoveryHeadRecord {
|
|
88
97
|
runId: string;
|
|
89
98
|
ticketId: string;
|
|
@@ -235,8 +244,8 @@ export declare class WorkflowDb {
|
|
|
235
244
|
text: string;
|
|
236
245
|
ids: string[];
|
|
237
246
|
};
|
|
238
|
-
verifyBuilderGuidance(ids: string[], certificate: Parameters<typeof verifyBuilderGuidance>[2], response: string, receiptDigest: string): void;
|
|
239
|
-
verifyGuidance(ids: string[], certificate: Parameters<typeof verifyGuidance>[2], response: string): void;
|
|
247
|
+
verifyBuilderGuidance(ids: string[], certificate: Parameters<typeof verifyBuilderGuidance>[2], response: string | readonly string[], receiptDigest: string): void;
|
|
248
|
+
verifyGuidance(ids: string[], certificate: Parameters<typeof verifyGuidance>[2], response: string | readonly string[]): void;
|
|
240
249
|
hasQueuedBuilderGuidance(runId: string, workId: string): boolean;
|
|
241
250
|
instruction(id: string): import("./buildInterventions.js").BuildInstruction | undefined;
|
|
242
251
|
assertFinalizationControls(runId: string, workId: string): void;
|
|
@@ -368,6 +377,7 @@ export declare class WorkflowDb {
|
|
|
368
377
|
sessionKey?: string;
|
|
369
378
|
}, now?: Date): ContinuityEvent;
|
|
370
379
|
continuityEvents(runId: string, afterSequence?: number): ContinuityEvent[];
|
|
380
|
+
providerCheckpoint(dispatchId: string, publicationKind: string): ContinuityCheckpoint | undefined;
|
|
371
381
|
publishContinuityCheckpoint(input: {
|
|
372
382
|
runId: string;
|
|
373
383
|
role: "builder" | "qa";
|
|
@@ -376,6 +386,9 @@ export declare class WorkflowDb {
|
|
|
376
386
|
authoritativeStateRevision: number;
|
|
377
387
|
sessionRef?: ProviderSessionRefV1;
|
|
378
388
|
sessionKey?: string;
|
|
389
|
+
origin?: CheckpointOrigin;
|
|
390
|
+
hostEventKind?: "role-baseline" | "operator-recovery" | "source-reconciliation";
|
|
391
|
+
inputCheckpointDigests?: string[];
|
|
379
392
|
}, now?: Date): ContinuityCheckpoint;
|
|
380
393
|
continuityCheckpoints(runId: string, role?: "builder" | "qa"): ContinuityCheckpoint[];
|
|
381
394
|
latestContinuityCheckpoint(runId: string, role: "builder" | "qa"): ContinuityCheckpoint | undefined;
|
|
@@ -409,6 +422,8 @@ export declare class WorkflowDb {
|
|
|
409
422
|
humanDecisionKey(decisionId: string): string | undefined;
|
|
410
423
|
humanDecision(decisionId: string): PendingHumanDecision | undefined;
|
|
411
424
|
pendingHumanDecisions(runId: string): PendingHumanDecision[];
|
|
425
|
+
/** Answered decisions for a run, oldest first (e.g. answered with build:decide while no worker waited). */
|
|
426
|
+
answeredHumanDecisions(runId: string): PendingHumanDecision[];
|
|
412
427
|
decisionContinuationAvailable(runId: string, decisionId: string): boolean;
|
|
413
428
|
nextDecisionContinuationKey(runId: string, decisionId: string): string;
|
|
414
429
|
answeredTicketDecisions(runId: string, scopeRevision: string): PendingHumanDecision[];
|
|
@@ -428,6 +443,41 @@ export declare class WorkflowDb {
|
|
|
428
443
|
kind: string;
|
|
429
444
|
intent: unknown;
|
|
430
445
|
}, now?: Date): OperationRecord;
|
|
446
|
+
/** Seal a previously persisted non-authoritative preparation intent. This
|
|
447
|
+
* is intentionally legal only before provider content dispatch begins. */
|
|
448
|
+
sealPlannedOperationIntent(idempotencyKey: string, intent: unknown, now?: Date): OperationRecord;
|
|
449
|
+
/** Called in the gateway's sealing transaction, before the send capability exists. */
|
|
450
|
+
sealStructuredOwnerDispatch(key: string, tuple: import("rafi-spec").SelectedDispatchTupleV2, renderedInstruction: string): void;
|
|
451
|
+
structuredOwnerDispatch(key: string): SealedDispatchRecordV2;
|
|
452
|
+
assertStructuredDispatchAdmission(runId: string, dispatchId: string, parentId: string | undefined, operation: string): void;
|
|
453
|
+
beginStructuredHandoff(predecessorId: string): void;
|
|
454
|
+
linkStructuredHandoffDispatch(dispatchId: string, parentId: string | undefined, operation: string): void;
|
|
455
|
+
planStructuredContinuation(predecessorId: string, successor: ProviderSessionRefV1): string;
|
|
456
|
+
structuredContinuation(runId: string, role: "builder" | "qa"): {
|
|
457
|
+
predecessorId: string;
|
|
458
|
+
continuationId: string;
|
|
459
|
+
state: string;
|
|
460
|
+
successor: ProviderSessionRefV1;
|
|
461
|
+
} | undefined;
|
|
462
|
+
finishStructuredContinuation(dispatchId: string): void;
|
|
463
|
+
private assertAcceptedStructuredSuccessor;
|
|
464
|
+
requireStructuredPostprocessing(dispatchId: string, context: unknown): void;
|
|
465
|
+
structuredProcessingContext(dispatchId: string): {
|
|
466
|
+
instruction: string;
|
|
467
|
+
policy: import("./adapters/types.js").TurnRequest;
|
|
468
|
+
};
|
|
469
|
+
failStructuredPostprocessing(dispatchId: string, error: string): void;
|
|
470
|
+
completeStructuredPostprocessing(dispatchId: string): void;
|
|
471
|
+
pendingStructuredPostprocessing(runId: string): string[];
|
|
472
|
+
retainedStructuredTurn(dispatchId: string): import("./adapters/types.js").TurnResult;
|
|
473
|
+
/** A claim is never recycled, even if transport fails before writing bytes. */
|
|
474
|
+
claimStructuredSubmission(tuple: import("rafi-spec").SelectedDispatchTupleV2, session?: ProviderSessionRefV1): string;
|
|
475
|
+
consumeStructuredSubmission(tuple: import("rafi-spec").SelectedDispatchTupleV2, claimId: string, session?: ProviderSessionRefV1): void;
|
|
476
|
+
assertNativeRunAdmission(runId: string): void;
|
|
477
|
+
recordCommunicationEvidenceRefs(ownerId: string, digests: string[]): void;
|
|
478
|
+
replayStructuredDispatch<T>(key: string, contract: import("rafi-spec").OutputContract<T>): T;
|
|
479
|
+
private assertStructuredProviderTurn;
|
|
480
|
+
private verifyRetainedStructuredDispatch;
|
|
431
481
|
updateOperation(idempotencyKey: string, status: OperationLifecycle, details?: {
|
|
432
482
|
result?: unknown;
|
|
433
483
|
externalId?: string;
|
|
@@ -438,6 +488,38 @@ export declare class WorkflowDb {
|
|
|
438
488
|
operation(idempotencyKey: string): OperationRecord | undefined;
|
|
439
489
|
operations(runId: string): OperationRecord[];
|
|
440
490
|
putEvidence(kind: "handoff" | "diff" | "test" | "qa", value: string | Buffer, now?: Date): string;
|
|
491
|
+
/** Create an append-only provider page artifact before its first dispatch. */
|
|
492
|
+
populationPageRecords(artifactId: string): string[];
|
|
493
|
+
populationPageLedger(artifactId: string): {
|
|
494
|
+
tupleDigest: string;
|
|
495
|
+
state: string;
|
|
496
|
+
pageCount: number;
|
|
497
|
+
} | undefined;
|
|
498
|
+
beginPageLedger(input: {
|
|
499
|
+
artifactId: string;
|
|
500
|
+
runId: string;
|
|
501
|
+
lineageId: string;
|
|
502
|
+
tupleDigest: string;
|
|
503
|
+
leaseId: string;
|
|
504
|
+
}, now?: Date): void;
|
|
505
|
+
/**
|
|
506
|
+
* Atomically appends a page. Duplicate delivery is accepted only when every
|
|
507
|
+
* immutable byte and linkage field agrees; a provider cannot fill gaps,
|
|
508
|
+
* rewrite history, or append after the terminal page.
|
|
509
|
+
*/
|
|
510
|
+
appendPageLedger(input: {
|
|
511
|
+
artifactId: string;
|
|
512
|
+
lineageId: string;
|
|
513
|
+
tupleDigest: string;
|
|
514
|
+
leaseId: string;
|
|
515
|
+
ordinal: number;
|
|
516
|
+
predecessorDigest: string | null;
|
|
517
|
+
content: string;
|
|
518
|
+
terminal: boolean;
|
|
519
|
+
}, now?: Date): {
|
|
520
|
+
digest: string;
|
|
521
|
+
duplicate: boolean;
|
|
522
|
+
};
|
|
441
523
|
registerGraphEvidence(value: string | Buffer, access: GraphDerivedAccess | GraphDerivedAccess[]): void;
|
|
442
524
|
getEvidence(digest: string): Buffer | undefined;
|
|
443
525
|
putQaRecoveryHead(input: Omit<QaRecoveryHeadRecord, "updatedAt">, now?: Date): QaRecoveryHeadRecord;
|
|
@@ -504,7 +586,23 @@ export declare class WorkflowDb {
|
|
|
504
586
|
}, expectedRevision: number, now?: Date): QaReducerStateV2;
|
|
505
587
|
recordQaHandoffReceipt(receipt: HandoffAcceptanceReceiptV2): void;
|
|
506
588
|
qaHandoffReceipt(operationId: string): HandoffAcceptanceReceiptV2 | undefined;
|
|
589
|
+
/**
|
|
590
|
+
* Structured authority is admitted only after the one-way v2 cutover has
|
|
591
|
+
* committed. Keeping this at the common admission boundary prevents a
|
|
592
|
+
* caller that opens WorkflowDb directly from bypassing the migration.
|
|
593
|
+
*/
|
|
594
|
+
enableStructuredCommunication(): void;
|
|
507
595
|
beginQaTurn(intent: QaTurnIntentV2): void;
|
|
596
|
+
/** Retire this run's unresolved dispatches on a provider session the host
|
|
597
|
+
* has just closed after a failed call. The closed provider can no longer
|
|
598
|
+
* act, so the call's outcome is final: unknown, never replayed. */
|
|
599
|
+
retireClosedSessionDispatches(runId: string, sessionId: string | undefined, reason: string): string[];
|
|
600
|
+
/** Retire interrupted role turns that are not implementation or bound QA
|
|
601
|
+
* review work (for example build preflight, coverage, or handoff
|
|
602
|
+
* acceptance) so the step restarts from its beginning. Implementation and
|
|
603
|
+
* review turns keep their own recovery. The interrupted outcome stays unknown and is
|
|
604
|
+
* never replayed or treated as a result. Returns the retired dispatch IDs. */
|
|
605
|
+
reconcileInterruptedSteps(runId: string, role: "builder" | "qa"): string[];
|
|
508
606
|
/** Retire an interrupted, proven read-only review transport, never a QA pass
|
|
509
607
|
* or Builder remediation. A fresh immutable review must still run afterward. */
|
|
510
608
|
reconcileInterruptedQaDispatch(runId: string, ticketId: string): boolean;
|
|
@@ -535,8 +633,10 @@ export declare class WorkflowDb {
|
|
|
535
633
|
authorizeContractQaWaiver(runId: string, workId: string, decisionId: string): string;
|
|
536
634
|
commitQaWaiver(runId: string, ticketId: string, expectedRevision: number, reason: string, now?: Date, authorizationRef?: string): QaReducerStateV2;
|
|
537
635
|
builderRemediationReceipt(operationId: string): BuilderRemediationReceiptV2 | BuilderRemediationReceiptV3 | undefined;
|
|
636
|
+
verifiedBuilderRemediationReceipt(operationId: string): BuilderRemediationReceiptV3 | undefined;
|
|
538
637
|
hasConfirmedQaRemediationForRecheck(runId: string, ticketId: string): boolean;
|
|
539
638
|
recordBuilderRemediationReceipt(receipt: BuilderRemediationReceiptV2 | BuilderRemediationReceiptV3): void;
|
|
639
|
+
private assertBuilderRemediationReceipt;
|
|
540
640
|
commitBuilderRemediationSuccess(receipt: BuilderRemediationReceiptV2 | BuilderRemediationReceiptV3, expectedRevision: number, now?: Date): QaReducerStateV2;
|
|
541
641
|
commitQaRemediationOutcome(input: {
|
|
542
642
|
recoveryAttemptId: string;
|
|
@@ -548,6 +648,7 @@ export declare class WorkflowDb {
|
|
|
548
648
|
receipt?: BuilderRemediationReceiptV2 | BuilderRemediationReceiptV3;
|
|
549
649
|
expectedRevision: number;
|
|
550
650
|
}, now?: Date): QaReducerStateV2;
|
|
651
|
+
private assertNativeQaCertificate;
|
|
551
652
|
private assertContractCertificate;
|
|
552
653
|
issueQaPassCertificate(input: Omit<QaPassCertificateV2, "version" | "certificateId" | "unresolvedReportCount" | "issuedAt">, now?: Date): QaPassCertificateV2;
|
|
553
654
|
commitQaPass(input: Omit<QaPassCertificateV2, "version" | "certificateId" | "unresolvedReportCount" | "issuedAt">, expectedRevision: number, now?: Date): QaPassCertificateV2;
|
|
@@ -574,6 +675,7 @@ export declare class WorkflowDb {
|
|
|
574
675
|
expectedRevision: number;
|
|
575
676
|
operationId: string;
|
|
576
677
|
}, now?: Date): QaReducerStateV2;
|
|
678
|
+
assertQaFinalizationAuthority(runId: string, ticketId: string): void;
|
|
577
679
|
planQaFinalizationStep(input: {
|
|
578
680
|
operationId: string;
|
|
579
681
|
runId: string;
|
|
@@ -636,6 +738,9 @@ export declare class WorkflowDb {
|
|
|
636
738
|
compactionAttempt(idempotencyKey: string): CompactionAttemptRecord | undefined;
|
|
637
739
|
unresolvedCompactions(runId: string, role: "builder" | "qa", sessionKey: string): CompactionAttemptRecord[];
|
|
638
740
|
successfulCompactionCount(runId: string, role: "builder" | "qa", providerSession?: string | ProviderSessionRefV1): number;
|
|
741
|
+
freshRecoveryValidation(runId: string, role: "builder" | "qa"): FreshRecoveryValidation;
|
|
742
|
+
/** This grants one response-only initialization, never ownership or business work. */
|
|
743
|
+
assertFreshRecoveryValidation(dispatchId: string, session: ProviderSessionRefV1): void;
|
|
639
744
|
recordRecoveryDecision(receipt: BuildRecoveryDecisionReceipt): void;
|
|
640
745
|
recoveryDecisions(runId: string): BuildRecoveryDecisionReceipt[];
|
|
641
746
|
/**
|
|
@@ -663,5 +768,13 @@ export declare class WorkflowDb {
|
|
|
663
768
|
private refreshRunContinuityHead;
|
|
664
769
|
private insertEvent;
|
|
665
770
|
private migrate;
|
|
771
|
+
/**
|
|
772
|
+
* One-way cutover fence. We deliberately do not attempt to convert an old
|
|
773
|
+
* receipt into authority: any pre-existing run with legacy state or a
|
|
774
|
+
* provider dispatch that lacks a sealed tuple is terminally superseded.
|
|
775
|
+
* User/project artifacts remain in their own tables; only workflow authority
|
|
776
|
+
* is fenced. The marker is committed atomically with the classification.
|
|
777
|
+
*/
|
|
778
|
+
private applyStructuredCommunicationV2Cutover;
|
|
666
779
|
private ensureColumn;
|
|
667
780
|
}
|