@abelxiaoxing/cadence 1.3.0 → 1.4.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.
- package/README.md +115 -10
- package/package.json +5 -3
- package/prompts/abel-design.md +71 -17
- package/prompts/abel-diagnose.md +10 -1
- package/prompts/abel-implement.md +118 -20
- package/prompts/abel-init.md +9 -2
- package/src/apply-transaction.ts +16 -14
- package/src/artifact-store.ts +17 -0
- package/src/candidate-patch.ts +2 -1
- package/src/canonical.ts +16 -0
- package/src/change-contract.ts +268 -0
- package/src/child-session.ts +4 -3
- package/src/contracts.ts +141 -33
- package/src/control-contracts.ts +51 -1
- package/src/delivery-compiler.ts +148 -31
- package/src/design-control.ts +69 -11
- package/src/design-journal.ts +216 -72
- package/src/durable-workflow.ts +4385 -0
- package/src/implement-graph.ts +11 -16
- package/src/index.ts +380 -981
- package/src/isolation-backend.ts +84 -23
- package/src/package-delivery.ts +486 -0
- package/src/package-verification.ts +509 -0
- package/src/route-policy.ts +1 -11
- package/src/run-state.ts +4 -16
- package/src/run-store.ts +5 -144
- package/src/scoped-grep-worker.mjs +23 -0
- package/src/scoped-tools.ts +312 -116
- package/src/sqlite-schema.ts +171 -0
- package/src/storage-schema.ts +352 -0
- package/src/subagent-activity.ts +14 -1
- package/src/submit-tool.ts +20 -2
- package/src/task-ledger.ts +14 -55
- package/src/verification-capability.ts +298 -102
- package/src/verification-environment.ts +213 -0
- package/src/verification-identity.ts +77 -0
- package/src/worker-broker.ts +97 -34
- package/src/workflow-engine.ts +28 -8285
- package/src/workflow-policy.ts +1427 -0
- package/src/workflow-state-machine.ts +4478 -0
- package/src/workspace-io-entry.mjs +58 -0
- package/src/workspace-io-worker.mjs +984 -0
- package/src/workspace-io.ts +84 -0
- package/src/workspace-store.ts +125 -19
- package/skills/abel-workflow/SKILL.md +0 -205
package/src/design-journal.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { compareCanonicalStrings } from "./canonical.ts";
|
|
4
|
+
import {
|
|
5
|
+
type ChangeContract,
|
|
6
|
+
normalizeChangeContract,
|
|
7
|
+
} from "./change-contract.ts";
|
|
3
8
|
import {
|
|
4
9
|
type DesignEvidenceResult,
|
|
5
10
|
validateEvidenceResult,
|
|
6
11
|
} from "./contracts.ts";
|
|
7
12
|
import { canonicalJson } from "./run-state.ts";
|
|
13
|
+
import { configureSqlite, ensureSqliteSchema } from "./sqlite-schema.ts";
|
|
8
14
|
import { prepareStateRoot, type ResolvedStateRoot } from "./state-root.ts";
|
|
15
|
+
import { DESIGN_SCHEMA as SCHEMA } from "./storage-schema.ts";
|
|
9
16
|
|
|
10
17
|
const SHA256 = /^[a-f0-9]{64}$/u;
|
|
11
18
|
const IDENTIFIER = /^[a-z0-9](?:[a-z0-9._:-]{0,126}[a-z0-9])?$/iu;
|
|
@@ -13,51 +20,6 @@ const ZERO_HASH = "0".repeat(64);
|
|
|
13
20
|
const MAX_PLAN_BYTES = 16 * 1024 * 1024;
|
|
14
21
|
const MAX_CONTRACT_BYTES = 256 * 1024;
|
|
15
22
|
|
|
16
|
-
const SCHEMA = `
|
|
17
|
-
CREATE TABLE IF NOT EXISTS design_facts (
|
|
18
|
-
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
19
|
-
sequence INTEGER NOT NULL,
|
|
20
|
-
kind TEXT NOT NULL,
|
|
21
|
-
identity TEXT NOT NULL,
|
|
22
|
-
payload_json TEXT NOT NULL,
|
|
23
|
-
canonical_hash TEXT NOT NULL,
|
|
24
|
-
prior_record_hash TEXT NOT NULL,
|
|
25
|
-
record_hash TEXT NOT NULL,
|
|
26
|
-
PRIMARY KEY (run_id, sequence),
|
|
27
|
-
UNIQUE (run_id, kind, identity),
|
|
28
|
-
UNIQUE (run_id, record_hash)
|
|
29
|
-
) STRICT;
|
|
30
|
-
|
|
31
|
-
CREATE TABLE IF NOT EXISTS design_operations (
|
|
32
|
-
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
33
|
-
operation_id TEXT NOT NULL,
|
|
34
|
-
operation TEXT NOT NULL,
|
|
35
|
-
request_hash TEXT NOT NULL,
|
|
36
|
-
outcome_json TEXT NOT NULL,
|
|
37
|
-
PRIMARY KEY (run_id, operation_id)
|
|
38
|
-
) STRICT;
|
|
39
|
-
|
|
40
|
-
CREATE TABLE IF NOT EXISTS design_compiled_plans (
|
|
41
|
-
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
42
|
-
revision INTEGER NOT NULL,
|
|
43
|
-
fact_sequence INTEGER NOT NULL,
|
|
44
|
-
raw_sha256 TEXT NOT NULL,
|
|
45
|
-
canonical_hash TEXT NOT NULL,
|
|
46
|
-
plan_bytes BLOB NOT NULL,
|
|
47
|
-
PRIMARY KEY (run_id, revision),
|
|
48
|
-
UNIQUE (run_id, fact_sequence),
|
|
49
|
-
FOREIGN KEY (run_id, fact_sequence)
|
|
50
|
-
REFERENCES design_facts(run_id, sequence) ON DELETE CASCADE
|
|
51
|
-
) STRICT;
|
|
52
|
-
|
|
53
|
-
CREATE TABLE IF NOT EXISTS design_finalization_leases (
|
|
54
|
-
run_id TEXT PRIMARY KEY REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
55
|
-
operation_id TEXT NOT NULL,
|
|
56
|
-
token TEXT NOT NULL UNIQUE,
|
|
57
|
-
expires_at INTEGER NOT NULL
|
|
58
|
-
) STRICT;
|
|
59
|
-
`;
|
|
60
|
-
|
|
61
23
|
type DecisionCategory = "behavior" | "technical";
|
|
62
24
|
type DesignGate = "gate-a" | "gate-b";
|
|
63
25
|
|
|
@@ -151,6 +113,7 @@ export interface DesignGateProjection {
|
|
|
151
113
|
}
|
|
152
114
|
|
|
153
115
|
export interface DesignStatusProjection {
|
|
116
|
+
changeContract?: ChangeContract;
|
|
154
117
|
runId: string;
|
|
155
118
|
change?: string;
|
|
156
119
|
evidence: DesignEvidenceProjection[];
|
|
@@ -283,7 +246,9 @@ function normalizeRefs(refs: readonly string[]): string[] {
|
|
|
283
246
|
) {
|
|
284
247
|
throw new Error("design-decision-refs-invalid");
|
|
285
248
|
}
|
|
286
|
-
return [...new Set(refs)].sort((left, right) =>
|
|
249
|
+
return [...new Set(refs)].sort((left, right) =>
|
|
250
|
+
compareCanonicalStrings(left, right),
|
|
251
|
+
);
|
|
287
252
|
}
|
|
288
253
|
|
|
289
254
|
export class DesignJournal {
|
|
@@ -318,11 +283,8 @@ export class DesignJournal {
|
|
|
318
283
|
const stateRoot = prepareStateRoot(unresolved);
|
|
319
284
|
const database = new DatabaseSync(stateRoot.databasePath);
|
|
320
285
|
try {
|
|
321
|
-
database
|
|
322
|
-
database
|
|
323
|
-
database.exec("PRAGMA synchronous = FULL");
|
|
324
|
-
database.exec("PRAGMA busy_timeout = 5000");
|
|
325
|
-
database.exec(SCHEMA);
|
|
286
|
+
configureSqlite(database);
|
|
287
|
+
ensureSqliteSchema(database, SCHEMA);
|
|
326
288
|
return new DesignJournal(stateRoot, database, options);
|
|
327
289
|
} catch (error) {
|
|
328
290
|
try {
|
|
@@ -868,10 +830,10 @@ export class DesignJournal {
|
|
|
868
830
|
}))
|
|
869
831
|
.sort(
|
|
870
832
|
(left, right) =>
|
|
871
|
-
left.path
|
|
833
|
+
compareCanonicalStrings(left.path, right.path) ||
|
|
872
834
|
left.lineStart - right.lineStart ||
|
|
873
835
|
left.lineEnd - right.lineEnd ||
|
|
874
|
-
left.claimHash
|
|
836
|
+
compareCanonicalStrings(left.claimHash, right.claimHash),
|
|
875
837
|
),
|
|
876
838
|
};
|
|
877
839
|
const fact = this.#appendFact(
|
|
@@ -954,6 +916,7 @@ export class DesignJournal {
|
|
|
954
916
|
revision,
|
|
955
917
|
contractHash: decisionContractHash,
|
|
956
918
|
refs,
|
|
919
|
+
authorityChanged: prior?.contractHash !== decisionContractHash,
|
|
957
920
|
};
|
|
958
921
|
const fact = this.#appendFact(
|
|
959
922
|
input.runId,
|
|
@@ -1019,7 +982,7 @@ export class DesignJournal {
|
|
|
1019
982
|
runId: string;
|
|
1020
983
|
operationId: string;
|
|
1021
984
|
gate: "gate-a";
|
|
1022
|
-
contract: string;
|
|
985
|
+
contract: string | ChangeContract;
|
|
1023
986
|
}
|
|
1024
987
|
| {
|
|
1025
988
|
runId: string;
|
|
@@ -1035,9 +998,18 @@ export class DesignJournal {
|
|
|
1035
998
|
if (input.gate !== "gate-a" && input.gate !== "gate-b") {
|
|
1036
999
|
throw new Error("design-gate-invalid");
|
|
1037
1000
|
}
|
|
1001
|
+
const changeContract =
|
|
1002
|
+
input.gate === "gate-a" && typeof input.contract !== "string"
|
|
1003
|
+
? normalizeChangeContract(input.contract)
|
|
1004
|
+
: undefined;
|
|
1038
1005
|
const gateAContractHash =
|
|
1039
1006
|
input.gate === "gate-a"
|
|
1040
|
-
? contractHash(
|
|
1007
|
+
? contractHash(
|
|
1008
|
+
"abel-design-gate-a",
|
|
1009
|
+
changeContract
|
|
1010
|
+
? canonicalJson(changeContract)
|
|
1011
|
+
: (input.contract as string),
|
|
1012
|
+
)
|
|
1041
1013
|
: undefined;
|
|
1042
1014
|
return this.#transaction(() => {
|
|
1043
1015
|
this.#run(input.runId, true);
|
|
@@ -1062,9 +1034,9 @@ export class DesignJournal {
|
|
|
1062
1034
|
if (!current.gateA.current) throw new Error("design-gate-a-required");
|
|
1063
1035
|
const plan = this.#currentPlanProjection(input.runId);
|
|
1064
1036
|
if (!plan) throw new Error("design-plan-required");
|
|
1065
|
-
const latestDecisionSequence = this.#
|
|
1066
|
-
.
|
|
1067
|
-
|
|
1037
|
+
const latestDecisionSequence = this.#latestSubstantiveDecisionSequence(
|
|
1038
|
+
input.runId,
|
|
1039
|
+
);
|
|
1068
1040
|
if (
|
|
1069
1041
|
plan.sequence <= (current.gateA.approvedSequence ?? 0) ||
|
|
1070
1042
|
(latestDecisionSequence !== undefined &&
|
|
@@ -1093,6 +1065,7 @@ export class DesignJournal {
|
|
|
1093
1065
|
gate: input.gate,
|
|
1094
1066
|
revision,
|
|
1095
1067
|
contractHash: approvedContractHash,
|
|
1068
|
+
...(changeContract ? { changeContract } : {}),
|
|
1096
1069
|
};
|
|
1097
1070
|
const fact = this.#appendFact(
|
|
1098
1071
|
input.runId,
|
|
@@ -1148,6 +1121,20 @@ export class DesignJournal {
|
|
|
1148
1121
|
return undefined;
|
|
1149
1122
|
}
|
|
1150
1123
|
|
|
1124
|
+
#certifyPlan(runId: string, planHash: string): void {
|
|
1125
|
+
const gates = this.#gateStatus(runId);
|
|
1126
|
+
if (gates.gateB.current && gates.gateB.proof?.contractHash === planHash)
|
|
1127
|
+
return;
|
|
1128
|
+
const previous = this.#latestApproval(runId, "gate-b");
|
|
1129
|
+
const revision = (previous?.proof.revision ?? 0) + 1;
|
|
1130
|
+
this.#appendFact(runId, "approval", `gate-b:${revision}`, {
|
|
1131
|
+
gate: "gate-b",
|
|
1132
|
+
revision,
|
|
1133
|
+
contractHash: planHash,
|
|
1134
|
+
authority: "compiler",
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1151
1138
|
recordCompiledPlan(input: {
|
|
1152
1139
|
runId: string;
|
|
1153
1140
|
operationId: string;
|
|
@@ -1155,6 +1142,7 @@ export class DesignJournal {
|
|
|
1155
1142
|
rawSha256: string;
|
|
1156
1143
|
canonicalHash: string;
|
|
1157
1144
|
lease?: DesignFinalizationLease;
|
|
1145
|
+
certify?: boolean;
|
|
1158
1146
|
}): Record<string, unknown> {
|
|
1159
1147
|
requireHash(input.rawSha256, "design-plan-raw-hash");
|
|
1160
1148
|
requireHash(input.canonicalHash, "design-plan-canonical-hash");
|
|
@@ -1193,6 +1181,32 @@ export class DesignJournal {
|
|
|
1193
1181
|
throw new Error("design-gate-a-required");
|
|
1194
1182
|
}
|
|
1195
1183
|
const prior = this.#currentPlanProjection(input.runId);
|
|
1184
|
+
const status = this.status(input.runId);
|
|
1185
|
+
const latestDecision =
|
|
1186
|
+
this.#latestSubstantiveDecisionSequence(input.runId) ?? 0;
|
|
1187
|
+
if (
|
|
1188
|
+
prior &&
|
|
1189
|
+
prior.rawSha256 === input.rawSha256 &&
|
|
1190
|
+
prior.canonicalHash === input.canonicalHash &&
|
|
1191
|
+
prior.sequence > latestDecision &&
|
|
1192
|
+
prior.sequence > (status.gates.gateA.approvedSequence ?? 0)
|
|
1193
|
+
) {
|
|
1194
|
+
if (input.certify) this.#certifyPlan(input.runId, input.canonicalHash);
|
|
1195
|
+
const outcome = {
|
|
1196
|
+
operation: "compile-plan",
|
|
1197
|
+
runId: input.runId,
|
|
1198
|
+
plan: prior,
|
|
1199
|
+
gates: this.#gateStatus(input.runId),
|
|
1200
|
+
};
|
|
1201
|
+
this.#recordOperation(
|
|
1202
|
+
input.runId,
|
|
1203
|
+
input.operationId,
|
|
1204
|
+
"compile-plan",
|
|
1205
|
+
request,
|
|
1206
|
+
outcome,
|
|
1207
|
+
);
|
|
1208
|
+
return structuredClone(outcome);
|
|
1209
|
+
}
|
|
1196
1210
|
const revision = (prior?.revision ?? 0) + 1;
|
|
1197
1211
|
const payload = {
|
|
1198
1212
|
revision,
|
|
@@ -1227,6 +1241,7 @@ export class DesignJournal {
|
|
|
1227
1241
|
recordHash: fact.record_hash,
|
|
1228
1242
|
sequence: fact.sequence,
|
|
1229
1243
|
};
|
|
1244
|
+
if (input.certify) this.#certifyPlan(input.runId, input.canonicalHash);
|
|
1230
1245
|
const outcome = {
|
|
1231
1246
|
operation: "compile-plan",
|
|
1232
1247
|
runId: input.runId,
|
|
@@ -1511,26 +1526,34 @@ export class DesignJournal {
|
|
|
1511
1526
|
});
|
|
1512
1527
|
}
|
|
1513
1528
|
|
|
1514
|
-
#
|
|
1515
|
-
|
|
1516
|
-
|
|
1529
|
+
#latestSubstantiveDecisionSequence(
|
|
1530
|
+
runId: string,
|
|
1531
|
+
category?: DecisionCategory,
|
|
1532
|
+
): number | undefined {
|
|
1533
|
+
return this.#facts(runId)
|
|
1517
1534
|
.filter((fact) => {
|
|
1518
1535
|
if (fact.kind !== "decision") return false;
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1536
|
+
const decision = parseRecord(
|
|
1537
|
+
fact.payload_json,
|
|
1538
|
+
"design-decision-record-invalid",
|
|
1522
1539
|
);
|
|
1523
|
-
})
|
|
1524
|
-
.at(-1)?.sequence;
|
|
1525
|
-
const latestTechnical = facts
|
|
1526
|
-
.filter((fact) => {
|
|
1527
|
-
if (fact.kind !== "decision") return false;
|
|
1528
1540
|
return (
|
|
1529
|
-
|
|
1530
|
-
|
|
1541
|
+
decision.authorityChanged !== false &&
|
|
1542
|
+
(category === undefined || decision.category === category)
|
|
1531
1543
|
);
|
|
1532
1544
|
})
|
|
1533
1545
|
.at(-1)?.sequence;
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
#gateStatus(runId: string): DesignStatusProjection["gates"] {
|
|
1549
|
+
const latestBehavior = this.#latestSubstantiveDecisionSequence(
|
|
1550
|
+
runId,
|
|
1551
|
+
"behavior",
|
|
1552
|
+
);
|
|
1553
|
+
const latestTechnical = this.#latestSubstantiveDecisionSequence(
|
|
1554
|
+
runId,
|
|
1555
|
+
"technical",
|
|
1556
|
+
);
|
|
1534
1557
|
const plan = this.#currentPlanProjection(runId);
|
|
1535
1558
|
const gateAApproval = this.#latestApproval(runId, "gate-a");
|
|
1536
1559
|
const gateBApproval = this.#latestApproval(runId, "gate-b");
|
|
@@ -1587,6 +1610,119 @@ export class DesignJournal {
|
|
|
1587
1610
|
return { gateA, gateB };
|
|
1588
1611
|
}
|
|
1589
1612
|
|
|
1613
|
+
/** Seed a new revision only from a completed, privately finalized delivery. */
|
|
1614
|
+
inheritFinalizedAuthority(runId: string): void {
|
|
1615
|
+
this.#transaction(() => {
|
|
1616
|
+
const run = this.#run(runId, true);
|
|
1617
|
+
if (!run.change_name || this.#facts(runId).length > 0) return;
|
|
1618
|
+
const candidates = this.#database
|
|
1619
|
+
.prepare(`
|
|
1620
|
+
SELECT facts.run_id, facts.payload_json FROM design_facts AS facts
|
|
1621
|
+
JOIN runs ON runs.run_id = facts.run_id
|
|
1622
|
+
WHERE runs.root_hash = ? AND runs.stage = 'abel-design'
|
|
1623
|
+
AND runs.change_name = ? AND runs.state = 'completed'
|
|
1624
|
+
AND facts.kind = 'finalization' AND runs.run_id <> ?
|
|
1625
|
+
`)
|
|
1626
|
+
.all(run.root_hash, run.change_name, runId) as unknown as Array<{
|
|
1627
|
+
run_id: string;
|
|
1628
|
+
payload_json: string;
|
|
1629
|
+
}>;
|
|
1630
|
+
const source = candidates
|
|
1631
|
+
.map((row) => ({
|
|
1632
|
+
runId: row.run_id,
|
|
1633
|
+
fact: parseRecord(
|
|
1634
|
+
row.payload_json,
|
|
1635
|
+
"design-finalization-record-invalid",
|
|
1636
|
+
),
|
|
1637
|
+
}))
|
|
1638
|
+
.sort(
|
|
1639
|
+
(left, right) =>
|
|
1640
|
+
Number(right.fact.deliveryRevision) -
|
|
1641
|
+
Number(left.fact.deliveryRevision),
|
|
1642
|
+
)[0];
|
|
1643
|
+
if (!source) return;
|
|
1644
|
+
const status = this.status(source.runId);
|
|
1645
|
+
const plan = this.currentCompiledPlan(source.runId);
|
|
1646
|
+
const gateA = status.gates.gateA.proof;
|
|
1647
|
+
const gateB = status.gates.gateB.proof;
|
|
1648
|
+
if (
|
|
1649
|
+
!plan ||
|
|
1650
|
+
!gateA ||
|
|
1651
|
+
!gateB ||
|
|
1652
|
+
!status.gates.gateA.current ||
|
|
1653
|
+
!status.gates.gateB.current ||
|
|
1654
|
+
sha256(plan.bytes) !== plan.projection.rawSha256 ||
|
|
1655
|
+
!this.verifyFinalizedDelivery({
|
|
1656
|
+
change: run.change_name,
|
|
1657
|
+
deliveryRevision: Number(source.fact.deliveryRevision),
|
|
1658
|
+
receiptHash: String(source.fact.receiptHash),
|
|
1659
|
+
gateA,
|
|
1660
|
+
gateB,
|
|
1661
|
+
planCanonicalHash: plan.projection.canonicalHash,
|
|
1662
|
+
})
|
|
1663
|
+
) {
|
|
1664
|
+
throw new Error("design-inherited-authority-invalid");
|
|
1665
|
+
}
|
|
1666
|
+
this.#appendFact(runId, "inheritance", "finalized-authority", {
|
|
1667
|
+
sourceRunId: source.runId,
|
|
1668
|
+
deliveryRevision: source.fact.deliveryRevision,
|
|
1669
|
+
receiptHash: source.fact.receiptHash,
|
|
1670
|
+
gateA,
|
|
1671
|
+
gateB,
|
|
1672
|
+
});
|
|
1673
|
+
for (const decision of status.decisions) {
|
|
1674
|
+
this.#appendFact(
|
|
1675
|
+
runId,
|
|
1676
|
+
"decision",
|
|
1677
|
+
`${decision.decisionId}:${decision.revision}`,
|
|
1678
|
+
{
|
|
1679
|
+
decisionId: decision.decisionId,
|
|
1680
|
+
category: decision.category,
|
|
1681
|
+
revision: decision.revision,
|
|
1682
|
+
contractHash: decision.contractHash,
|
|
1683
|
+
refs: decision.refs,
|
|
1684
|
+
},
|
|
1685
|
+
);
|
|
1686
|
+
}
|
|
1687
|
+
this.#appendFact(runId, "approval", `gate-a:${gateA.revision}`, {
|
|
1688
|
+
gate: "gate-a",
|
|
1689
|
+
revision: gateA.revision,
|
|
1690
|
+
contractHash: gateA.contractHash,
|
|
1691
|
+
...(status.changeContract
|
|
1692
|
+
? { changeContract: status.changeContract }
|
|
1693
|
+
: {}),
|
|
1694
|
+
});
|
|
1695
|
+
const projection = plan.projection;
|
|
1696
|
+
const fact = this.#appendFact(
|
|
1697
|
+
runId,
|
|
1698
|
+
"plan",
|
|
1699
|
+
String(projection.revision),
|
|
1700
|
+
{
|
|
1701
|
+
revision: projection.revision,
|
|
1702
|
+
rawSha256: projection.rawSha256,
|
|
1703
|
+
canonicalHash: projection.canonicalHash,
|
|
1704
|
+
},
|
|
1705
|
+
);
|
|
1706
|
+
this.#database
|
|
1707
|
+
.prepare(`INSERT INTO design_compiled_plans(
|
|
1708
|
+
run_id, revision, fact_sequence, raw_sha256, canonical_hash, plan_bytes
|
|
1709
|
+
) VALUES (?, ?, ?, ?, ?, ?)`)
|
|
1710
|
+
.run(
|
|
1711
|
+
runId,
|
|
1712
|
+
projection.revision,
|
|
1713
|
+
fact.sequence,
|
|
1714
|
+
projection.rawSha256,
|
|
1715
|
+
projection.canonicalHash,
|
|
1716
|
+
Buffer.from(plan.bytes),
|
|
1717
|
+
);
|
|
1718
|
+
this.#appendFact(runId, "approval", `gate-b:${gateB.revision}`, {
|
|
1719
|
+
gate: "gate-b",
|
|
1720
|
+
revision: gateB.revision,
|
|
1721
|
+
contractHash: gateB.contractHash,
|
|
1722
|
+
});
|
|
1723
|
+
});
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1590
1726
|
status(runId: string): DesignStatusProjection {
|
|
1591
1727
|
this.#assertOpen();
|
|
1592
1728
|
const run = this.#run(runId, false);
|
|
@@ -1609,12 +1745,20 @@ export class DesignJournal {
|
|
|
1609
1745
|
);
|
|
1610
1746
|
latestDecisions.set(decision.decisionId, decision);
|
|
1611
1747
|
}
|
|
1748
|
+
const approved = facts
|
|
1749
|
+
.filter((fact) => fact.kind === "approval")
|
|
1750
|
+
.map((fact) => parseRecord(fact.payload_json, "design-approval-invalid"))
|
|
1751
|
+
.filter((fact) => fact.gate === "gate-a")
|
|
1752
|
+
.at(-1);
|
|
1612
1753
|
return {
|
|
1613
1754
|
runId,
|
|
1614
1755
|
...(run.change_name ? { change: run.change_name } : {}),
|
|
1756
|
+
...(approved?.changeContract
|
|
1757
|
+
? { changeContract: normalizeChangeContract(approved.changeContract) }
|
|
1758
|
+
: {}),
|
|
1615
1759
|
evidence,
|
|
1616
1760
|
decisions: [...latestDecisions.values()].sort((left, right) =>
|
|
1617
|
-
left.decisionId
|
|
1761
|
+
compareCanonicalStrings(left.decisionId, right.decisionId),
|
|
1618
1762
|
),
|
|
1619
1763
|
gates: this.#gateStatus(runId),
|
|
1620
1764
|
plan: this.#currentPlanProjection(runId) ?? null,
|