@hasna/todos 0.15.25 → 0.15.26
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/dist/cli/cloud-router.d.ts +7 -0
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/task-manifest-commands.d.ts +3 -0
- package/dist/cli/commands/task-manifest-commands.d.ts.map +1 -0
- package/dist/cli/index.js +776 -25
- package/dist/contracts.js +4 -2
- package/dist/index.js +13 -5
- package/dist/mcp/index.js +14 -3
- package/dist/mcp.js +4 -2
- package/dist/project-registration.js +4 -2
- package/dist/registry.js +4 -2
- package/dist/release-provenance.json +5 -5
- package/dist/server/index.js +78 -67
- package/dist/task-manifest/plan-slug.d.ts +11 -0
- package/dist/task-manifest/plan-slug.d.ts.map +1 -0
- package/dist/task-manifest/postgres.d.ts.map +1 -1
- package/dist/task-manifest/sqlite.d.ts.map +1 -1
- package/dist/task-manifest.js +9 -3
- package/package.json +4 -2
package/dist/cli/index.js
CHANGED
|
@@ -2123,7 +2123,7 @@ var package_default;
|
|
|
2123
2123
|
var init_package = __esm(() => {
|
|
2124
2124
|
package_default = {
|
|
2125
2125
|
name: "@hasna/todos",
|
|
2126
|
-
version: "0.15.
|
|
2126
|
+
version: "0.15.26",
|
|
2127
2127
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
2128
2128
|
type: "module",
|
|
2129
2129
|
main: "dist/index.js",
|
|
@@ -2172,7 +2172,8 @@ var init_package = __esm(() => {
|
|
|
2172
2172
|
}
|
|
2173
2173
|
},
|
|
2174
2174
|
workspaces: [
|
|
2175
|
-
"dashboard"
|
|
2175
|
+
"dashboard",
|
|
2176
|
+
"ai"
|
|
2176
2177
|
],
|
|
2177
2178
|
files: [
|
|
2178
2179
|
"dist",
|
|
@@ -2243,6 +2244,7 @@ var init_package = __esm(() => {
|
|
|
2243
2244
|
zod: "^3.24.2"
|
|
2244
2245
|
},
|
|
2245
2246
|
overrides: {
|
|
2247
|
+
ajv: "8.20.0",
|
|
2246
2248
|
"fast-uri": "3.1.2"
|
|
2247
2249
|
},
|
|
2248
2250
|
devDependencies: {
|
|
@@ -5788,6 +5790,39 @@ async function cloudPrGroupEvents(client, groupId, options = {}) {
|
|
|
5788
5790
|
throw new Error(`REMOTE_API_INCOMPATIBLE: ${route} did not return a complete closed authoritative remote event page; ` + "local SQLite fallback is disabled", { cause: error });
|
|
5789
5791
|
}
|
|
5790
5792
|
}
|
|
5793
|
+
function unwrapTaskManifestEnvelope(raw, key, route) {
|
|
5794
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw) || !(key in raw)) {
|
|
5795
|
+
throw new Error(`REMOTE_API_INCOMPATIBLE: ${route} returned a non-authoritative task-manifest response envelope; ` + "local SQLite fallback is disabled");
|
|
5796
|
+
}
|
|
5797
|
+
return raw[key];
|
|
5798
|
+
}
|
|
5799
|
+
async function cloudTaskManifestCapability(client) {
|
|
5800
|
+
const route = "/v1/task-manifest/capability";
|
|
5801
|
+
return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.get("/task-manifest/capability")), "capability", route);
|
|
5802
|
+
}
|
|
5803
|
+
async function cloudApplyTaskManifest(client, input) {
|
|
5804
|
+
const route = "/v1/task-manifest/apply";
|
|
5805
|
+
return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/apply", input)), "result", route);
|
|
5806
|
+
}
|
|
5807
|
+
async function cloudReadExactTaskManifest(client, receiptId) {
|
|
5808
|
+
const route = "/v1/task-manifest/read-exact";
|
|
5809
|
+
return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/read-exact", { receipt_id: receiptId })), "result", route);
|
|
5810
|
+
}
|
|
5811
|
+
async function cloudLookupTaskManifestBinding(client, input) {
|
|
5812
|
+
const route = "/v1/task-manifest/bindings/lookup";
|
|
5813
|
+
return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/bindings/lookup", input)), "result", route);
|
|
5814
|
+
}
|
|
5815
|
+
async function cloudCompensateTaskManifest(client, input) {
|
|
5816
|
+
const route = "/v1/task-manifest/compensate";
|
|
5817
|
+
return unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/compensate", input)), "result", route);
|
|
5818
|
+
}
|
|
5819
|
+
async function cloudMarkTaskManifestOutboxDelivered(client, outboxId) {
|
|
5820
|
+
const route = "/v1/task-manifest/outbox/delivered";
|
|
5821
|
+
const delivered = unwrapTaskManifestEnvelope(await requiredRemoteRoute(client, route, () => client.transport.post("/task-manifest/outbox/delivered", { outbox_id: outboxId })), "delivered", route);
|
|
5822
|
+
if (delivered !== true) {
|
|
5823
|
+
throw new Error(`REMOTE_API_INCOMPATIBLE: ${route} did not confirm task-manifest outbox delivery; ` + "local SQLite fallback is disabled");
|
|
5824
|
+
}
|
|
5825
|
+
}
|
|
5791
5826
|
function unwrapTask(raw) {
|
|
5792
5827
|
if (raw && typeof raw === "object" && "task" in raw) {
|
|
5793
5828
|
return raw.task;
|
|
@@ -7759,6 +7794,7 @@ var init_stage_a = __esm(() => {
|
|
|
7759
7794
|
"sync",
|
|
7760
7795
|
"tag",
|
|
7761
7796
|
"task",
|
|
7797
|
+
"task-manifest",
|
|
7762
7798
|
"template-export",
|
|
7763
7799
|
"template-history",
|
|
7764
7800
|
"template-import",
|
|
@@ -7884,6 +7920,7 @@ var init_stage_a = __esm(() => {
|
|
|
7884
7920
|
"task",
|
|
7885
7921
|
"task-lists",
|
|
7886
7922
|
"stale-lock-handoff",
|
|
7923
|
+
"task-manifest",
|
|
7887
7924
|
"template-export",
|
|
7888
7925
|
"template-import",
|
|
7889
7926
|
"template-preview",
|
|
@@ -46091,6 +46128,13 @@ var init_schema2 = __esm(() => {
|
|
|
46091
46128
|
}).strict();
|
|
46092
46129
|
});
|
|
46093
46130
|
|
|
46131
|
+
// src/task-manifest/plan-slug.ts
|
|
46132
|
+
function taskManifestPlanSlug(manifest, planId) {
|
|
46133
|
+
const base = normalizeSlug(manifest.plan.key) || normalizeSlug(manifest.plan.name) || "plan";
|
|
46134
|
+
return `${base}-${planId}`;
|
|
46135
|
+
}
|
|
46136
|
+
var init_plan_slug = () => {};
|
|
46137
|
+
|
|
46094
46138
|
// src/task-manifest/backend.ts
|
|
46095
46139
|
function validateTaskManifestBindingLookupRows(rows, tenantId, planId) {
|
|
46096
46140
|
if (rows.length === 0) {
|
|
@@ -46119,6 +46163,67 @@ var init_backend = __esm(() => {
|
|
|
46119
46163
|
});
|
|
46120
46164
|
|
|
46121
46165
|
// src/task-manifest/reference-guard.ts
|
|
46166
|
+
function quoteSqliteIdentifier2(value) {
|
|
46167
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
46168
|
+
}
|
|
46169
|
+
function placeholders3(count) {
|
|
46170
|
+
return Array.from({ length: count }, () => "?").join(",");
|
|
46171
|
+
}
|
|
46172
|
+
function sqliteManagedReferencePredicate(table, field, target, managed) {
|
|
46173
|
+
const taskPlaceholders = placeholders3(managed.task_ids.length);
|
|
46174
|
+
if (table === "tasks" && field === "plan_id" && target === "plans") {
|
|
46175
|
+
return {
|
|
46176
|
+
sql: `id IN (${taskPlaceholders})`,
|
|
46177
|
+
values: managed.task_ids
|
|
46178
|
+
};
|
|
46179
|
+
}
|
|
46180
|
+
if (table === "task_dependencies" && target === "tasks" && (field === "task_id" || field === "depends_on")) {
|
|
46181
|
+
return {
|
|
46182
|
+
sql: `task_id IN (${taskPlaceholders}) AND depends_on IN (${taskPlaceholders})`,
|
|
46183
|
+
values: [...managed.task_ids, ...managed.task_ids]
|
|
46184
|
+
};
|
|
46185
|
+
}
|
|
46186
|
+
if (table === "task_comments" && field === "task_id" && target === "tasks") {
|
|
46187
|
+
return managed.comment_ids.length > 0 ? { sql: `id IN (${placeholders3(managed.comment_ids.length)})`, values: managed.comment_ids } : { sql: "0", values: [] };
|
|
46188
|
+
}
|
|
46189
|
+
if (table === "task_verifications" && field === "task_id" && target === "tasks") {
|
|
46190
|
+
return managed.verification_ids.length > 0 ? { sql: `id IN (${placeholders3(managed.verification_ids.length)})`, values: managed.verification_ids } : { sql: "0", values: [] };
|
|
46191
|
+
}
|
|
46192
|
+
if (table === "task_tags" && field === "task_id" && target === "tasks") {
|
|
46193
|
+
return {
|
|
46194
|
+
sql: `task_id IN (${taskPlaceholders})`,
|
|
46195
|
+
values: managed.task_ids
|
|
46196
|
+
};
|
|
46197
|
+
}
|
|
46198
|
+
return null;
|
|
46199
|
+
}
|
|
46200
|
+
function findSqliteTaskManifestForeignReference(db, managed) {
|
|
46201
|
+
const tables = db.query(`SELECT name FROM sqlite_master
|
|
46202
|
+
WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`).all();
|
|
46203
|
+
for (const { name: table } of tables) {
|
|
46204
|
+
const foreignKeys = db.query(`PRAGMA foreign_key_list(${quoteSqliteIdentifier2(table)})`).all();
|
|
46205
|
+
for (const foreignKey of foreignKeys) {
|
|
46206
|
+
if (foreignKey.table !== "tasks" && foreignKey.table !== "plans")
|
|
46207
|
+
continue;
|
|
46208
|
+
const target = foreignKey.table;
|
|
46209
|
+
const targetIds = target === "tasks" ? managed.task_ids : [managed.plan_id];
|
|
46210
|
+
const owned = sqliteManagedReferencePredicate(table, foreignKey.from, target, managed);
|
|
46211
|
+
const sql = `SELECT 1 AS found FROM ${quoteSqliteIdentifier2(table)}
|
|
46212
|
+
WHERE ${quoteSqliteIdentifier2(foreignKey.from)} IN (${placeholders3(targetIds.length)})
|
|
46213
|
+
${owned ? `AND NOT (${owned.sql})` : ""}
|
|
46214
|
+
LIMIT 1`;
|
|
46215
|
+
if (db.query(sql).get(...targetIds, ...owned?.values ?? [])) {
|
|
46216
|
+
return {
|
|
46217
|
+
surface: table,
|
|
46218
|
+
field: foreignKey.from,
|
|
46219
|
+
target,
|
|
46220
|
+
on_delete: foreignKey.on_delete.toUpperCase()
|
|
46221
|
+
};
|
|
46222
|
+
}
|
|
46223
|
+
}
|
|
46224
|
+
}
|
|
46225
|
+
return null;
|
|
46226
|
+
}
|
|
46122
46227
|
function postgresTaskManifestForeignReferenceSql(tableName) {
|
|
46123
46228
|
return `SELECT object_type, object_id
|
|
46124
46229
|
FROM ${tableName}
|
|
@@ -46148,6 +46253,89 @@ function postgresTaskManifestForeignReferenceSql(tableName) {
|
|
|
46148
46253
|
function sqlString(value) {
|
|
46149
46254
|
return `'${value.replaceAll("'", "''")}'`;
|
|
46150
46255
|
}
|
|
46256
|
+
function sqliteTodosTaskManifestSchemaSql() {
|
|
46257
|
+
return `
|
|
46258
|
+
CREATE TABLE IF NOT EXISTS todos_task_manifest_receipts (
|
|
46259
|
+
receipt_id TEXT PRIMARY KEY,
|
|
46260
|
+
tenant_id TEXT NOT NULL,
|
|
46261
|
+
authority TEXT NOT NULL CHECK(authority = 'todos'),
|
|
46262
|
+
route TEXT NOT NULL,
|
|
46263
|
+
schema_version INTEGER NOT NULL CHECK(schema_version = 1),
|
|
46264
|
+
kind TEXT NOT NULL CHECK(kind IN ('apply', 'compensate')),
|
|
46265
|
+
operation_id TEXT NOT NULL,
|
|
46266
|
+
idempotency_key TEXT NOT NULL,
|
|
46267
|
+
request_digest TEXT NOT NULL,
|
|
46268
|
+
result_digest TEXT NOT NULL,
|
|
46269
|
+
binding_version INTEGER NOT NULL,
|
|
46270
|
+
apply_receipt_id TEXT,
|
|
46271
|
+
manifest_json TEXT,
|
|
46272
|
+
result_json TEXT NOT NULL,
|
|
46273
|
+
created_at TEXT NOT NULL,
|
|
46274
|
+
UNIQUE(kind, idempotency_key)
|
|
46275
|
+
);
|
|
46276
|
+
CREATE TABLE IF NOT EXISTS todos_task_manifest_bindings (
|
|
46277
|
+
operation_id TEXT PRIMARY KEY,
|
|
46278
|
+
tenant_id TEXT NOT NULL,
|
|
46279
|
+
idempotency_key TEXT NOT NULL UNIQUE,
|
|
46280
|
+
request_digest TEXT NOT NULL,
|
|
46281
|
+
result_digest TEXT NOT NULL,
|
|
46282
|
+
apply_receipt_id TEXT NOT NULL UNIQUE REFERENCES todos_task_manifest_receipts(receipt_id),
|
|
46283
|
+
manifest_json TEXT NOT NULL,
|
|
46284
|
+
result_json TEXT NOT NULL,
|
|
46285
|
+
state TEXT NOT NULL CHECK(state IN ('applied', 'compensated')),
|
|
46286
|
+
version INTEGER NOT NULL,
|
|
46287
|
+
compensation_receipt_id TEXT,
|
|
46288
|
+
created_at TEXT NOT NULL,
|
|
46289
|
+
updated_at TEXT NOT NULL
|
|
46290
|
+
);
|
|
46291
|
+
CREATE TABLE IF NOT EXISTS todos_task_manifest_outbox (
|
|
46292
|
+
id TEXT PRIMARY KEY,
|
|
46293
|
+
apply_receipt_id TEXT NOT NULL REFERENCES todos_task_manifest_receipts(receipt_id),
|
|
46294
|
+
topic TEXT NOT NULL,
|
|
46295
|
+
payload TEXT NOT NULL,
|
|
46296
|
+
payload_digest TEXT NOT NULL,
|
|
46297
|
+
status TEXT NOT NULL CHECK(status IN ('pending', 'delivered', 'cancelled')),
|
|
46298
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
46299
|
+
created_at TEXT NOT NULL,
|
|
46300
|
+
delivered_at TEXT
|
|
46301
|
+
);
|
|
46302
|
+
CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_outbox_receipt
|
|
46303
|
+
ON todos_task_manifest_outbox(apply_receipt_id, status);
|
|
46304
|
+
CREATE TRIGGER IF NOT EXISTS todos_task_manifest_receipts_immutable_update
|
|
46305
|
+
BEFORE UPDATE ON todos_task_manifest_receipts BEGIN
|
|
46306
|
+
SELECT RAISE(ABORT, 'todos task manifest receipts are immutable');
|
|
46307
|
+
END;
|
|
46308
|
+
CREATE TRIGGER IF NOT EXISTS todos_task_manifest_receipts_immutable_delete
|
|
46309
|
+
BEFORE DELETE ON todos_task_manifest_receipts BEGIN
|
|
46310
|
+
SELECT RAISE(ABORT, 'todos task manifest receipts are immutable');
|
|
46311
|
+
END;
|
|
46312
|
+
`;
|
|
46313
|
+
}
|
|
46314
|
+
function sqliteTableHasColumn(db, tableName, columnName) {
|
|
46315
|
+
const columns = db.query(`PRAGMA table_info("${tableName}")`).all();
|
|
46316
|
+
return columns.some((column) => column.name === columnName);
|
|
46317
|
+
}
|
|
46318
|
+
function ensureSqliteTodosTaskManifestSchema(db, tenantId) {
|
|
46319
|
+
db.exec(sqliteTodosTaskManifestSchemaSql());
|
|
46320
|
+
const tenantDefault = sqlString(tenantId);
|
|
46321
|
+
for (const tableName of [
|
|
46322
|
+
"todos_task_manifest_receipts",
|
|
46323
|
+
"todos_task_manifest_bindings"
|
|
46324
|
+
]) {
|
|
46325
|
+
if (!sqliteTableHasColumn(db, tableName, "tenant_id")) {
|
|
46326
|
+
db.exec(`ALTER TABLE "${tableName}" ADD COLUMN tenant_id TEXT NOT NULL DEFAULT ${tenantDefault}`);
|
|
46327
|
+
}
|
|
46328
|
+
}
|
|
46329
|
+
db.exec(`
|
|
46330
|
+
CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_receipts_tenant
|
|
46331
|
+
ON todos_task_manifest_receipts(tenant_id, receipt_id, kind);
|
|
46332
|
+
CREATE INDEX IF NOT EXISTS idx_todos_task_manifest_bindings_tenant_plan
|
|
46333
|
+
ON todos_task_manifest_bindings(
|
|
46334
|
+
tenant_id,
|
|
46335
|
+
json_extract(result_json, '$.graph.plan_id')
|
|
46336
|
+
);
|
|
46337
|
+
`);
|
|
46338
|
+
}
|
|
46151
46339
|
function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
|
|
46152
46340
|
const tenantDefault = sqlString(tenantId);
|
|
46153
46341
|
return [
|
|
@@ -46224,9 +46412,411 @@ function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
|
|
|
46224
46412
|
}
|
|
46225
46413
|
|
|
46226
46414
|
// src/task-manifest/sqlite.ts
|
|
46415
|
+
function fault(faults, point) {
|
|
46416
|
+
if (faults.points.has(point))
|
|
46417
|
+
throw new Error(`Injected task-manifest fault at ${point}`);
|
|
46418
|
+
}
|
|
46419
|
+
function parseApplyResult(value, duplicate) {
|
|
46420
|
+
return { ...JSON.parse(value), duplicate };
|
|
46421
|
+
}
|
|
46422
|
+
|
|
46423
|
+
class SqliteTodosTaskManifestBackend {
|
|
46424
|
+
db;
|
|
46425
|
+
tenantId;
|
|
46426
|
+
kind = "sqlite";
|
|
46427
|
+
constructor(db, tenantId = "default") {
|
|
46428
|
+
this.db = db;
|
|
46429
|
+
this.tenantId = tenantId;
|
|
46430
|
+
ensureSqliteTodosTaskManifestSchema(db, tenantId);
|
|
46431
|
+
}
|
|
46432
|
+
async serialized(run) {
|
|
46433
|
+
const previous = sqliteTails.get(this.db) ?? Promise.resolve();
|
|
46434
|
+
let release;
|
|
46435
|
+
const current = new Promise((resolve17) => {
|
|
46436
|
+
release = resolve17;
|
|
46437
|
+
});
|
|
46438
|
+
const tail = previous.then(() => current);
|
|
46439
|
+
sqliteTails.set(this.db, tail);
|
|
46440
|
+
await previous;
|
|
46441
|
+
try {
|
|
46442
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
46443
|
+
try {
|
|
46444
|
+
const result = run();
|
|
46445
|
+
this.db.exec("COMMIT");
|
|
46446
|
+
return result;
|
|
46447
|
+
} catch (error) {
|
|
46448
|
+
this.db.exec("ROLLBACK");
|
|
46449
|
+
throw error;
|
|
46450
|
+
}
|
|
46451
|
+
} finally {
|
|
46452
|
+
release();
|
|
46453
|
+
if (sqliteTails.get(this.db) === tail)
|
|
46454
|
+
sqliteTails.delete(this.db);
|
|
46455
|
+
}
|
|
46456
|
+
}
|
|
46457
|
+
async apply(input, faults) {
|
|
46458
|
+
return this.serialized(() => {
|
|
46459
|
+
const { manifest } = input;
|
|
46460
|
+
const binding = this.db.query("SELECT * FROM todos_task_manifest_bindings WHERE tenant_id = ? AND operation_id = ? LIMIT 1").get(this.tenantId, manifest.operation_id);
|
|
46461
|
+
if (binding) {
|
|
46462
|
+
if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest) {
|
|
46463
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Operation is already bound to a different request");
|
|
46464
|
+
}
|
|
46465
|
+
if (binding["state"] !== "applied") {
|
|
46466
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", "Operation was already compensated");
|
|
46467
|
+
}
|
|
46468
|
+
return parseApplyResult(String(binding["result_json"]), true);
|
|
46469
|
+
}
|
|
46470
|
+
const idempotency = this.db.query("SELECT operation_id, request_digest FROM todos_task_manifest_bindings WHERE tenant_id = ? AND idempotency_key = ? LIMIT 1").get(this.tenantId, manifest.idempotency_key);
|
|
46471
|
+
if (idempotency)
|
|
46472
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Idempotency key is already used by another operation");
|
|
46473
|
+
if (manifest.if_binding_version !== undefined && manifest.if_binding_version !== 0) {
|
|
46474
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "New manifest binding version must be 0");
|
|
46475
|
+
}
|
|
46476
|
+
if (!this.db.query("SELECT 1 AS found FROM projects WHERE id = ? LIMIT 1").get(manifest.project_id)) {
|
|
46477
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", "Project does not exist");
|
|
46478
|
+
}
|
|
46479
|
+
if (manifest.task_list_id && !this.db.query("SELECT 1 AS found FROM task_lists WHERE id = ? AND project_id = ? LIMIT 1").get(manifest.task_list_id, manifest.project_id)) {
|
|
46480
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_FOREIGN_REFERENCE", "Task list does not belong to the project");
|
|
46481
|
+
}
|
|
46482
|
+
const allIds = [input.graph.plan_id, ...Object.values(input.graph.task_ids), ...input.graph.comment_ids, ...input.graph.verification_ids];
|
|
46483
|
+
for (const id of allIds) {
|
|
46484
|
+
for (const table of ["plans", "tasks", "task_comments", "task_verifications"]) {
|
|
46485
|
+
if (this.db.query(`SELECT 1 AS found FROM ${table} WHERE id = ? LIMIT 1`).get(id)) {
|
|
46486
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Deterministic id already exists: ${id}`);
|
|
46487
|
+
}
|
|
46488
|
+
}
|
|
46489
|
+
}
|
|
46490
|
+
this.db.query(`INSERT INTO plans (id, project_id, name, description, status, task_list_id, slug, created_at, updated_at)
|
|
46491
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.graph.plan_id, manifest.project_id, manifest.plan.name, manifest.plan.description ?? null, manifest.plan.status ?? "active", manifest.task_list_id ?? null, taskManifestPlanSlug(manifest, input.graph.plan_id), input.now, input.now);
|
|
46492
|
+
fault(faults, "after_plan_write");
|
|
46493
|
+
for (const task2 of manifest.tasks) {
|
|
46494
|
+
const taskId = input.graph.task_ids[task2.key];
|
|
46495
|
+
this.db.query(`INSERT INTO tasks (
|
|
46496
|
+
id, project_id, title, description, status, priority, assigned_to, tags, metadata,
|
|
46497
|
+
version, plan_id, task_list_id, created_at, updated_at, created_by
|
|
46498
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?)`).run(taskId, manifest.project_id, task2.title, task2.description ?? null, task2.status ?? "pending", task2.priority ?? "medium", task2.assigned_to ?? null, JSON.stringify(task2.tags ?? []), canonicalJson2(task2.metadata ?? {}), input.graph.plan_id, manifest.task_list_id ?? null, input.now, input.now, task2.created_by ?? null);
|
|
46499
|
+
for (const tag of [...new Set(task2.tags ?? [])].sort()) {
|
|
46500
|
+
this.db.query("INSERT INTO task_tags (task_id, tag) VALUES (?, ?)").run(taskId, tag);
|
|
46501
|
+
}
|
|
46502
|
+
}
|
|
46503
|
+
fault(faults, "after_task_write");
|
|
46504
|
+
for (const edge of manifest.dependencies ?? []) {
|
|
46505
|
+
this.db.query("INSERT INTO task_dependencies (task_id, depends_on) VALUES (?, ?)").run(input.graph.task_ids[edge.task], input.graph.task_ids[edge.depends_on]);
|
|
46506
|
+
}
|
|
46507
|
+
fault(faults, "after_dependency_write");
|
|
46508
|
+
let commentIndex = 0;
|
|
46509
|
+
for (const task2 of manifest.tasks)
|
|
46510
|
+
for (const comment2 of task2.comments ?? []) {
|
|
46511
|
+
this.db.query(`INSERT INTO task_comments (id, task_id, agent_id, session_id, content, created_at, type, progress_pct)
|
|
46512
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(input.graph.comment_ids[commentIndex++], input.graph.task_ids[task2.key], comment2.agent_id ?? null, comment2.session_id ?? null, comment2.content, input.now, comment2.type ?? "comment", comment2.progress_pct ?? null);
|
|
46513
|
+
}
|
|
46514
|
+
fault(faults, "after_comment_write");
|
|
46515
|
+
let verificationIndex = 0;
|
|
46516
|
+
for (const task2 of manifest.tasks)
|
|
46517
|
+
for (const verification2 of task2.verifications ?? []) {
|
|
46518
|
+
this.db.query(`INSERT INTO task_verifications (
|
|
46519
|
+
id, task_id, command, status, output_summary, artifact_path, agent_id, run_at, created_at
|
|
46520
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.graph.verification_ids[verificationIndex++], input.graph.task_ids[task2.key], verification2.command, verification2.status ?? "unknown", verification2.output_summary ?? null, verification2.artifact_path ?? null, verification2.agent_id ?? null, input.now, input.now);
|
|
46521
|
+
}
|
|
46522
|
+
fault(faults, "after_verification_write");
|
|
46523
|
+
const readback = this.readback(input.graph);
|
|
46524
|
+
const expected = {
|
|
46525
|
+
plans: 1,
|
|
46526
|
+
tasks: manifest.tasks.length,
|
|
46527
|
+
dependencies: manifest.dependencies?.length ?? 0,
|
|
46528
|
+
comments: input.graph.comment_ids.length,
|
|
46529
|
+
verifications: input.graph.verification_ids.length,
|
|
46530
|
+
complete: true
|
|
46531
|
+
};
|
|
46532
|
+
if (canonicalJson2(readback) !== canonicalJson2(expected)) {
|
|
46533
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_READBACK_MISMATCH", "Exact graph readback did not match", { expected, readback });
|
|
46534
|
+
}
|
|
46535
|
+
const receipt = {
|
|
46536
|
+
receipt_id: input.receipt_id,
|
|
46537
|
+
authority: "todos",
|
|
46538
|
+
route: "todos.task-manifest.v1",
|
|
46539
|
+
schema_version: 1,
|
|
46540
|
+
kind: "apply",
|
|
46541
|
+
operation_id: manifest.operation_id,
|
|
46542
|
+
idempotency_key: manifest.idempotency_key,
|
|
46543
|
+
request_digest: input.request_digest,
|
|
46544
|
+
result_digest: input.result_digest,
|
|
46545
|
+
binding_version: 1,
|
|
46546
|
+
apply_receipt_id: null,
|
|
46547
|
+
created_at: input.now
|
|
46548
|
+
};
|
|
46549
|
+
const result = {
|
|
46550
|
+
duplicate: false,
|
|
46551
|
+
receipt,
|
|
46552
|
+
graph: input.graph,
|
|
46553
|
+
readback,
|
|
46554
|
+
outbox_ids: input.outbox.map((entry2) => entry2.id),
|
|
46555
|
+
result_digest: input.result_digest
|
|
46556
|
+
};
|
|
46557
|
+
const resultJson2 = canonicalJson2(result);
|
|
46558
|
+
const manifestJson = canonicalJson2(manifest);
|
|
46559
|
+
this.db.query(`INSERT INTO todos_task_manifest_receipts (
|
|
46560
|
+
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
|
|
46561
|
+
request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
46562
|
+
) VALUES (?, ?, 'todos', 'todos.task-manifest.v1', 1, 'apply', ?, ?, ?, ?, 1, NULL, ?, ?, ?)`).run(input.receipt_id, this.tenantId, manifest.operation_id, manifest.idempotency_key, input.request_digest, input.result_digest, manifestJson, resultJson2, input.now);
|
|
46563
|
+
for (const entry2 of input.outbox) {
|
|
46564
|
+
this.db.query(`INSERT INTO todos_task_manifest_outbox (
|
|
46565
|
+
id, apply_receipt_id, topic, payload, payload_digest, status, created_at
|
|
46566
|
+
) VALUES (?, ?, ?, ?, ?, 'pending', ?)`).run(entry2.id, input.receipt_id, entry2.topic, canonicalJson2(entry2.payload), entry2.digest, input.now);
|
|
46567
|
+
}
|
|
46568
|
+
fault(faults, "after_outbox_write");
|
|
46569
|
+
this.db.query(`INSERT INTO todos_task_manifest_bindings (
|
|
46570
|
+
operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
|
|
46571
|
+
manifest_json, result_json, state, version, created_at, updated_at
|
|
46572
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'applied', 1, ?, ?)`).run(manifest.operation_id, this.tenantId, manifest.idempotency_key, input.request_digest, input.result_digest, input.receipt_id, manifestJson, resultJson2, input.now, input.now);
|
|
46573
|
+
fault(faults, "after_receipt_write");
|
|
46574
|
+
return result;
|
|
46575
|
+
});
|
|
46576
|
+
}
|
|
46577
|
+
async readExact(receiptId3) {
|
|
46578
|
+
const row = this.db.query("SELECT result_json FROM todos_task_manifest_receipts WHERE tenant_id = ? AND receipt_id = ? AND kind = 'apply' LIMIT 1").get(this.tenantId, receiptId3);
|
|
46579
|
+
if (!row)
|
|
46580
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", `Apply receipt not found: ${receiptId3}`);
|
|
46581
|
+
return parseApplyResult(row.result_json, false);
|
|
46582
|
+
}
|
|
46583
|
+
async lookupBindingByPlanId(planId) {
|
|
46584
|
+
const rows = this.db.query(`
|
|
46585
|
+
SELECT
|
|
46586
|
+
b.apply_receipt_id AS apply_receipt_id,
|
|
46587
|
+
b.state AS state,
|
|
46588
|
+
b.version AS binding_version,
|
|
46589
|
+
b.tenant_id AS binding_tenant_id,
|
|
46590
|
+
b.operation_id AS binding_operation_id,
|
|
46591
|
+
json_extract(b.result_json, '$.graph.plan_id') AS binding_plan_id,
|
|
46592
|
+
r.tenant_id AS receipt_tenant_id,
|
|
46593
|
+
r.authority AS receipt_authority,
|
|
46594
|
+
r.route AS receipt_route,
|
|
46595
|
+
r.schema_version AS receipt_schema_version,
|
|
46596
|
+
r.kind AS receipt_kind,
|
|
46597
|
+
r.operation_id AS receipt_operation_id,
|
|
46598
|
+
json_extract(r.result_json, '$.graph.plan_id') AS receipt_plan_id
|
|
46599
|
+
FROM todos_task_manifest_bindings b
|
|
46600
|
+
LEFT JOIN todos_task_manifest_receipts r
|
|
46601
|
+
ON r.receipt_id = b.apply_receipt_id
|
|
46602
|
+
AND r.tenant_id = b.tenant_id
|
|
46603
|
+
WHERE b.tenant_id = ?
|
|
46604
|
+
AND json_extract(b.result_json, '$.graph.plan_id') = ?
|
|
46605
|
+
LIMIT 2
|
|
46606
|
+
`).all(this.tenantId, planId);
|
|
46607
|
+
return validateTaskManifestBindingLookupRows(rows, this.tenantId, planId);
|
|
46608
|
+
}
|
|
46609
|
+
async markOutboxDelivered(outboxId, deliveredAt) {
|
|
46610
|
+
await this.serialized(() => {
|
|
46611
|
+
const result = this.db.query(`UPDATE todos_task_manifest_outbox
|
|
46612
|
+
SET status = 'delivered', delivered_at = ?, attempts = attempts + 1
|
|
46613
|
+
WHERE id = ? AND status = 'pending'
|
|
46614
|
+
AND EXISTS (
|
|
46615
|
+
SELECT 1 FROM todos_task_manifest_receipts r
|
|
46616
|
+
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
46617
|
+
AND r.tenant_id = ?
|
|
46618
|
+
AND r.authority = 'todos'
|
|
46619
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
46620
|
+
AND r.schema_version = 1
|
|
46621
|
+
AND r.kind = 'apply'
|
|
46622
|
+
)`).run(deliveredAt, outboxId, this.tenantId);
|
|
46623
|
+
if (result.changes === 1)
|
|
46624
|
+
return;
|
|
46625
|
+
const existing = this.db.query(`SELECT o.status
|
|
46626
|
+
FROM todos_task_manifest_outbox o
|
|
46627
|
+
JOIN todos_task_manifest_receipts r
|
|
46628
|
+
ON r.receipt_id = o.apply_receipt_id
|
|
46629
|
+
WHERE r.tenant_id = ?
|
|
46630
|
+
AND r.authority = 'todos'
|
|
46631
|
+
AND r.route = 'todos.task-manifest.v1'
|
|
46632
|
+
AND r.schema_version = 1
|
|
46633
|
+
AND r.kind = 'apply'
|
|
46634
|
+
AND o.id = ?
|
|
46635
|
+
LIMIT 1`).get(this.tenantId, outboxId);
|
|
46636
|
+
if (existing?.status === "delivered")
|
|
46637
|
+
return;
|
|
46638
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", `Pending outbox row not found: ${outboxId}`);
|
|
46639
|
+
});
|
|
46640
|
+
}
|
|
46641
|
+
async compensate(input, receipt, compensationReceiptId, requestDigest, now4) {
|
|
46642
|
+
return this.serialized(() => {
|
|
46643
|
+
const existing = this.db.query(`SELECT apply_receipt_id, request_digest, result_json
|
|
46644
|
+
FROM todos_task_manifest_receipts
|
|
46645
|
+
WHERE tenant_id = ? AND kind = 'compensate' AND idempotency_key = ?
|
|
46646
|
+
LIMIT 1`).get(this.tenantId, input.idempotency_key);
|
|
46647
|
+
if (existing) {
|
|
46648
|
+
if (existing.apply_receipt_id !== input.receipt_id || existing.request_digest !== requestDigest) {
|
|
46649
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Compensation idempotency key is already used");
|
|
46650
|
+
}
|
|
46651
|
+
return { ...JSON.parse(existing.result_json), duplicate: true };
|
|
46652
|
+
}
|
|
46653
|
+
const row = this.db.query("SELECT * FROM todos_task_manifest_receipts WHERE tenant_id = ? AND receipt_id = ? AND kind = 'apply' LIMIT 1").get(this.tenantId, input.receipt_id);
|
|
46654
|
+
if (!row)
|
|
46655
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", "Apply receipt not found");
|
|
46656
|
+
const binding = this.db.query("SELECT * FROM todos_task_manifest_bindings WHERE tenant_id = ? AND operation_id = ? LIMIT 1").get(this.tenantId, String(row["operation_id"]));
|
|
46657
|
+
if (!binding || Number(binding["version"]) !== input.if_binding_version) {
|
|
46658
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding version changed before compensation");
|
|
46659
|
+
}
|
|
46660
|
+
if (binding["state"] !== "applied")
|
|
46661
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not in applied state");
|
|
46662
|
+
const delivered = this.db.query(`SELECT o.id FROM todos_task_manifest_outbox o
|
|
46663
|
+
JOIN todos_task_manifest_receipts r ON r.receipt_id = o.apply_receipt_id
|
|
46664
|
+
WHERE r.tenant_id = ? AND o.apply_receipt_id = ? AND o.status = 'delivered'
|
|
46665
|
+
LIMIT 1`).get(this.tenantId, input.receipt_id);
|
|
46666
|
+
if (delivered)
|
|
46667
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
|
|
46668
|
+
const applyResult = parseApplyResult(String(row["result_json"]), false);
|
|
46669
|
+
const manifest = JSON.parse(String(row["manifest_json"]));
|
|
46670
|
+
const expectedEffects = [
|
|
46671
|
+
{
|
|
46672
|
+
topic: "todos.task-manifest.applied",
|
|
46673
|
+
payload: { operation_id: manifest.operation_id, project_id: manifest.project_id }
|
|
46674
|
+
},
|
|
46675
|
+
...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
|
|
46676
|
+
];
|
|
46677
|
+
const storedOutbox = this.db.query(`SELECT id, topic, payload, payload_digest, status, attempts, delivered_at
|
|
46678
|
+
FROM todos_task_manifest_outbox
|
|
46679
|
+
WHERE apply_receipt_id = ?
|
|
46680
|
+
AND EXISTS (
|
|
46681
|
+
SELECT 1 FROM todos_task_manifest_receipts r
|
|
46682
|
+
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
46683
|
+
AND r.tenant_id = ?
|
|
46684
|
+
)
|
|
46685
|
+
ORDER BY id`).all(input.receipt_id, this.tenantId);
|
|
46686
|
+
if (storedOutbox.length !== expectedEffects.length) {
|
|
46687
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
|
|
46688
|
+
}
|
|
46689
|
+
const outboxById = new Map(storedOutbox.map((entry2) => [String(entry2["id"]), entry2]));
|
|
46690
|
+
for (const [index, expectedEffect] of expectedEffects.entries()) {
|
|
46691
|
+
const stored = outboxById.get(applyResult.outbox_ids[index]);
|
|
46692
|
+
const expectedPayload = canonicalJson2(expectedEffect.payload);
|
|
46693
|
+
if (!stored || stored["topic"] !== expectedEffect.topic || stored["payload"] !== expectedPayload || stored["payload_digest"] !== canonicalDigest(expectedEffect) || stored["status"] !== "pending" || Number(stored["attempts"]) !== 0 || stored["delivered_at"] !== null) {
|
|
46694
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: outbox changed since apply");
|
|
46695
|
+
}
|
|
46696
|
+
}
|
|
46697
|
+
const taskIds = Object.values(applyResult.graph.task_ids);
|
|
46698
|
+
const placeholders4 = taskIds.map(() => "?").join(",");
|
|
46699
|
+
const foreignReference = findSqliteTaskManifestForeignReference(this.db, {
|
|
46700
|
+
plan_id: applyResult.graph.plan_id,
|
|
46701
|
+
task_ids: taskIds,
|
|
46702
|
+
dependency_ids: applyResult.graph.dependency_ids,
|
|
46703
|
+
comment_ids: applyResult.graph.comment_ids,
|
|
46704
|
+
verification_ids: applyResult.graph.verification_ids
|
|
46705
|
+
});
|
|
46706
|
+
if (foreignReference) {
|
|
46707
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", `Compensation refused: foreign reference at ${foreignReference.surface}.${foreignReference.field} would be changed by ${foreignReference.on_delete}`, { ...foreignReference });
|
|
46708
|
+
}
|
|
46709
|
+
const actualReadback = this.readback(applyResult.graph);
|
|
46710
|
+
if (canonicalJson2(actualReadback) !== canonicalJson2(applyResult.readback)) {
|
|
46711
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: graph changed since apply", { actualReadback });
|
|
46712
|
+
}
|
|
46713
|
+
const plan = this.db.query("SELECT project_id, name, description, status, task_list_id, slug FROM plans WHERE id = ? LIMIT 1").get(applyResult.graph.plan_id);
|
|
46714
|
+
if (!plan || plan["project_id"] !== manifest.project_id || plan["name"] !== manifest.plan.name || plan["description"] !== (manifest.plan.description ?? null) || plan["status"] !== (manifest.plan.status ?? "active") || plan["task_list_id"] !== (manifest.task_list_id ?? null) || plan["slug"] !== taskManifestPlanSlug(manifest, applyResult.graph.plan_id)) {
|
|
46715
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: plan changed since apply");
|
|
46716
|
+
}
|
|
46717
|
+
for (const task2 of manifest.tasks) {
|
|
46718
|
+
const stored = this.db.query("SELECT title, description, status, priority, assigned_to, tags, metadata, created_by FROM tasks WHERE id = ? LIMIT 1").get(applyResult.graph.task_ids[task2.key]);
|
|
46719
|
+
if (!stored || stored["title"] !== task2.title || stored["description"] !== (task2.description ?? null) || stored["status"] !== (task2.status ?? "pending") || stored["priority"] !== (task2.priority ?? "medium") || stored["assigned_to"] !== (task2.assigned_to ?? null) || stored["tags"] !== JSON.stringify(task2.tags ?? []) || stored["metadata"] !== canonicalJson2(task2.metadata ?? {}) || stored["created_by"] !== (task2.created_by ?? null)) {
|
|
46720
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", `Compensation refused: task ${task2.key} changed since apply`);
|
|
46721
|
+
}
|
|
46722
|
+
const storedTags = this.db.query("SELECT tag FROM task_tags WHERE task_id = ? ORDER BY tag").all(applyResult.graph.task_ids[task2.key]).map((row2) => row2.tag);
|
|
46723
|
+
const expectedTags = [...new Set(task2.tags ?? [])].sort();
|
|
46724
|
+
if (canonicalJson2(storedTags) !== canonicalJson2(expectedTags)) {
|
|
46725
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", `Compensation refused: task ${task2.key} tags changed since apply`);
|
|
46726
|
+
}
|
|
46727
|
+
}
|
|
46728
|
+
const storedDependencies = this.db.query(`SELECT task_id, depends_on FROM task_dependencies
|
|
46729
|
+
WHERE task_id IN (${placeholders4}) AND depends_on IN (${placeholders4}) ORDER BY task_id, depends_on`).all(...taskIds, ...taskIds);
|
|
46730
|
+
const expectedDependencies = (manifest.dependencies ?? []).map((edge) => ({
|
|
46731
|
+
task_id: applyResult.graph.task_ids[edge.task],
|
|
46732
|
+
depends_on: applyResult.graph.task_ids[edge.depends_on]
|
|
46733
|
+
})).sort((left, right) => left.task_id.localeCompare(right.task_id) || left.depends_on.localeCompare(right.depends_on));
|
|
46734
|
+
if (canonicalJson2(storedDependencies) !== canonicalJson2(expectedDependencies)) {
|
|
46735
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: dependencies changed since apply");
|
|
46736
|
+
}
|
|
46737
|
+
let expectedCommentIndex = 0;
|
|
46738
|
+
for (const task2 of manifest.tasks)
|
|
46739
|
+
for (const comment2 of task2.comments ?? []) {
|
|
46740
|
+
const stored = this.db.query(`SELECT task_id, agent_id, session_id, content, type, progress_pct
|
|
46741
|
+
FROM task_comments WHERE id = ? LIMIT 1`).get(applyResult.graph.comment_ids[expectedCommentIndex]);
|
|
46742
|
+
const expected = {
|
|
46743
|
+
task_id: applyResult.graph.task_ids[task2.key],
|
|
46744
|
+
agent_id: comment2.agent_id ?? null,
|
|
46745
|
+
session_id: comment2.session_id ?? null,
|
|
46746
|
+
content: comment2.content,
|
|
46747
|
+
type: comment2.type ?? "comment",
|
|
46748
|
+
progress_pct: comment2.progress_pct ?? null
|
|
46749
|
+
};
|
|
46750
|
+
if (!stored || canonicalJson2(stored) !== canonicalJson2(expected)) {
|
|
46751
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: comment changed since apply");
|
|
46752
|
+
}
|
|
46753
|
+
expectedCommentIndex += 1;
|
|
46754
|
+
}
|
|
46755
|
+
let expectedVerificationIndex = 0;
|
|
46756
|
+
for (const task2 of manifest.tasks)
|
|
46757
|
+
for (const verification2 of task2.verifications ?? []) {
|
|
46758
|
+
const stored = this.db.query(`SELECT task_id, command, status, output_summary, artifact_path, agent_id
|
|
46759
|
+
FROM task_verifications WHERE id = ? LIMIT 1`).get(applyResult.graph.verification_ids[expectedVerificationIndex]);
|
|
46760
|
+
const expected = {
|
|
46761
|
+
task_id: applyResult.graph.task_ids[task2.key],
|
|
46762
|
+
command: verification2.command,
|
|
46763
|
+
status: verification2.status ?? "unknown",
|
|
46764
|
+
output_summary: verification2.output_summary ?? null,
|
|
46765
|
+
artifact_path: verification2.artifact_path ?? null,
|
|
46766
|
+
agent_id: verification2.agent_id ?? null
|
|
46767
|
+
};
|
|
46768
|
+
if (!stored || canonicalJson2(stored) !== canonicalJson2(expected)) {
|
|
46769
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: verification changed since apply");
|
|
46770
|
+
}
|
|
46771
|
+
expectedVerificationIndex += 1;
|
|
46772
|
+
}
|
|
46773
|
+
this.db.query(`UPDATE todos_task_manifest_outbox
|
|
46774
|
+
SET status = 'cancelled'
|
|
46775
|
+
WHERE apply_receipt_id = ? AND status = 'pending'
|
|
46776
|
+
AND EXISTS (
|
|
46777
|
+
SELECT 1 FROM todos_task_manifest_receipts r
|
|
46778
|
+
WHERE r.receipt_id = todos_task_manifest_outbox.apply_receipt_id
|
|
46779
|
+
AND r.tenant_id = ?
|
|
46780
|
+
)`).run(input.receipt_id, this.tenantId);
|
|
46781
|
+
for (const table of ["task_tags", "task_dependencies", "task_comments", "task_verifications"]) {
|
|
46782
|
+
const column = table === "task_dependencies" ? "task_id" : "task_id";
|
|
46783
|
+
this.db.query(`DELETE FROM ${table} WHERE ${column} IN (${placeholders4})`).run(...taskIds);
|
|
46784
|
+
}
|
|
46785
|
+
this.db.query(`DELETE FROM tasks WHERE id IN (${placeholders4})`).run(...taskIds);
|
|
46786
|
+
this.db.query("DELETE FROM plans WHERE id = ?").run(applyResult.graph.plan_id);
|
|
46787
|
+
const readback = this.readback(applyResult.graph);
|
|
46788
|
+
const result = { duplicate: false, receipt, absent: true, readback };
|
|
46789
|
+
const resultJson2 = canonicalJson2(result);
|
|
46790
|
+
this.db.query(`INSERT INTO todos_task_manifest_receipts (
|
|
46791
|
+
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
|
|
46792
|
+
request_digest, result_digest, binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
46793
|
+
) VALUES (?, ?, 'todos', 'todos.task-manifest.v1', 1, 'compensate', ?, ?, ?, ?, ?, ?, NULL, ?, ?)`).run(compensationReceiptId, this.tenantId, receipt.operation_id, input.idempotency_key, requestDigest, receipt.result_digest, receipt.binding_version, input.receipt_id, resultJson2, now4);
|
|
46794
|
+
const updated = this.db.query(`UPDATE todos_task_manifest_bindings SET state = 'compensated', version = ?, compensation_receipt_id = ?, updated_at = ?
|
|
46795
|
+
WHERE tenant_id = ? AND operation_id = ? AND state = 'applied' AND version = ?`).run(receipt.binding_version, compensationReceiptId, now4, this.tenantId, receipt.operation_id, input.if_binding_version);
|
|
46796
|
+
if (updated.changes !== 1) {
|
|
46797
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding changed during compensation");
|
|
46798
|
+
}
|
|
46799
|
+
return result;
|
|
46800
|
+
});
|
|
46801
|
+
}
|
|
46802
|
+
readback(graph) {
|
|
46803
|
+
const taskIds = Object.values(graph.task_ids);
|
|
46804
|
+
const placeholders4 = taskIds.map(() => "?").join(",");
|
|
46805
|
+
const count = (sql, ...values) => Number(this.db.query(sql).get(...values).count);
|
|
46806
|
+
return {
|
|
46807
|
+
plans: count("SELECT count(*) AS count FROM plans WHERE id = ?", graph.plan_id),
|
|
46808
|
+
tasks: count(`SELECT count(*) AS count FROM tasks WHERE id IN (${placeholders4})`, ...taskIds),
|
|
46809
|
+
dependencies: count(`SELECT count(*) AS count FROM task_dependencies WHERE task_id IN (${placeholders4}) AND depends_on IN (${placeholders4})`, ...taskIds, ...taskIds),
|
|
46810
|
+
comments: count(`SELECT count(*) AS count FROM task_comments WHERE id IN (${graph.comment_ids.map(() => "?").join(",") || "NULL"})`, ...graph.comment_ids),
|
|
46811
|
+
verifications: count(`SELECT count(*) AS count FROM task_verifications WHERE id IN (${graph.verification_ids.map(() => "?").join(",") || "NULL"})`, ...graph.verification_ids),
|
|
46812
|
+
complete: true
|
|
46813
|
+
};
|
|
46814
|
+
}
|
|
46815
|
+
}
|
|
46227
46816
|
var sqliteTails;
|
|
46228
46817
|
var init_sqlite2 = __esm(() => {
|
|
46229
46818
|
init_canonical();
|
|
46819
|
+
init_plan_slug();
|
|
46230
46820
|
init_backend();
|
|
46231
46821
|
init_types5();
|
|
46232
46822
|
sqliteTails = new WeakMap;
|
|
@@ -46245,7 +46835,7 @@ function parseJson2(value) {
|
|
|
46245
46835
|
function timestamp2(value) {
|
|
46246
46836
|
return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
|
|
46247
46837
|
}
|
|
46248
|
-
function
|
|
46838
|
+
function fault2(faults, point) {
|
|
46249
46839
|
if (faults.points.has(point))
|
|
46250
46840
|
throw new Error(`Injected task-manifest fault at ${point}`);
|
|
46251
46841
|
}
|
|
@@ -46327,7 +46917,7 @@ function taskPayload(manifest, task2, taskId, planId, now4) {
|
|
|
46327
46917
|
function planPayload(input) {
|
|
46328
46918
|
return {
|
|
46329
46919
|
id: input.graph.plan_id,
|
|
46330
|
-
slug:
|
|
46920
|
+
slug: taskManifestPlanSlug(input.manifest, input.graph.plan_id),
|
|
46331
46921
|
project_id: input.manifest.project_id,
|
|
46332
46922
|
task_list_id: input.manifest.task_list_id ?? null,
|
|
46333
46923
|
agent_id: null,
|
|
@@ -46340,7 +46930,7 @@ function planPayload(input) {
|
|
|
46340
46930
|
synced_at: null
|
|
46341
46931
|
};
|
|
46342
46932
|
}
|
|
46343
|
-
function
|
|
46933
|
+
function placeholders4(start, count) {
|
|
46344
46934
|
return Array.from({ length: count }, (_, index) => `$${start + index}`).join(",");
|
|
46345
46935
|
}
|
|
46346
46936
|
|
|
@@ -46414,15 +47004,15 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46414
47004
|
}
|
|
46415
47005
|
const objectIds = [input.graph.plan_id, ...Object.values(input.graph.task_ids), ...input.graph.comment_ids, ...input.graph.verification_ids, ...input.graph.dependency_ids];
|
|
46416
47006
|
const conflict = await tx.query(`SELECT object_id FROM ${this.tableName}
|
|
46417
|
-
WHERE service = $1 AND object_id IN (${
|
|
47007
|
+
WHERE service = $1 AND object_id IN (${placeholders4(2, objectIds.length)}) LIMIT 1`, [this.service, ...objectIds]);
|
|
46418
47008
|
if (conflict.rows[0])
|
|
46419
47009
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_GRAPH_CONFLICT", "A deterministic graph id already exists");
|
|
46420
47010
|
await this.insertSync(tx, "plans", input.graph.plan_id, planPayload(input), input.now);
|
|
46421
|
-
|
|
47011
|
+
fault2(faults, "after_plan_write");
|
|
46422
47012
|
for (const task2 of manifest.tasks) {
|
|
46423
47013
|
await this.insertSync(tx, "tasks", input.graph.task_ids[task2.key], taskPayload(manifest, task2, input.graph.task_ids[task2.key], input.graph.plan_id, input.now), input.now);
|
|
46424
47014
|
}
|
|
46425
|
-
|
|
47015
|
+
fault2(faults, "after_task_write");
|
|
46426
47016
|
for (const [index, edge] of (manifest.dependencies ?? []).entries()) {
|
|
46427
47017
|
await this.insertSync(tx, "dependencies", input.graph.dependency_ids[index], {
|
|
46428
47018
|
id: input.graph.dependency_ids[index],
|
|
@@ -46432,7 +47022,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46432
47022
|
updated_at: input.now
|
|
46433
47023
|
}, input.now);
|
|
46434
47024
|
}
|
|
46435
|
-
|
|
47025
|
+
fault2(faults, "after_dependency_write");
|
|
46436
47026
|
let commentIndex = 0;
|
|
46437
47027
|
for (const task2 of manifest.tasks)
|
|
46438
47028
|
for (const comment2 of task2.comments ?? []) {
|
|
@@ -46448,7 +47038,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46448
47038
|
created_at: input.now
|
|
46449
47039
|
}, input.now);
|
|
46450
47040
|
}
|
|
46451
|
-
|
|
47041
|
+
fault2(faults, "after_comment_write");
|
|
46452
47042
|
let verificationIndex = 0;
|
|
46453
47043
|
for (const task2 of manifest.tasks)
|
|
46454
47044
|
for (const verification2 of task2.verifications ?? []) {
|
|
@@ -46466,7 +47056,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46466
47056
|
updated_at: input.now
|
|
46467
47057
|
}, input.now);
|
|
46468
47058
|
}
|
|
46469
|
-
|
|
47059
|
+
fault2(faults, "after_verification_write");
|
|
46470
47060
|
const readback = await this.readback(tx, input.graph);
|
|
46471
47061
|
const expected = {
|
|
46472
47062
|
plans: 1,
|
|
@@ -46529,7 +47119,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46529
47119
|
input.now
|
|
46530
47120
|
]);
|
|
46531
47121
|
}
|
|
46532
|
-
|
|
47122
|
+
fault2(faults, "after_outbox_write");
|
|
46533
47123
|
await tx.query(`INSERT INTO todos_task_manifest_bindings (
|
|
46534
47124
|
operation_id, tenant_id, idempotency_key, request_digest, result_digest, apply_receipt_id,
|
|
46535
47125
|
manifest_json, result_json, state, version, created_at, updated_at
|
|
@@ -46544,7 +47134,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46544
47134
|
resultJson2,
|
|
46545
47135
|
input.now
|
|
46546
47136
|
]);
|
|
46547
|
-
|
|
47137
|
+
fault2(faults, "after_receipt_write");
|
|
46548
47138
|
return result;
|
|
46549
47139
|
});
|
|
46550
47140
|
}
|
|
@@ -46784,7 +47374,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46784
47374
|
}
|
|
46785
47375
|
const managedIds = [...expectedPayloads.keys()];
|
|
46786
47376
|
const stored = await tx.query(`SELECT object_type, object_id, payload FROM ${this.tableName}
|
|
46787
|
-
WHERE service = $1 AND object_id IN (${
|
|
47377
|
+
WHERE service = $1 AND object_id IN (${placeholders4(2, managedIds.length)})`, [this.service, ...managedIds]);
|
|
46788
47378
|
for (const row of stored.rows) {
|
|
46789
47379
|
const expected = expectedPayloads.get(String(row["object_id"]));
|
|
46790
47380
|
if (!expected || expected.type !== row["object_type"] || expected.payload !== canonicalJson2(parseJson2(row["payload"]))) {
|
|
@@ -46821,7 +47411,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46821
47411
|
if (!ids.length)
|
|
46822
47412
|
continue;
|
|
46823
47413
|
await tx.query(`DELETE FROM ${this.tableName} WHERE service = $1 AND object_type = $2
|
|
46824
|
-
AND object_id IN (${
|
|
47414
|
+
AND object_id IN (${placeholders4(3, ids.length)})`, [this.service, objectType2, ...ids]);
|
|
46825
47415
|
}
|
|
46826
47416
|
const readback = await this.readback(tx, applyResult.graph);
|
|
46827
47417
|
const result = { duplicate: false, receipt, absent: true, readback };
|
|
@@ -46861,7 +47451,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46861
47451
|
return 0;
|
|
46862
47452
|
const result = await tx.query(`SELECT count(*) AS count FROM ${this.tableName}
|
|
46863
47453
|
WHERE service = $1 AND object_type = $2 AND deleted_at IS NULL
|
|
46864
|
-
AND object_id IN (${
|
|
47454
|
+
AND object_id IN (${placeholders4(3, ids.length)})`, [this.service, objectType2, ...ids]);
|
|
46865
47455
|
return Number(result.rows[0]?.count ?? 0);
|
|
46866
47456
|
};
|
|
46867
47457
|
return {
|
|
@@ -46876,6 +47466,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
46876
47466
|
}
|
|
46877
47467
|
var init_postgres3 = __esm(() => {
|
|
46878
47468
|
init_canonical();
|
|
47469
|
+
init_plan_slug();
|
|
46879
47470
|
init_backend();
|
|
46880
47471
|
init_postgres_sync();
|
|
46881
47472
|
init_types5();
|
|
@@ -47078,6 +47669,13 @@ class PackageOwnedTodosTaskManifestAuthority {
|
|
|
47078
47669
|
return result;
|
|
47079
47670
|
}
|
|
47080
47671
|
}
|
|
47672
|
+
function createSqliteTodosTaskManifestAuthority(options) {
|
|
47673
|
+
if (!options?.database) {
|
|
47674
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An explicit SQLite Database is required");
|
|
47675
|
+
}
|
|
47676
|
+
const tenantId = resolveTenantId(options.tenantId);
|
|
47677
|
+
return new PackageOwnedTodosTaskManifestAuthority(new SqliteTodosTaskManifestBackend(options.database, tenantId), { ...options, tenantId });
|
|
47678
|
+
}
|
|
47081
47679
|
function createPostgresTodosTaskManifestAuthority(client, options = {}) {
|
|
47082
47680
|
if (!client || typeof client.transaction !== "function") {
|
|
47083
47681
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_ATOMICITY_UNAVAILABLE", "An authoritative PostgreSQL transaction(callback) client is required");
|
|
@@ -60189,18 +60787,18 @@ function createSessionRecoveryHandoff(input, db) {
|
|
|
60189
60787
|
LIMIT ?
|
|
60190
60788
|
`).all(...params, limit);
|
|
60191
60789
|
const taskIds = tasks.map((task2) => task2.id);
|
|
60192
|
-
const
|
|
60790
|
+
const placeholders5 = taskIds.map(() => "?").join(",");
|
|
60193
60791
|
const files = taskIds.length ? d.query(`
|
|
60194
60792
|
SELECT DISTINCT path
|
|
60195
60793
|
FROM task_files
|
|
60196
|
-
WHERE task_id IN (${
|
|
60794
|
+
WHERE task_id IN (${placeholders5}) AND status != 'removed'
|
|
60197
60795
|
ORDER BY updated_at DESC, path
|
|
60198
60796
|
LIMIT ?
|
|
60199
60797
|
`).all(...taskIds, limit).map((row) => row.path) : [];
|
|
60200
60798
|
const runs = taskIds.length ? d.query(`
|
|
60201
60799
|
SELECT id
|
|
60202
60800
|
FROM task_runs
|
|
60203
|
-
WHERE task_id IN (${
|
|
60801
|
+
WHERE task_id IN (${placeholders5})
|
|
60204
60802
|
ORDER BY started_at DESC, created_at DESC
|
|
60205
60803
|
LIMIT ?
|
|
60206
60804
|
`).all(...taskIds, limit).map((row) => row.id) : [];
|
|
@@ -69771,8 +70369,8 @@ function findPath(sourceId, targetId, opts, db) {
|
|
|
69771
70369
|
let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
|
|
69772
70370
|
const params = [current.id];
|
|
69773
70371
|
if (opts?.relation_types && opts.relation_types.length > 0) {
|
|
69774
|
-
const
|
|
69775
|
-
sql += ` AND relation_type IN (${
|
|
70372
|
+
const placeholders5 = opts.relation_types.map(() => "?").join(",");
|
|
70373
|
+
sql += ` AND relation_type IN (${placeholders5})`;
|
|
69776
70374
|
params.push(...opts.relation_types);
|
|
69777
70375
|
}
|
|
69778
70376
|
const edges = d.query(sql).all(...params).map(rowToEdge);
|
|
@@ -69807,8 +70405,8 @@ function getImpactAnalysis(entityId, opts, db) {
|
|
|
69807
70405
|
let sql = "SELECT * FROM kg_edges WHERE source_id = ?";
|
|
69808
70406
|
const params = [current.id];
|
|
69809
70407
|
if (opts?.relation_types && opts.relation_types.length > 0) {
|
|
69810
|
-
const
|
|
69811
|
-
sql += ` AND relation_type IN (${
|
|
70408
|
+
const placeholders5 = opts.relation_types.map(() => "?").join(",");
|
|
70409
|
+
sql += ` AND relation_type IN (${placeholders5})`;
|
|
69812
70410
|
params.push(...opts.relation_types);
|
|
69813
70411
|
}
|
|
69814
70412
|
const edges = d.query(sql).all(...params).map(rowToEdge);
|
|
@@ -90970,6 +91568,156 @@ var init_pr_group_commands = __esm(() => {
|
|
|
90970
91568
|
init_helpers();
|
|
90971
91569
|
});
|
|
90972
91570
|
|
|
91571
|
+
// src/cli/commands/task-manifest-commands.ts
|
|
91572
|
+
var exports_task_manifest_commands = {};
|
|
91573
|
+
__export(exports_task_manifest_commands, {
|
|
91574
|
+
registerTaskManifestCommands: () => registerTaskManifestCommands
|
|
91575
|
+
});
|
|
91576
|
+
import chalk32 from "chalk";
|
|
91577
|
+
import { readFileSync as readFileSync26 } from "fs";
|
|
91578
|
+
function globalOptions11(program2) {
|
|
91579
|
+
const command = program2;
|
|
91580
|
+
return command.optsWithGlobals?.() ?? program2.opts();
|
|
91581
|
+
}
|
|
91582
|
+
function jsonRequested(program2, opts) {
|
|
91583
|
+
return opts.json === true || globalOptions11(program2)["json"] === true;
|
|
91584
|
+
}
|
|
91585
|
+
function parseJsonFile(path) {
|
|
91586
|
+
try {
|
|
91587
|
+
return JSON.parse(readFileSync26(path, "utf8"));
|
|
91588
|
+
} catch (error2) {
|
|
91589
|
+
throw new Error(`task-manifest input must be a readable JSON file: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
91590
|
+
}
|
|
91591
|
+
}
|
|
91592
|
+
function parseInteger2(value, label, min) {
|
|
91593
|
+
const parsed = Number(value);
|
|
91594
|
+
if (!Number.isSafeInteger(parsed) || parsed < min) {
|
|
91595
|
+
throw new Error(`${label} must be an integer >= ${min}`);
|
|
91596
|
+
}
|
|
91597
|
+
return parsed;
|
|
91598
|
+
}
|
|
91599
|
+
async function localAuthority(tenantId) {
|
|
91600
|
+
return createSqliteTodosTaskManifestAuthority({
|
|
91601
|
+
database: getDatabase(),
|
|
91602
|
+
...tenantId ? { tenantId } : {}
|
|
91603
|
+
});
|
|
91604
|
+
}
|
|
91605
|
+
async function currentTenantId(remote, authority, explicit) {
|
|
91606
|
+
if (explicit)
|
|
91607
|
+
return explicit;
|
|
91608
|
+
const capability = remote ? await cloudTaskManifestCapability(remote) : await authority.capability();
|
|
91609
|
+
return capability.tenant_id;
|
|
91610
|
+
}
|
|
91611
|
+
function registerTaskManifestCommands(program2) {
|
|
91612
|
+
const taskManifest = program2.command("task-manifest").description("Apply and inspect package-owned task-manifest safe mutations");
|
|
91613
|
+
taskManifest.command("capability").description("Show the current task-manifest authority capability").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (opts) => {
|
|
91614
|
+
try {
|
|
91615
|
+
const remote = getTodosCloudClient();
|
|
91616
|
+
const result = remote ? await cloudTaskManifestCapability(remote) : await (await localAuthority(opts.tenantId)).capability();
|
|
91617
|
+
if (jsonRequested(program2, opts)) {
|
|
91618
|
+
output({ capability: result }, true);
|
|
91619
|
+
return;
|
|
91620
|
+
}
|
|
91621
|
+
console.log(`${chalk32.bold(result.route)} tenant=${result.tenant_id} backend=${result.backend}`);
|
|
91622
|
+
} catch (error2) {
|
|
91623
|
+
handleError(error2);
|
|
91624
|
+
}
|
|
91625
|
+
});
|
|
91626
|
+
taskManifest.command("apply").description("Apply a task-manifest JSON file exactly once by operation/idempotency key").requiredOption("--file <path>", "Task manifest JSON file").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (opts) => {
|
|
91627
|
+
try {
|
|
91628
|
+
const manifest = parseJsonFile(opts.file);
|
|
91629
|
+
const remote = getTodosCloudClient();
|
|
91630
|
+
const result = remote ? await cloudApplyTaskManifest(remote, manifest) : await (await localAuthority(opts.tenantId)).apply(manifest);
|
|
91631
|
+
if (jsonRequested(program2, opts)) {
|
|
91632
|
+
output({ result }, true);
|
|
91633
|
+
return;
|
|
91634
|
+
}
|
|
91635
|
+
console.log(`${result.duplicate ? "duplicate" : "applied"} receipt=${result.receipt.receipt_id}`);
|
|
91636
|
+
console.log(`plan=${result.graph.plan_id} tasks=${Object.keys(result.graph.task_ids).length}`);
|
|
91637
|
+
} catch (error2) {
|
|
91638
|
+
handleError(error2);
|
|
91639
|
+
}
|
|
91640
|
+
});
|
|
91641
|
+
taskManifest.command("read-exact <receipt-id>").description("Read one immutable task-manifest apply receipt by full receipt id").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (receiptId3, opts) => {
|
|
91642
|
+
try {
|
|
91643
|
+
const remote = getTodosCloudClient();
|
|
91644
|
+
const result = remote ? await cloudReadExactTaskManifest(remote, receiptId3) : await (await localAuthority(opts.tenantId)).readExact(receiptId3);
|
|
91645
|
+
if (jsonRequested(program2, opts)) {
|
|
91646
|
+
output({ result }, true);
|
|
91647
|
+
return;
|
|
91648
|
+
}
|
|
91649
|
+
console.log(`receipt=${result.receipt.receipt_id} duplicate=${String(result.duplicate)}`);
|
|
91650
|
+
console.log(`readback=${JSON.stringify(result.readback)}`);
|
|
91651
|
+
} catch (error2) {
|
|
91652
|
+
handleError(error2);
|
|
91653
|
+
}
|
|
91654
|
+
});
|
|
91655
|
+
taskManifest.command("lookup").description("Recover one exact managed apply receipt from a full plan id").requiredOption("--plan-id <uuid>", "Full managed plan UUID").option("--tenant-id <id>", "Authority tenant id; defaults to capability tenant").option("-j, --json", "Output as JSON").action(async (opts) => {
|
|
91656
|
+
try {
|
|
91657
|
+
const remote = getTodosCloudClient();
|
|
91658
|
+
const authority = remote ? null : await localAuthority(opts.tenantId);
|
|
91659
|
+
const tenantId = await currentTenantId(remote, authority, opts.tenantId);
|
|
91660
|
+
const request = {
|
|
91661
|
+
authority: "todos",
|
|
91662
|
+
route: TODOS_TASK_MANIFEST_ROUTE,
|
|
91663
|
+
schema_version: TODOS_TASK_MANIFEST_SCHEMA_VERSION,
|
|
91664
|
+
tenant_id: tenantId,
|
|
91665
|
+
plan_id: opts.planId,
|
|
91666
|
+
max_items: 1
|
|
91667
|
+
};
|
|
91668
|
+
const result = remote ? await cloudLookupTaskManifestBinding(remote, request) : await authority.lookupBinding(request);
|
|
91669
|
+
if (jsonRequested(program2, opts)) {
|
|
91670
|
+
output({ result }, true);
|
|
91671
|
+
return;
|
|
91672
|
+
}
|
|
91673
|
+
console.log(`plan=${result.plan_id} receipt=${result.apply_receipt_id} state=${result.state}`);
|
|
91674
|
+
} catch (error2) {
|
|
91675
|
+
handleError(error2);
|
|
91676
|
+
}
|
|
91677
|
+
});
|
|
91678
|
+
taskManifest.command("compensate").description("Compensate an untouched task-manifest graph with CAS protection").requiredOption("--receipt-id <uuid>", "Full apply receipt UUID").requiredOption("--idempotency-key <key>", "Stable compensation idempotency key").requiredOption("--if-binding-version <n>", "Expected current binding version").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (opts) => {
|
|
91679
|
+
try {
|
|
91680
|
+
const request = {
|
|
91681
|
+
receipt_id: opts.receiptId,
|
|
91682
|
+
idempotency_key: opts.idempotencyKey,
|
|
91683
|
+
if_binding_version: parseInteger2(opts.ifBindingVersion, "--if-binding-version", 1)
|
|
91684
|
+
};
|
|
91685
|
+
const remote = getTodosCloudClient();
|
|
91686
|
+
const result = remote ? await cloudCompensateTaskManifest(remote, request) : await (await localAuthority(opts.tenantId)).compensate(request);
|
|
91687
|
+
if (jsonRequested(program2, opts)) {
|
|
91688
|
+
output({ result }, true);
|
|
91689
|
+
return;
|
|
91690
|
+
}
|
|
91691
|
+
console.log(`${result.duplicate ? "duplicate" : "compensated"} receipt=${result.receipt.receipt_id}`);
|
|
91692
|
+
console.log(`absent=${String(result.absent)} readback=${JSON.stringify(result.readback)}`);
|
|
91693
|
+
} catch (error2) {
|
|
91694
|
+
handleError(error2);
|
|
91695
|
+
}
|
|
91696
|
+
});
|
|
91697
|
+
taskManifest.command("outbox-delivered <outbox-id>").description("Mark one task-manifest outbox effect delivered").option("-j, --json", "Output as JSON").option("--tenant-id <id>", "Tenant id for local SQLite authority").action(async (outboxId, opts) => {
|
|
91698
|
+
try {
|
|
91699
|
+
const remote = getTodosCloudClient();
|
|
91700
|
+
if (remote)
|
|
91701
|
+
await cloudMarkTaskManifestOutboxDelivered(remote, outboxId);
|
|
91702
|
+
else
|
|
91703
|
+
await (await localAuthority(opts.tenantId)).markOutboxDelivered(outboxId);
|
|
91704
|
+
if (jsonRequested(program2, opts)) {
|
|
91705
|
+
output({ delivered: true }, true);
|
|
91706
|
+
return;
|
|
91707
|
+
}
|
|
91708
|
+
console.log(`delivered=${outboxId}`);
|
|
91709
|
+
} catch (error2) {
|
|
91710
|
+
handleError(error2);
|
|
91711
|
+
}
|
|
91712
|
+
});
|
|
91713
|
+
}
|
|
91714
|
+
var init_task_manifest_commands = __esm(() => {
|
|
91715
|
+
init_database();
|
|
91716
|
+
init_task_manifest();
|
|
91717
|
+
init_cloud_router();
|
|
91718
|
+
init_helpers();
|
|
91719
|
+
});
|
|
91720
|
+
|
|
90973
91721
|
// src/lib/cli-help.ts
|
|
90974
91722
|
function optionEntry(option) {
|
|
90975
91723
|
return {
|
|
@@ -91233,7 +91981,7 @@ var exports_help_commands = {};
|
|
|
91233
91981
|
__export(exports_help_commands, {
|
|
91234
91982
|
registerHelpCommands: () => registerHelpCommands
|
|
91235
91983
|
});
|
|
91236
|
-
function
|
|
91984
|
+
function globalOptions12(program2) {
|
|
91237
91985
|
const command = program2;
|
|
91238
91986
|
return command.optsWithGlobals?.() ?? program2.opts();
|
|
91239
91987
|
}
|
|
@@ -91252,7 +92000,7 @@ function registerHelpCommands(program2, route = "local", remoteCapabilities = ne
|
|
|
91252
92000
|
});
|
|
91253
92001
|
program2.command("manual").description("Print the complete local CLI manual").option("--format <format>", "markdown or json", "markdown").option("-j, --json", "Output as JSON").action((opts) => {
|
|
91254
92002
|
try {
|
|
91255
|
-
const globalOpts =
|
|
92003
|
+
const globalOpts = globalOptions12(program2);
|
|
91256
92004
|
const manual = createCliManual(program2, {
|
|
91257
92005
|
isCommandVisible: (command) => isTodosCliCommandVisibleForRoute(command, route, remoteCapabilities),
|
|
91258
92006
|
localOnly: route === "local"
|
|
@@ -91387,6 +92135,7 @@ var [
|
|
|
91387
92135
|
{ registerStorageCommands: registerStorageCommands2 },
|
|
91388
92136
|
{ registerScaleHardeningCommands: registerScaleHardeningCommands2 },
|
|
91389
92137
|
{ registerPrGroupCommands: registerPrGroupCommands2 },
|
|
92138
|
+
{ registerTaskManifestCommands: registerTaskManifestCommands2 },
|
|
91390
92139
|
{ registerHelpCommands: registerHelpCommands2 }
|
|
91391
92140
|
] = await Promise.all([
|
|
91392
92141
|
Promise.resolve().then(() => (init_helpers(), exports_helpers)),
|
|
@@ -91420,6 +92169,7 @@ var [
|
|
|
91420
92169
|
Promise.resolve().then(() => (init_storage_commands(), exports_storage_commands)),
|
|
91421
92170
|
Promise.resolve().then(() => (init_scale_hardening_commands(), exports_scale_hardening_commands)),
|
|
91422
92171
|
Promise.resolve().then(() => (init_pr_group_commands(), exports_pr_group_commands)),
|
|
92172
|
+
Promise.resolve().then(() => (init_task_manifest_commands(), exports_task_manifest_commands)),
|
|
91423
92173
|
Promise.resolve().then(() => (init_help_commands(), exports_help_commands))
|
|
91424
92174
|
]);
|
|
91425
92175
|
registerTaskCommands2(program2);
|
|
@@ -91452,6 +92202,7 @@ registerLocalBackupCommands2(program2);
|
|
|
91452
92202
|
registerStorageCommands2(program2);
|
|
91453
92203
|
registerScaleHardeningCommands2(program2);
|
|
91454
92204
|
registerPrGroupCommands2(program2);
|
|
92205
|
+
registerTaskManifestCommands2(program2);
|
|
91455
92206
|
await registerOptionalEventsCommands(program2);
|
|
91456
92207
|
registerHelpCommands2(program2, authority.route, remoteCommandCapabilities);
|
|
91457
92208
|
applyTodosCliHelpVisibility(program2, authority.route, remoteCommandCapabilities);
|