@abelxiaoxing/cadence 1.8.4 → 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 +324 -117
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/agents/design-explorer.md +14 -18
- package/agents/diagnosis-worker.md +14 -14
- package/agents/implementation-worker.md +16 -17
- package/config/workflow-task.example.json +7 -0
- package/docs/remote-access.md +60 -0
- package/docs/reuse-and-distribution.md +44 -0
- package/docs/service-management.md +108 -0
- package/licenses/bundled-web.txt +6245 -0
- package/licenses/pi-web-ui-MIT.txt +21 -0
- package/licenses/pi-web-ui-reuse.md +22 -0
- package/package.json +92 -20
- package/src/index.ts +21 -1802
- package/src/native-build.ts +6 -0
- package/src/native-client.ts +103 -0
- package/src/native-export.ts +58 -0
- package/src/native-history.ts +359 -0
- package/src/operator-cli.mjs +453 -1937
- package/src/plan-proposal.ts +87 -0
- package/src/sdk-worker.mjs +549 -0
- package/src/service/contracts/active.ts +82 -0
- package/src/service/contracts/budget.ts +148 -0
- package/src/service/contracts/build.ts +67 -0
- package/src/service/contracts/capture.ts +48 -0
- package/src/service/contracts/core.ts +33 -0
- package/src/service/contracts/current.ts +261 -0
- package/src/service/contracts/domain.ts +68 -0
- package/src/service/contracts/history.ts +2245 -0
- package/src/service/contracts/models.ts +139 -0
- package/src/service/coordination.ts +862 -0
- package/src/service/private-files.ts +204 -0
- package/src/service-cli.mjs +5592 -0
- package/src/service-client.ts +747 -0
- package/src/service-contract.ts +245 -0
- package/src/service-host.mjs +22382 -0
- package/src/service-http-client.ts +425 -0
- package/src/service-wire.ts +372 -0
- package/src/task-shell.ts +1045 -0
- package/src/task-store.ts +479 -0
- package/src/task-types.ts +184 -0
- package/src/verification-diagnostics.ts +29 -72
- package/src/worker-capture.mjs +1197 -0
- package/src/worker-public.mjs +1753 -0
- package/src/worker-redaction.mjs +608 -0
- package/src/worker-tools.mjs +202 -0
- package/web/dist/assets/KaTeX_AMS-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_AMS-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Caligraphic-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Fraktur-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Main-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-BoldItalic.woff2 +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_Math-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Bold.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Italic.woff2 +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_SansSerif-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Script-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size1-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size2-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size3-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Size4-Regular.woff2 +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.ttf +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff +0 -0
- package/web/dist/assets/KaTeX_Typewriter-Regular.woff2 +0 -0
- package/web/dist/assets/cadence.css +2 -0
- package/web/dist/assets/cadence.js +7 -0
- package/web/dist/assets/markdown.js +296 -0
- package/web/dist/assets/react.js +9 -0
- package/web/dist/assets/rolldown-runtime.js +1 -0
- package/web/dist/assets/timeline-client.js +41 -0
- package/web/dist/assets/vis-timeline.css +1274 -0
- package/web/dist/assets/vis-timeline.js +32 -0
- package/web/dist/index.html +18 -0
- package/web/dist/manifest.json +505 -0
- package/config/plan-draft.example.json +0 -125
- package/config/plan-draft.multiple-tasks.example.json +0 -375
- package/config/plan-draft.quick.example.json +0 -78
- package/prompts/abel-design.md +0 -382
- package/prompts/abel-diagnose.md +0 -71
- package/prompts/abel-implement.md +0 -284
- package/prompts/abel-init.md +0 -52
- package/src/activation.ts +0 -44
- package/src/activity-contracts.ts +0 -82
- package/src/agent-registry.ts +0 -53
- package/src/apply-transaction.ts +0 -1437
- package/src/artifact-store.ts +0 -213
- package/src/build-windows-job.mjs +0 -95
- package/src/candidate-artifact.ts +0 -143
- package/src/candidate-context.ts +0 -289
- package/src/candidate-patch.ts +0 -271
- package/src/candidate-workspace.ts +0 -80
- package/src/canonical.ts +0 -16
- package/src/change-contract-schema.ts +0 -191
- package/src/change-contract.ts +0 -324
- package/src/change-verification.ts +0 -1617
- package/src/contracts.ts +0 -2466
- package/src/control-contracts.ts +0 -382
- package/src/delivery-compiler.ts +0 -1805
- package/src/delivery-revision.ts +0 -42
- package/src/design-control.ts +0 -1299
- package/src/design-diagnostics.ts +0 -181
- package/src/design-journal.ts +0 -1773
- package/src/directory-sync.ts +0 -36
- package/src/durable-contracts.ts +0 -227
- package/src/durable-workflow.ts +0 -822
- package/src/evidence-draft.ts +0 -122
- package/src/execution-profile.ts +0 -64
- package/src/execution-retention.ts +0 -77
- package/src/file-snapshot.ts +0 -163
- package/src/implement-continuation.ts +0 -559
- package/src/implement-graph.ts +0 -690
- package/src/implement-plan.ts +0 -72
- package/src/install-config.d.mts +0 -6
- package/src/isolation-backend.ts +0 -353
- package/src/model-source.ts +0 -22
- package/src/openspec-cli.ts +0 -494
- package/src/operator-cli-entry.mjs +0 -24
- package/src/operator-tools.ts +0 -309
- package/src/package-candidate-process.ts +0 -206
- package/src/package-candidate.ts +0 -24
- package/src/package-delivery.ts +0 -486
- package/src/package-state.ts +0 -320
- package/src/package-verification.ts +0 -635
- package/src/package-workflow.ts +0 -305
- package/src/packet-runtime.ts +0 -459
- package/src/phase-execution.ts +0 -2163
- package/src/pi-adapter.ts +0 -19
- package/src/plan-draft-summary.ts +0 -527
- package/src/plan-draft.ts +0 -502
- package/src/remove-path.ts +0 -32
- package/src/run-state.ts +0 -420
- package/src/run-store.ts +0 -1179
- package/src/safe-path.ts +0 -77
- package/src/scoped-grep-worker.mjs +0 -23
- package/src/scoped-tools.ts +0 -775
- package/src/single-task-draft.ts +0 -142
- package/src/sqlite-schema.ts +0 -171
- package/src/state-root.ts +0 -215
- package/src/storage-schema.ts +0 -360
- package/src/subagent-activity.ts +0 -1417
- package/src/subagent-process.ts +0 -476
- package/src/task-ledger.ts +0 -1338
- package/src/transport-budget.ts +0 -50
- package/src/verification-capability.ts +0 -1191
- package/src/verification-environment-io.ts +0 -389
- package/src/verification-environment.ts +0 -162
- package/src/verification-identity.ts +0 -77
- package/src/verification-scan.ts +0 -64
- package/src/windows-job-backend.ts +0 -419
- package/src/windows-job.c +0 -149
- package/src/workflow-engine.ts +0 -28
- package/src/workflow-policy.ts +0 -1670
- package/src/workflow-recovery-policy.ts +0 -97
- package/src/workflow-scheduling.ts +0 -105
- package/src/workflow-state-machine.ts +0 -4341
- package/src/workflow-status.ts +0 -576
- package/src/workspace-io-entry.mjs +0 -76
- package/src/workspace-io-worker.mjs +0 -1312
- package/src/workspace-io.ts +0 -89
- package/src/workspace-store.ts +0 -634
package/src/single-task-draft.ts
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { DesignPlanValidationError } from "./design-diagnostics.ts";
|
|
2
|
-
import type {
|
|
3
|
-
DraftVerification,
|
|
4
|
-
PlanDraft,
|
|
5
|
-
PlanTaskInput,
|
|
6
|
-
} from "./plan-draft.ts";
|
|
7
|
-
|
|
8
|
-
/** Small author input. Evidence, scope and the complete suite remain explicit. */
|
|
9
|
-
export interface SingleTaskDraft {
|
|
10
|
-
changeId: string;
|
|
11
|
-
changeContract?: PlanDraft["changeContract"];
|
|
12
|
-
singleTask: {
|
|
13
|
-
taskId: string;
|
|
14
|
-
objective: string;
|
|
15
|
-
context: PlanTaskInput["context"];
|
|
16
|
-
roots: string[];
|
|
17
|
-
read: string[];
|
|
18
|
-
greenWrite: string[];
|
|
19
|
-
redWrite?: string[];
|
|
20
|
-
expectedFailure?: string;
|
|
21
|
-
verificationMode?: "behavior" | "mechanical" | "refactor";
|
|
22
|
-
verification: DraftVerification;
|
|
23
|
-
fullSuite: DraftVerification;
|
|
24
|
-
impactClosure: PlanTaskInput["impactClosure"];
|
|
25
|
-
agents: PlanTaskInput["agents"];
|
|
26
|
-
recovery?: { artifactAttempts: number; repairAttempts: number };
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function invalid(code: string, field = "singleTask"): never {
|
|
31
|
-
throw new DesignPlanValidationError(code, [{ code, field }]);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function record(value: unknown): value is Record<string, unknown> {
|
|
35
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** Mechanical expansion only; the ordinary compiler validates all resulting fields. */
|
|
39
|
-
export function expandSingleTaskDraft(value: unknown): unknown {
|
|
40
|
-
if (!record(value) || !Object.hasOwn(value, "singleTask")) return value;
|
|
41
|
-
if (
|
|
42
|
-
Object.keys(value).some(
|
|
43
|
-
(key) => !["changeId", "changeContract", "singleTask"].includes(key),
|
|
44
|
-
) ||
|
|
45
|
-
!record(value.singleTask)
|
|
46
|
-
)
|
|
47
|
-
invalid("single-task-draft-invalid");
|
|
48
|
-
const input = value.singleTask;
|
|
49
|
-
const required = [
|
|
50
|
-
"taskId",
|
|
51
|
-
"objective",
|
|
52
|
-
"context",
|
|
53
|
-
"roots",
|
|
54
|
-
"read",
|
|
55
|
-
"greenWrite",
|
|
56
|
-
"verification",
|
|
57
|
-
"fullSuite",
|
|
58
|
-
"impactClosure",
|
|
59
|
-
"agents",
|
|
60
|
-
];
|
|
61
|
-
const optional = [
|
|
62
|
-
"redWrite",
|
|
63
|
-
"expectedFailure",
|
|
64
|
-
"verificationMode",
|
|
65
|
-
"recovery",
|
|
66
|
-
];
|
|
67
|
-
if (
|
|
68
|
-
required.some((key) => !Object.hasOwn(input, key)) ||
|
|
69
|
-
Object.keys(input).some((key) => ![...required, ...optional].includes(key))
|
|
70
|
-
)
|
|
71
|
-
invalid("single-task-draft-invalid");
|
|
72
|
-
const task = structuredClone(
|
|
73
|
-
input,
|
|
74
|
-
) as unknown as SingleTaskDraft["singleTask"];
|
|
75
|
-
const mode = task.verificationMode ?? "behavior";
|
|
76
|
-
if (mode === "behavior" && (!task.redWrite?.length || !task.expectedFailure))
|
|
77
|
-
invalid("single-task-red-required");
|
|
78
|
-
if (
|
|
79
|
-
mode !== "behavior" &&
|
|
80
|
-
(task.redWrite !== undefined || task.expectedFailure !== undefined)
|
|
81
|
-
)
|
|
82
|
-
invalid("single-task-red-unexpected");
|
|
83
|
-
if (
|
|
84
|
-
Object.hasOwn(input, "recovery") &&
|
|
85
|
-
(!record(task.recovery) ||
|
|
86
|
-
!Object.hasOwn(task.recovery, "artifactAttempts") ||
|
|
87
|
-
!Object.hasOwn(task.recovery, "repairAttempts") ||
|
|
88
|
-
Object.keys(task.recovery).some(
|
|
89
|
-
(key) => !["artifactAttempts", "repairAttempts"].includes(key),
|
|
90
|
-
))
|
|
91
|
-
)
|
|
92
|
-
invalid("single-task-recovery-invalid");
|
|
93
|
-
return {
|
|
94
|
-
changeId: value.changeId,
|
|
95
|
-
...(value.changeContract === undefined
|
|
96
|
-
? {}
|
|
97
|
-
: { changeContract: value.changeContract }),
|
|
98
|
-
tasks: [
|
|
99
|
-
{
|
|
100
|
-
taskId: task.taskId,
|
|
101
|
-
objective: task.objective,
|
|
102
|
-
context: task.context,
|
|
103
|
-
roots: task.roots,
|
|
104
|
-
read: task.read,
|
|
105
|
-
dependsOn: [],
|
|
106
|
-
verificationMode: mode,
|
|
107
|
-
phases: {
|
|
108
|
-
...(mode === "behavior"
|
|
109
|
-
? {
|
|
110
|
-
red: {
|
|
111
|
-
write: task.redWrite,
|
|
112
|
-
delete: [],
|
|
113
|
-
verification: {
|
|
114
|
-
...task.verification,
|
|
115
|
-
expectedFailure: task.expectedFailure,
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
}
|
|
119
|
-
: {}),
|
|
120
|
-
green: {
|
|
121
|
-
write: task.greenWrite,
|
|
122
|
-
delete: [],
|
|
123
|
-
verification: task.verification,
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
affectedVerification: task.fullSuite,
|
|
127
|
-
repairVerification: task.fullSuite,
|
|
128
|
-
scheduling: { conflicts: [], resources: [] },
|
|
129
|
-
agents: task.agents,
|
|
130
|
-
approvedDependencies: [],
|
|
131
|
-
impactClosure: task.impactClosure,
|
|
132
|
-
},
|
|
133
|
-
],
|
|
134
|
-
outputs: [],
|
|
135
|
-
verification: {
|
|
136
|
-
baseline: { fullSuite: task.fullSuite },
|
|
137
|
-
change: { fullSuite: task.fullSuite, postApply: task.fullSuite },
|
|
138
|
-
artifactCorrection: { maxAttempts: task.recovery?.artifactAttempts ?? 2 },
|
|
139
|
-
repair: { maxAttempts: task.recovery?.repairAttempts ?? 1 },
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
}
|
package/src/sqlite-schema.ts
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { DatabaseSync } from "node:sqlite";
|
|
2
|
-
import { canonicalJson } from "./canonical.ts";
|
|
3
|
-
|
|
4
|
-
export class SqliteSchemaError extends Error {
|
|
5
|
-
readonly code = "storage-schema-invalid";
|
|
6
|
-
constructor() {
|
|
7
|
-
super("storage-schema-invalid");
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type Row = Record<string, string | number | null>;
|
|
12
|
-
interface TableContract {
|
|
13
|
-
columns: Row[];
|
|
14
|
-
foreignKeys: string[];
|
|
15
|
-
uniqueKeys: string[];
|
|
16
|
-
strict: number;
|
|
17
|
-
}
|
|
18
|
-
const contracts = new Map<string, Map<string, TableContract>>();
|
|
19
|
-
const identifier = (name: string): string => `"${name.replaceAll('"', '""')}"`;
|
|
20
|
-
|
|
21
|
-
export function configureSqlite(
|
|
22
|
-
database: DatabaseSync,
|
|
23
|
-
busyTimeoutMs = 5000,
|
|
24
|
-
): void {
|
|
25
|
-
database.exec("PRAGMA foreign_keys = ON");
|
|
26
|
-
database.exec("PRAGMA journal_mode = WAL");
|
|
27
|
-
database.exec("PRAGMA synchronous = FULL");
|
|
28
|
-
database.exec(
|
|
29
|
-
`PRAGMA busy_timeout = ${Math.max(1, Math.min(30_000, busyTimeoutMs))}`,
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function describeTable(database: DatabaseSync, table: string): TableContract {
|
|
34
|
-
const columns = database
|
|
35
|
-
.prepare(`PRAGMA table_info(${identifier(table)})`)
|
|
36
|
-
.all() as Row[];
|
|
37
|
-
const foreignKeys = database
|
|
38
|
-
.prepare(`PRAGMA foreign_key_list(${identifier(table)})`)
|
|
39
|
-
.all() as Row[];
|
|
40
|
-
const indexes = database
|
|
41
|
-
.prepare(`PRAGMA index_list(${identifier(table)})`)
|
|
42
|
-
.all() as Row[];
|
|
43
|
-
const uniqueKeys = indexes
|
|
44
|
-
.filter((row) => row.unique === 1)
|
|
45
|
-
.map((row) => {
|
|
46
|
-
const fields = database
|
|
47
|
-
.prepare(`PRAGMA index_xinfo(${identifier(String(row.name))})`)
|
|
48
|
-
.all() as Row[];
|
|
49
|
-
return canonicalJson({
|
|
50
|
-
partial: row.partial,
|
|
51
|
-
fields: fields
|
|
52
|
-
.filter((field) => field.key === 1)
|
|
53
|
-
.map(({ name, coll, desc }) => ({ name, coll, desc })),
|
|
54
|
-
});
|
|
55
|
-
})
|
|
56
|
-
.sort();
|
|
57
|
-
const strict = (
|
|
58
|
-
database
|
|
59
|
-
.prepare(
|
|
60
|
-
"SELECT strict FROM pragma_table_list WHERE schema = 'main' AND name = ?",
|
|
61
|
-
)
|
|
62
|
-
.get(table) as Row | undefined
|
|
63
|
-
)?.strict;
|
|
64
|
-
return {
|
|
65
|
-
columns: columns.map(({ name, type, notnull, dflt_value, pk }) => ({
|
|
66
|
-
name,
|
|
67
|
-
type,
|
|
68
|
-
notnull,
|
|
69
|
-
dflt_value,
|
|
70
|
-
pk,
|
|
71
|
-
})),
|
|
72
|
-
foreignKeys: foreignKeys
|
|
73
|
-
.map(({ table: target, from, to, on_update, on_delete, match, seq }) =>
|
|
74
|
-
canonicalJson({ target, from, to, on_update, on_delete, match, seq }),
|
|
75
|
-
)
|
|
76
|
-
.sort(),
|
|
77
|
-
uniqueKeys,
|
|
78
|
-
strict: Number(strict ?? 0),
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function schemaContract(schema: string): Map<string, TableContract> {
|
|
83
|
-
const cached = contracts.get(schema);
|
|
84
|
-
if (cached) return cached;
|
|
85
|
-
const reference = new DatabaseSync(":memory:");
|
|
86
|
-
try {
|
|
87
|
-
reference.exec(schema);
|
|
88
|
-
const names = reference
|
|
89
|
-
.prepare(
|
|
90
|
-
"SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'",
|
|
91
|
-
)
|
|
92
|
-
.all() as Row[];
|
|
93
|
-
const contract = new Map(
|
|
94
|
-
names.map(({ name }) => [
|
|
95
|
-
String(name),
|
|
96
|
-
describeTable(reference, String(name)),
|
|
97
|
-
]),
|
|
98
|
-
);
|
|
99
|
-
contracts.set(schema, contract);
|
|
100
|
-
return contract;
|
|
101
|
-
} finally {
|
|
102
|
-
reference.close();
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Check every required column, key, foreign key and STRICT boundary; permit additive columns. */
|
|
107
|
-
export function matchesSqliteSchema(
|
|
108
|
-
database: DatabaseSync,
|
|
109
|
-
schema: string,
|
|
110
|
-
): boolean {
|
|
111
|
-
for (const [table, expected] of schemaContract(schema)) {
|
|
112
|
-
const actual = describeTable(database, table);
|
|
113
|
-
if (
|
|
114
|
-
actual.strict !== expected.strict ||
|
|
115
|
-
expected.columns.some(
|
|
116
|
-
(column) =>
|
|
117
|
-
!actual.columns.some(
|
|
118
|
-
(candidate) => canonicalJson(candidate) === canonicalJson(column),
|
|
119
|
-
),
|
|
120
|
-
) ||
|
|
121
|
-
canonicalJson(actual.foreignKeys) !==
|
|
122
|
-
canonicalJson(expected.foreignKeys) ||
|
|
123
|
-
canonicalJson(actual.uniqueKeys) !== canonicalJson(expected.uniqueKeys)
|
|
124
|
-
)
|
|
125
|
-
return false;
|
|
126
|
-
}
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Serialize creation and the closed, additive migration set across concurrent openers. */
|
|
131
|
-
export function ensureSqliteSchema(
|
|
132
|
-
database: DatabaseSync,
|
|
133
|
-
schema: string,
|
|
134
|
-
additions: Record<string, Record<string, string>> = {},
|
|
135
|
-
): void {
|
|
136
|
-
database.exec("BEGIN IMMEDIATE");
|
|
137
|
-
try {
|
|
138
|
-
const expected = [...schemaContract(schema).keys()];
|
|
139
|
-
const present = expected.filter((table) =>
|
|
140
|
-
database
|
|
141
|
-
.prepare(
|
|
142
|
-
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?",
|
|
143
|
-
)
|
|
144
|
-
.get(table),
|
|
145
|
-
);
|
|
146
|
-
// An absent namespace is a first open; a partially missing namespace is damaged.
|
|
147
|
-
if (present.length > 0 && present.length !== expected.length)
|
|
148
|
-
throw new SqliteSchemaError();
|
|
149
|
-
database.exec(schema);
|
|
150
|
-
for (const [table, columns] of Object.entries(additions)) {
|
|
151
|
-
const existing = new Set(
|
|
152
|
-
describeTable(database, table).columns.map(({ name }) => name),
|
|
153
|
-
);
|
|
154
|
-
for (const [column, declaration] of Object.entries(columns)) {
|
|
155
|
-
if (!existing.has(column))
|
|
156
|
-
database.exec(
|
|
157
|
-
`ALTER TABLE ${identifier(table)} ADD COLUMN ${identifier(column)} ${declaration}`,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (!matchesSqliteSchema(database, schema)) throw new SqliteSchemaError();
|
|
162
|
-
database.exec("COMMIT");
|
|
163
|
-
} catch (error) {
|
|
164
|
-
try {
|
|
165
|
-
database.exec("ROLLBACK");
|
|
166
|
-
} catch {
|
|
167
|
-
/* Preserve the original failure. */
|
|
168
|
-
}
|
|
169
|
-
throw error;
|
|
170
|
-
}
|
|
171
|
-
}
|
package/src/state-root.ts
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { chmodSync, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
export const STATE_ROOT_ERROR_CODES = [
|
|
7
|
-
"consumer-root-unavailable",
|
|
8
|
-
"invalid-state-base",
|
|
9
|
-
"state-component-symlink",
|
|
10
|
-
"state-component-type",
|
|
11
|
-
"state-root-inside-consumer",
|
|
12
|
-
] as const;
|
|
13
|
-
|
|
14
|
-
export type StateRootErrorCode = (typeof STATE_ROOT_ERROR_CODES)[number];
|
|
15
|
-
|
|
16
|
-
export class StateRootError extends Error {
|
|
17
|
-
readonly code: StateRootErrorCode;
|
|
18
|
-
|
|
19
|
-
constructor(code: StateRootErrorCode) {
|
|
20
|
-
super(code);
|
|
21
|
-
this.name = "StateRootError";
|
|
22
|
-
this.code = code;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface StateRootOptions {
|
|
27
|
-
consumerRoot: string;
|
|
28
|
-
xdgStateHome?: string;
|
|
29
|
-
homeDir?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface ResolvedStateRoot {
|
|
33
|
-
consumerRoot: string;
|
|
34
|
-
consumerRootHash: string;
|
|
35
|
-
stateBase: string;
|
|
36
|
-
productRoot: string;
|
|
37
|
-
rootsDir: string;
|
|
38
|
-
rootDir: string;
|
|
39
|
-
databasePath: string;
|
|
40
|
-
artifactsDir: string;
|
|
41
|
-
workspacesDir: string;
|
|
42
|
-
transactionsDir: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function isContainedBy(parent: string, candidate: string): boolean {
|
|
46
|
-
const relative = path.relative(parent, candidate);
|
|
47
|
-
return (
|
|
48
|
-
relative === "" ||
|
|
49
|
-
(!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function inspectExistingComponents(absolutePath: string): void {
|
|
54
|
-
const parsed = path.parse(absolutePath);
|
|
55
|
-
const relativeParts = absolutePath
|
|
56
|
-
.slice(parsed.root.length)
|
|
57
|
-
.split(path.sep)
|
|
58
|
-
.filter(Boolean);
|
|
59
|
-
let current = parsed.root;
|
|
60
|
-
for (const part of relativeParts) {
|
|
61
|
-
current = path.join(current, part);
|
|
62
|
-
let stat: ReturnType<typeof lstatSync>;
|
|
63
|
-
try {
|
|
64
|
-
stat = lstatSync(current);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
67
|
-
throw error;
|
|
68
|
-
}
|
|
69
|
-
if (stat.isSymbolicLink()) {
|
|
70
|
-
throw new StateRootError("state-component-symlink");
|
|
71
|
-
}
|
|
72
|
-
if (!stat.isDirectory() && current !== absolutePath) {
|
|
73
|
-
throw new StateRootError("state-component-type");
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function canonicalFuturePath(input: string): string {
|
|
79
|
-
const absolute = path.resolve(input);
|
|
80
|
-
inspectExistingComponents(absolute);
|
|
81
|
-
let cursor = absolute;
|
|
82
|
-
const missing: string[] = [];
|
|
83
|
-
for (;;) {
|
|
84
|
-
try {
|
|
85
|
-
return path.join(realpathSync.native(cursor), ...missing.reverse());
|
|
86
|
-
} catch (error) {
|
|
87
|
-
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
88
|
-
const parent = path.dirname(cursor);
|
|
89
|
-
if (parent === cursor) throw error;
|
|
90
|
-
missing.push(path.basename(cursor));
|
|
91
|
-
cursor = parent;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function requireAbsoluteDirectoryBase(
|
|
97
|
-
xdgStateHome: string | undefined,
|
|
98
|
-
homeDir: string,
|
|
99
|
-
): string {
|
|
100
|
-
if (xdgStateHome && path.isAbsolute(xdgStateHome)) {
|
|
101
|
-
return canonicalFuturePath(xdgStateHome);
|
|
102
|
-
}
|
|
103
|
-
if (!path.isAbsolute(homeDir)) {
|
|
104
|
-
throw new StateRootError("invalid-state-base");
|
|
105
|
-
}
|
|
106
|
-
return canonicalFuturePath(path.join(homeDir, ".local", "state"));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function resolveStateRoot(options: StateRootOptions): ResolvedStateRoot {
|
|
110
|
-
let consumerRoot: string;
|
|
111
|
-
try {
|
|
112
|
-
consumerRoot = realpathSync.native(path.resolve(options.consumerRoot));
|
|
113
|
-
if (!lstatSync(consumerRoot).isDirectory()) {
|
|
114
|
-
throw new StateRootError("consumer-root-unavailable");
|
|
115
|
-
}
|
|
116
|
-
} catch (error) {
|
|
117
|
-
if (error instanceof StateRootError) throw error;
|
|
118
|
-
throw new StateRootError("consumer-root-unavailable");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const stateBase = requireAbsoluteDirectoryBase(
|
|
122
|
-
options.xdgStateHome,
|
|
123
|
-
options.homeDir ?? os.homedir(),
|
|
124
|
-
);
|
|
125
|
-
const consumerRootHash = createHash("sha256")
|
|
126
|
-
.update(consumerRoot)
|
|
127
|
-
.digest("hex");
|
|
128
|
-
const productRoot = canonicalFuturePath(path.join(stateBase, "abel-cadence"));
|
|
129
|
-
const rootsDir = canonicalFuturePath(path.join(productRoot, "roots"));
|
|
130
|
-
const rootDir = canonicalFuturePath(path.join(rootsDir, consumerRootHash));
|
|
131
|
-
|
|
132
|
-
if (isContainedBy(consumerRoot, rootDir)) {
|
|
133
|
-
throw new StateRootError("state-root-inside-consumer");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
consumerRoot,
|
|
138
|
-
consumerRootHash,
|
|
139
|
-
stateBase,
|
|
140
|
-
productRoot,
|
|
141
|
-
rootsDir,
|
|
142
|
-
rootDir,
|
|
143
|
-
databasePath: path.join(rootDir, "control.sqlite3"),
|
|
144
|
-
artifactsDir: path.join(rootDir, "artifacts"),
|
|
145
|
-
workspacesDir: path.join(rootDir, "workspaces"),
|
|
146
|
-
transactionsDir: path.join(rootDir, "transactions"),
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function ensureDirectory(directory: string): void {
|
|
151
|
-
let stat: ReturnType<typeof lstatSync>;
|
|
152
|
-
try {
|
|
153
|
-
stat = lstatSync(directory);
|
|
154
|
-
} catch (error) {
|
|
155
|
-
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
156
|
-
const parent = path.dirname(directory);
|
|
157
|
-
if (parent !== directory) ensureDirectory(parent);
|
|
158
|
-
mkdirSync(directory, { mode: 0o700 });
|
|
159
|
-
chmodSync(directory, 0o700);
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
if (stat.isSymbolicLink()) {
|
|
163
|
-
throw new StateRootError("state-component-symlink");
|
|
164
|
-
}
|
|
165
|
-
if (!stat.isDirectory()) {
|
|
166
|
-
throw new StateRootError("state-component-type");
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export function prepareStateRoot(
|
|
171
|
-
resolved: ResolvedStateRoot,
|
|
172
|
-
): ResolvedStateRoot {
|
|
173
|
-
const checked = resolveStateRoot({
|
|
174
|
-
consumerRoot: resolved.consumerRoot,
|
|
175
|
-
xdgStateHome: resolved.stateBase,
|
|
176
|
-
homeDir: path.dirname(resolved.stateBase),
|
|
177
|
-
});
|
|
178
|
-
if (
|
|
179
|
-
checked.consumerRootHash !== resolved.consumerRootHash ||
|
|
180
|
-
checked.rootDir !== resolved.rootDir
|
|
181
|
-
) {
|
|
182
|
-
throw new StateRootError("invalid-state-base");
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
ensureDirectory(resolved.stateBase);
|
|
186
|
-
for (const directory of [
|
|
187
|
-
resolved.productRoot,
|
|
188
|
-
resolved.rootsDir,
|
|
189
|
-
resolved.rootDir,
|
|
190
|
-
resolved.artifactsDir,
|
|
191
|
-
resolved.workspacesDir,
|
|
192
|
-
resolved.transactionsDir,
|
|
193
|
-
]) {
|
|
194
|
-
ensureDirectory(directory);
|
|
195
|
-
chmodSync(directory, 0o700);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
const database = lstatSync(resolved.databasePath);
|
|
200
|
-
if (database.isSymbolicLink()) {
|
|
201
|
-
throw new StateRootError("state-component-symlink");
|
|
202
|
-
}
|
|
203
|
-
if (!database.isFile()) {
|
|
204
|
-
throw new StateRootError("state-component-type");
|
|
205
|
-
}
|
|
206
|
-
} catch (error) {
|
|
207
|
-
if (
|
|
208
|
-
error instanceof StateRootError ||
|
|
209
|
-
(error as NodeJS.ErrnoException).code !== "ENOENT"
|
|
210
|
-
) {
|
|
211
|
-
throw error;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return { ...resolved };
|
|
215
|
-
}
|