@hasna/todos 0.15.29 → 0.15.32
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 +13 -1
- package/dist/cli/cloud-router.d.ts.map +1 -1
- package/dist/cli/commands/project-registration-commands.d.ts +2 -0
- package/dist/cli/commands/project-registration-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-commands.d.ts.map +1 -1
- package/dist/cli/commands/task-manifest-commands.d.ts.map +1 -1
- package/dist/cli/index.js +2835 -334
- package/dist/contracts.js +52 -2
- package/dist/db/task-crud.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1447 -144
- package/dist/lib/task-parent-integrity.d.ts +14 -0
- package/dist/lib/task-parent-integrity.d.ts.map +1 -0
- package/dist/mcp/index.js +2031 -135
- package/dist/mcp/tools/task-crud.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/dist/project-registration/adoption-validation.d.ts +3 -0
- package/dist/project-registration/adoption-validation.d.ts.map +1 -0
- package/dist/project-registration/authority.d.ts +3 -1
- package/dist/project-registration/authority.d.ts.map +1 -1
- package/dist/project-registration/backend.d.ts +24 -1
- package/dist/project-registration/backend.d.ts.map +1 -1
- package/dist/project-registration/http.d.ts +3 -1
- package/dist/project-registration/http.d.ts.map +1 -1
- package/dist/project-registration/index.d.ts +2 -1
- package/dist/project-registration/index.d.ts.map +1 -1
- package/dist/project-registration/page-validation.d.ts +5 -0
- package/dist/project-registration/page-validation.d.ts.map +1 -0
- package/dist/project-registration/postgres.d.ts +13 -1
- package/dist/project-registration/postgres.d.ts.map +1 -1
- package/dist/project-registration/sqlite.d.ts +13 -1
- package/dist/project-registration/sqlite.d.ts.map +1 -1
- package/dist/project-registration/types.d.ts +68 -1
- package/dist/project-registration/types.d.ts.map +1 -1
- package/dist/project-registration.js +831 -58
- package/dist/registry.js +52 -2
- package/dist/release-provenance.json +5 -5
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +151 -0
- package/dist/sdk/v1.generated.d.ts +282 -0
- package/dist/sdk/v1.generated.d.ts.map +1 -1
- package/dist/server/index.js +2022 -126
- package/dist/server/openapi.d.ts +2856 -1357
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/v1.d.ts.map +1 -1
- package/dist/storage/postgres-adapter.d.ts.map +1 -1
- package/dist/storage/postgres-sync.d.ts +8 -0
- package/dist/storage/postgres-sync.d.ts.map +1 -1
- package/dist/storage.js +232 -19
- package/dist/task-manifest/authority.d.ts +13 -1
- package/dist/task-manifest/authority.d.ts.map +1 -1
- package/dist/task-manifest/backend.d.ts +5 -0
- package/dist/task-manifest/backend.d.ts.map +1 -1
- package/dist/task-manifest/index.d.ts +2 -2
- package/dist/task-manifest/index.d.ts.map +1 -1
- package/dist/task-manifest/plan-slug.d.ts +20 -0
- package/dist/task-manifest/plan-slug.d.ts.map +1 -1
- package/dist/task-manifest/postgres.d.ts +1 -0
- package/dist/task-manifest/postgres.d.ts.map +1 -1
- package/dist/task-manifest/schema-sql.d.ts.map +1 -1
- package/dist/task-manifest/schema.d.ts.map +1 -1
- package/dist/task-manifest/sqlite.d.ts +1 -0
- package/dist/task-manifest/sqlite.d.ts.map +1 -1
- package/dist/task-manifest/types.d.ts +21 -1
- package/dist/task-manifest/types.d.ts.map +1 -1
- package/dist/task-manifest.js +592 -61
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/mcp/index.js
CHANGED
|
@@ -12492,6 +12492,46 @@ function guardPlanRowsSqlite(planIds, db) {
|
|
|
12492
12492
|
}
|
|
12493
12493
|
}
|
|
12494
12494
|
|
|
12495
|
+
// src/lib/task-parent-integrity.ts
|
|
12496
|
+
function parentCycleError(taskId, parentId) {
|
|
12497
|
+
return new ResourceConflictError("TASK_PARENT_CYCLE", `TASK_PARENT_CYCLE: assigning parent ${parentId} to task ${taskId} would create or retain a parent cycle`);
|
|
12498
|
+
}
|
|
12499
|
+
function assertTaskParentIntegrity(taskId, parentId, getTask) {
|
|
12500
|
+
if (parentId === undefined || parentId === null)
|
|
12501
|
+
return;
|
|
12502
|
+
const visited = new Set;
|
|
12503
|
+
let cursor = parentId;
|
|
12504
|
+
while (cursor) {
|
|
12505
|
+
if (cursor === taskId || visited.has(cursor)) {
|
|
12506
|
+
throw parentCycleError(taskId, parentId);
|
|
12507
|
+
}
|
|
12508
|
+
visited.add(cursor);
|
|
12509
|
+
const parent = getTask(cursor);
|
|
12510
|
+
if (!parent)
|
|
12511
|
+
throw new TaskNotFoundError(cursor);
|
|
12512
|
+
cursor = parent.parent_id;
|
|
12513
|
+
}
|
|
12514
|
+
}
|
|
12515
|
+
async function assertTaskParentIntegrityAsync(taskId, parentId, getTask) {
|
|
12516
|
+
if (parentId === undefined || parentId === null)
|
|
12517
|
+
return;
|
|
12518
|
+
const visited = new Set;
|
|
12519
|
+
let cursor = parentId;
|
|
12520
|
+
while (cursor) {
|
|
12521
|
+
if (cursor === taskId || visited.has(cursor)) {
|
|
12522
|
+
throw parentCycleError(taskId, parentId);
|
|
12523
|
+
}
|
|
12524
|
+
visited.add(cursor);
|
|
12525
|
+
const parent = await getTask(cursor);
|
|
12526
|
+
if (!parent)
|
|
12527
|
+
throw new TaskNotFoundError(cursor);
|
|
12528
|
+
cursor = parent.parent_id;
|
|
12529
|
+
}
|
|
12530
|
+
}
|
|
12531
|
+
var init_task_parent_integrity = __esm(() => {
|
|
12532
|
+
init_types();
|
|
12533
|
+
});
|
|
12534
|
+
|
|
12495
12535
|
// src/lib/creator-identity.ts
|
|
12496
12536
|
import { existsSync as existsSync6, rmSync } from "fs";
|
|
12497
12537
|
import { join as join5 } from "path";
|
|
@@ -14071,6 +14111,7 @@ function createTaskStored(input, d) {
|
|
|
14071
14111
|
let id = uuid();
|
|
14072
14112
|
for (let attempt = 0;attempt < 3; attempt++) {
|
|
14073
14113
|
try {
|
|
14114
|
+
assertTaskParentIntegrity(id, input.parent_id, (candidateId) => getTask(candidateId, d));
|
|
14074
14115
|
d.run(`INSERT INTO tasks (id, short_id, project_id, parent_id, plan_id, task_list_id, cycle_id, title, description, status, priority, agent_id, assigned_to, session_id, working_dir, tags, metadata, version, created_at, updated_at, due_at, estimated_minutes, sla_minutes, confidence, retry_count, max_retries, retry_after, requires_approval, approved_by, approved_at, recurrence_rule, recurrence_parent_id, spawns_template_id, reason, spawned_from_session, assigned_by, created_by, assigned_from_project, task_type, machine_id)
|
|
14075
14116
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
14076
14117
|
id,
|
|
@@ -14449,6 +14490,7 @@ function updateTaskStored(id, input, db) {
|
|
|
14449
14490
|
throw new VersionConflictError(id, input.version, task.version);
|
|
14450
14491
|
}
|
|
14451
14492
|
input = sanitizeUpdateTaskInput(input);
|
|
14493
|
+
assertTaskParentIntegrity(id, input.parent_id, (candidateId) => getTask(candidateId, d));
|
|
14452
14494
|
const effectivePlanId = input.plan_id !== undefined ? input.plan_id : task.plan_id;
|
|
14453
14495
|
const linkedProjectId = linkedPlanProjectId(effectivePlanId, d);
|
|
14454
14496
|
if (linkedProjectId) {
|
|
@@ -14502,6 +14544,10 @@ function updateTaskStored(id, input, db) {
|
|
|
14502
14544
|
sets.push("project_id = ?");
|
|
14503
14545
|
params.push(input.project_id);
|
|
14504
14546
|
}
|
|
14547
|
+
if (input.parent_id !== undefined) {
|
|
14548
|
+
sets.push("parent_id = ?");
|
|
14549
|
+
params.push(input.parent_id);
|
|
14550
|
+
}
|
|
14505
14551
|
if (input.assigned_to !== undefined) {
|
|
14506
14552
|
sets.push("assigned_to = ?");
|
|
14507
14553
|
params.push(input.assigned_to);
|
|
@@ -14617,6 +14663,8 @@ function updateTaskStored(id, input, db) {
|
|
|
14617
14663
|
logTaskChange(id, "update", "priority", task.priority, input.priority, agentId, d);
|
|
14618
14664
|
if (input.title !== undefined && input.title !== task.title)
|
|
14619
14665
|
logTaskChange(id, "update", "title", task.title, input.title, agentId, d);
|
|
14666
|
+
if (input.parent_id !== undefined && input.parent_id !== task.parent_id)
|
|
14667
|
+
logTaskChange(id, "update", "parent_id", task.parent_id, input.parent_id, agentId, d);
|
|
14620
14668
|
if (input.assigned_to !== undefined && input.assigned_to !== task.assigned_to)
|
|
14621
14669
|
logTaskChange(id, "update", "assigned_to", task.assigned_to, input.assigned_to, agentId, d);
|
|
14622
14670
|
if (input.working_dir !== undefined && input.working_dir !== task.working_dir)
|
|
@@ -14674,7 +14722,8 @@ function updateTask(id, input, db) {
|
|
|
14674
14722
|
if (!before)
|
|
14675
14723
|
throw new TaskNotFoundError(id);
|
|
14676
14724
|
const guardedPlanIds = [before.plan_id, input.plan_id];
|
|
14677
|
-
|
|
14725
|
+
const needsSerializedWrite = input.parent_id !== undefined || guardedPlanIds.some(Boolean);
|
|
14726
|
+
if (!needsSerializedWrite)
|
|
14678
14727
|
return updateTaskStored(id, input, d);
|
|
14679
14728
|
return d.transaction(() => {
|
|
14680
14729
|
guardPlanRowsSqlite(guardedPlanIds, d);
|
|
@@ -14710,6 +14759,7 @@ var init_task_crud = __esm(() => {
|
|
|
14710
14759
|
init_checklists();
|
|
14711
14760
|
init_storage_tombstones();
|
|
14712
14761
|
init_prewrite_secrets();
|
|
14762
|
+
init_task_parent_integrity();
|
|
14713
14763
|
});
|
|
14714
14764
|
|
|
14715
14765
|
// src/db/task-status.ts
|
|
@@ -21016,6 +21066,31 @@ var init_http_client = __esm(() => {
|
|
|
21016
21066
|
]);
|
|
21017
21067
|
});
|
|
21018
21068
|
|
|
21069
|
+
// src/project-registration/types.ts
|
|
21070
|
+
var TODOS_PROJECT_REGISTRATION_ROUTE = "todos.project-registration.v1", TODOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1", TodosProjectRegistrationError;
|
|
21071
|
+
var init_types4 = __esm(() => {
|
|
21072
|
+
TodosProjectRegistrationError = class TodosProjectRegistrationError extends Error {
|
|
21073
|
+
code;
|
|
21074
|
+
details;
|
|
21075
|
+
constructor(code, message, details = {}) {
|
|
21076
|
+
super(message);
|
|
21077
|
+
this.code = code;
|
|
21078
|
+
this.details = details;
|
|
21079
|
+
this.name = "TodosProjectRegistrationError";
|
|
21080
|
+
}
|
|
21081
|
+
};
|
|
21082
|
+
});
|
|
21083
|
+
|
|
21084
|
+
// src/project-registration/adoption-validation.ts
|
|
21085
|
+
var init_adoption_validation = __esm(() => {
|
|
21086
|
+
init_types4();
|
|
21087
|
+
});
|
|
21088
|
+
|
|
21089
|
+
// src/project-registration/page-validation.ts
|
|
21090
|
+
var init_page_validation = __esm(() => {
|
|
21091
|
+
init_types4();
|
|
21092
|
+
});
|
|
21093
|
+
|
|
21019
21094
|
// src/cli/cloud-router.ts
|
|
21020
21095
|
import { resolveStorageClient } from "@hasna/contracts/client/storage";
|
|
21021
21096
|
import { normalizeStorageMode } from "@hasna/contracts/mode";
|
|
@@ -21416,12 +21491,39 @@ async function cloudListTasks(client, filter = {}) {
|
|
|
21416
21491
|
union.sort(compareCloudTaskOrder);
|
|
21417
21492
|
return union.slice(start, windowEnd);
|
|
21418
21493
|
}
|
|
21494
|
+
async function cloudResolveTaskRef(client, ref) {
|
|
21495
|
+
const input = ref.trim().toLowerCase();
|
|
21496
|
+
if (!input)
|
|
21497
|
+
throw new Error("Task reference must not be empty");
|
|
21498
|
+
if (UUID_RE.test(input))
|
|
21499
|
+
return input;
|
|
21500
|
+
let task;
|
|
21501
|
+
try {
|
|
21502
|
+
task = await cloudGetTask(client, input);
|
|
21503
|
+
} catch (error) {
|
|
21504
|
+
const status = error && typeof error === "object" ? error.status : undefined;
|
|
21505
|
+
if (status === 409) {
|
|
21506
|
+
const body = error && typeof error === "object" ? error.body : undefined;
|
|
21507
|
+
const authorityMessage = body && typeof body === "object" && !Array.isArray(body) ? body.error : undefined;
|
|
21508
|
+
throw new Error(typeof authorityMessage === "string" && authorityMessage.trim() ? authorityMessage : `Task reference is ambiguous: "${ref}"`);
|
|
21509
|
+
}
|
|
21510
|
+
throw error;
|
|
21511
|
+
}
|
|
21512
|
+
if (task && typeof task.id === "string" && (task.short_id?.toLowerCase() === input || task.id.toLowerCase().startsWith(input))) {
|
|
21513
|
+
return task.id;
|
|
21514
|
+
}
|
|
21515
|
+
if (task) {
|
|
21516
|
+
throw new Error(`Task not found: ${ref} \u2014 the authority returned a task carrying neither this short id ` + "nor this id prefix, so the reference was not resolved.");
|
|
21517
|
+
}
|
|
21518
|
+
throw new Error(`Task not found: ${ref} \u2014 the authority resolved no task for this short reference. ` + "That is not proof the task is absent: an authority predating server-side " + "short-reference resolution answers EVERY short id and id prefix with the same 404. " + "Retry with the full task UUID to tell the two apart, or deploy the current " + "@hasna/todos /v1 server.");
|
|
21519
|
+
}
|
|
21419
21520
|
async function cloudGetTask(client, id) {
|
|
21420
21521
|
const raw = await client.get("tasks", id);
|
|
21421
21522
|
return raw == null ? null : unwrapTask(raw);
|
|
21422
21523
|
}
|
|
21423
21524
|
async function cloudCreateTask(client, input, verification = {}) {
|
|
21424
21525
|
const expectedParentId = typeof input["parent_id"] === "string" ? input["parent_id"] : null;
|
|
21526
|
+
const expectedPlanId = typeof input["plan_id"] === "string" ? input["plan_id"] : null;
|
|
21425
21527
|
const expectedCreatedBy = typeof verification.expectedCreatedBy === "string" && verification.expectedCreatedBy.trim() ? verification.expectedCreatedBy : null;
|
|
21426
21528
|
if (expectedCreatedBy !== null) {
|
|
21427
21529
|
await requireTaskCreatorCapability(client);
|
|
@@ -21434,9 +21536,9 @@ async function cloudCreateTask(client, input, verification = {}) {
|
|
|
21434
21536
|
throw new Error(`REMOTE_API_INCOMPATIBLE: configured Todos authority ${remoteAuthorityBase(client)} returned a task create ` + "response without a stored task id; no success row or local SQLite fallback is permitted");
|
|
21435
21537
|
}
|
|
21436
21538
|
const persisted = await cloudGetTask(client, created.id);
|
|
21437
|
-
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId || expectedCreatedBy !== null && persisted.created_by !== expectedCreatedBy) {
|
|
21539
|
+
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== expectedParentId || (persisted.plan_id ?? null) !== expectedPlanId || expectedCreatedBy !== null && persisted.created_by !== expectedCreatedBy) {
|
|
21438
21540
|
const creatorDetail = expectedCreatedBy === null ? "" : ` and explicit created_by=${JSON.stringify(expectedCreatedBy)} ` + `(readback ${JSON.stringify(persisted?.created_by ?? null)})`;
|
|
21439
|
-
throw new Error(`TASK_CREATE_PERSISTENCE_UNVERIFIED: configured Todos authority ${remoteAuthorityBase(client)} accepted ` + `POST /v1/tasks but authoritative GET /v1/tasks/${encodeURIComponent(created.id)} did not return the same ` + `stored task id and
|
|
21541
|
+
throw new Error(`TASK_CREATE_PERSISTENCE_UNVERIFIED: configured Todos authority ${remoteAuthorityBase(client)} accepted ` + `POST /v1/tasks but authoritative GET /v1/tasks/${encodeURIComponent(created.id)} did not return the same ` + `stored task id, parent_id, and plan_id ` + `(requested plan_id=${JSON.stringify(expectedPlanId)}, readback=${JSON.stringify(persisted?.plan_id ?? null)})` + `${creatorDetail}; no success row or local SQLite fallback is permitted`);
|
|
21440
21542
|
}
|
|
21441
21543
|
return persisted;
|
|
21442
21544
|
}
|
|
@@ -21703,6 +21805,8 @@ var init_cloud_router = __esm(() => {
|
|
|
21703
21805
|
init_redaction();
|
|
21704
21806
|
init_plan_project_link_contract();
|
|
21705
21807
|
init_http_client();
|
|
21808
|
+
init_adoption_validation();
|
|
21809
|
+
init_page_validation();
|
|
21706
21810
|
UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
21707
21811
|
TRANSPORT_TOKENS = {
|
|
21708
21812
|
sqlite: "sqlite",
|
|
@@ -21739,6 +21843,7 @@ function registerTaskCrudTools(server, ctx) {
|
|
|
21739
21843
|
compact["version"] = task.version;
|
|
21740
21844
|
compact["created_at"] = task.created_at;
|
|
21741
21845
|
compact["task_list_id"] = task.task_list_id;
|
|
21846
|
+
compact["parent_id"] = task.parent_id;
|
|
21742
21847
|
return compactJson(compact);
|
|
21743
21848
|
}
|
|
21744
21849
|
function versionFor(taskId, version) {
|
|
@@ -22008,6 +22113,7 @@ ${task.description}` : null
|
|
|
22008
22113
|
priority: exports_external.enum(["low", "medium", "high", "critical"]).optional(),
|
|
22009
22114
|
assigned_to: exports_external.string().nullable().optional().describe("Agent ID or name, null to unassign"),
|
|
22010
22115
|
project_id: exports_external.string().nullable().optional(),
|
|
22116
|
+
parent_id: exports_external.string().nullable().optional().describe("Existing parent task ID/reference, null to detach"),
|
|
22011
22117
|
task_list_id: exports_external.string().nullable().optional(),
|
|
22012
22118
|
depends_on: exports_external.array(exports_external.string()).optional().describe("Full replacement array of dependency IDs"),
|
|
22013
22119
|
tags: exports_external.array(exports_external.string()).optional(),
|
|
@@ -22039,6 +22145,9 @@ ${task.description}` : null
|
|
|
22039
22145
|
if (typeof patch.project_id === "string" && patch.project_id) {
|
|
22040
22146
|
patch.project_id = await cloudResolveProjectRef(cloud, patch.project_id);
|
|
22041
22147
|
}
|
|
22148
|
+
if (typeof patch.parent_id === "string" && patch.parent_id) {
|
|
22149
|
+
patch.parent_id = await cloudResolveTaskRef(cloud, patch.parent_id);
|
|
22150
|
+
}
|
|
22042
22151
|
if (typeof patch.task_list_id === "string" && patch.task_list_id) {
|
|
22043
22152
|
let scope = typeof patch.project_id === "string" ? patch.project_id : undefined;
|
|
22044
22153
|
if (!scope) {
|
|
@@ -22047,9 +22156,22 @@ ${task.description}` : null
|
|
|
22047
22156
|
}
|
|
22048
22157
|
patch.task_list_id = await cloudResolveTaskListRef(cloud, patch.task_list_id, scope);
|
|
22049
22158
|
}
|
|
22159
|
+
if (patch.parent_id !== undefined && version2 === undefined) {
|
|
22160
|
+
const current = await cloudGetTask(cloud, task_id2);
|
|
22161
|
+
if (!current)
|
|
22162
|
+
throw new TaskNotFoundError(task_id2);
|
|
22163
|
+
patch.version = current.version;
|
|
22164
|
+
}
|
|
22050
22165
|
if (version2 !== undefined)
|
|
22051
22166
|
patch.version = version2;
|
|
22052
|
-
|
|
22167
|
+
let updated = await cloudUpdateTask(cloud, task_id2, patch);
|
|
22168
|
+
if (patch.parent_id !== undefined) {
|
|
22169
|
+
const persisted = await cloudGetTask(cloud, task_id2);
|
|
22170
|
+
if (!persisted || (persisted.parent_id ?? null) !== patch.parent_id) {
|
|
22171
|
+
throw new Error(`TASK_REPARENT_PERSISTENCE_UNVERIFIED: parent_id expected ${patch.parent_id ?? "null"}, ` + `received ${persisted?.parent_id ?? "missing task"}`);
|
|
22172
|
+
}
|
|
22173
|
+
updated = persisted;
|
|
22174
|
+
}
|
|
22053
22175
|
return { content: [{ type: "text", text: mutationTaskResponse(updated) }] };
|
|
22054
22176
|
}
|
|
22055
22177
|
const resolvedId = resolveId(params.task_id);
|
|
@@ -22061,6 +22183,8 @@ ${task.description}` : null
|
|
|
22061
22183
|
resolved.assigned_to = resolveAssignee(resolved.assigned_to);
|
|
22062
22184
|
if (resolved.project_id && typeof resolved.project_id === "string")
|
|
22063
22185
|
resolved.project_id = resolveId(resolved.project_id, "projects");
|
|
22186
|
+
if (resolved.parent_id && typeof resolved.parent_id === "string")
|
|
22187
|
+
resolved.parent_id = resolveId(resolved.parent_id);
|
|
22064
22188
|
if (resolved.task_list_id && typeof resolved.task_list_id === "string")
|
|
22065
22189
|
resolved.task_list_id = resolveId(resolved.task_list_id, "task_lists");
|
|
22066
22190
|
if (resolved.depends_on && Array.isArray(resolved.depends_on))
|
|
@@ -32760,7 +32884,7 @@ function registerTaskMetaTools(server, ctx) {
|
|
|
32760
32884
|
create_task: "create_task \u2014 Create a new task. Params: title (required), description, status, priority, project_id, task_list_id, assigned_to, depends_on, short_id (null to disable), tags, estimate (minutes), confidence (0.0-1.0), deadline (ISO), retry_count",
|
|
32761
32885
|
list_tasks: "list_tasks \u2014 List tasks with filters. Params: status, priority, project_id, task_list_id, assigned_to, tags[], created_after, created_before, limit, offset",
|
|
32762
32886
|
get_task: "get_task \u2014 Get compact task details by default. Params: task_id, detail=compact|full, max_description_chars, include_metadata",
|
|
32763
|
-
update_task: "update_task \u2014 Update task fields (optimistic locking). Params: task_id (required), title, description, status, priority, assigned_to (null to unassign), project_id, task_list_id, depends_on[], tags[], estimate, actual_minutes, confidence, approved_by, completed_at, deadline, retry_count, version",
|
|
32887
|
+
update_task: "update_task \u2014 Update task fields (optimistic locking). Params: task_id (required), title, description, status, priority, assigned_to (null to unassign), project_id, parent_id (null to detach), task_list_id, depends_on[], tags[], estimate, actual_minutes, confidence, approved_by, completed_at, deadline, retry_count, version",
|
|
32764
32888
|
delete_task: "delete_task \u2014 Delete a task. Params: task_id, force (skip child check)",
|
|
32765
32889
|
start_task: "start_task \u2014 Mark task in_progress. Params: task_id, version",
|
|
32766
32890
|
complete_task: "complete_task \u2014 Mark task completed. Params: task_id, confidence, completed_at, version",
|
|
@@ -35815,7 +35939,7 @@ var package_default;
|
|
|
35815
35939
|
var init_package = __esm(() => {
|
|
35816
35940
|
package_default = {
|
|
35817
35941
|
name: "@hasna/todos",
|
|
35818
|
-
version: "0.15.
|
|
35942
|
+
version: "0.15.32",
|
|
35819
35943
|
description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
|
|
35820
35944
|
type: "module",
|
|
35821
35945
|
main: "dist/index.js",
|
|
@@ -46947,8 +47071,8 @@ function createPostgresTodosStorageAdapter(options) {
|
|
|
46947
47071
|
resolveRef: (ref) => store.resolveTaskRef(ref),
|
|
46948
47072
|
list: (filter = {}) => store.listTasks(filter),
|
|
46949
47073
|
count: (filter = {}) => store.countTasks(filter),
|
|
46950
|
-
update: (id, input) => updateTask2(id, input, store),
|
|
46951
|
-
delete: (id, context) => store.
|
|
47074
|
+
update: (id, input, context) => updateTask2(id, input, store, context),
|
|
47075
|
+
delete: (id, context) => store.deleteTaskHierarchy(id, context),
|
|
46952
47076
|
start: (id, agentId) => startTask2(id, agentId, store),
|
|
46953
47077
|
complete: (id, agentId, options2) => completeTask2(id, agentId, options2, store),
|
|
46954
47078
|
fail: (id, agentId, reason, options2) => failTask2(id, agentId, reason, options2, store),
|
|
@@ -47481,22 +47605,70 @@ class PostgresJsonRecordStore {
|
|
|
47481
47605
|
return "identical";
|
|
47482
47606
|
throw new Error(divergentAuditHistoryReplayError(value.id));
|
|
47483
47607
|
}
|
|
47484
|
-
async
|
|
47608
|
+
async withTaskParentIntegrityTransaction(fn) {
|
|
47609
|
+
if (typeof this.options.client.transaction !== "function") {
|
|
47610
|
+
throw new Error("TASK_PARENT_ATOMICITY_UNAVAILABLE: PostgreSQL parent writes and task deletion require transaction(callback)");
|
|
47611
|
+
}
|
|
47612
|
+
return this.options.client.transaction(async (client) => {
|
|
47613
|
+
await client.query("/* todos:task-parent-integrity-lock */ SELECT pg_advisory_xact_lock(hashtextextended($1 || ':task-parent-integrity', 0))", [this.service]);
|
|
47614
|
+
return fn(client);
|
|
47615
|
+
});
|
|
47616
|
+
}
|
|
47617
|
+
async upsertTaskWithPlanMembershipGuard(value, guardedPlanIds, explicitProject, context = {}, parentGuard, queryClient) {
|
|
47485
47618
|
const planIds = [...new Set(guardedPlanIds.filter(Boolean))].sort();
|
|
47486
|
-
if (planIds.length === 0)
|
|
47619
|
+
if (planIds.length === 0 && !parentGuard)
|
|
47487
47620
|
return this.upsert("tasks", value, context);
|
|
47488
47621
|
await this.ensureSchema();
|
|
47622
|
+
if (parentGuard && !queryClient) {
|
|
47623
|
+
return this.withTaskParentIntegrityTransaction((client2) => this.upsertTaskWithPlanMembershipGuard(value, guardedPlanIds, explicitProject, context, parentGuard, client2));
|
|
47624
|
+
}
|
|
47625
|
+
const client = queryClient ?? this.options.client;
|
|
47489
47626
|
const updatedAt = value.updated_at;
|
|
47490
47627
|
const targetPlanId = value.plan_id;
|
|
47491
|
-
const result = await
|
|
47628
|
+
const result = await client.query(`/* todos:task-plan-membership-guard todos:task-parent-integrity-guard */ WITH RECURSIVE
|
|
47629
|
+
locked_task AS MATERIALIZED (
|
|
47630
|
+
SELECT payload FROM ${this.tableName}
|
|
47631
|
+
WHERE service = $1 AND object_type = 'tasks' AND object_id = $2 AND deleted_at IS NULL
|
|
47632
|
+
FOR UPDATE
|
|
47633
|
+
),
|
|
47492
47634
|
locked_plans AS MATERIALIZED (
|
|
47493
47635
|
SELECT object_id, payload FROM ${this.tableName}
|
|
47494
47636
|
WHERE service = $1 AND object_type = 'plans' AND deleted_at IS NULL
|
|
47495
47637
|
AND object_id IN (SELECT value FROM jsonb_array_elements_text($7::jsonb))
|
|
47496
47638
|
ORDER BY object_id
|
|
47497
47639
|
FOR UPDATE
|
|
47640
|
+
), parent_chain(object_id, payload, path, cycle) AS (
|
|
47641
|
+
SELECT parent.object_id, parent.payload, ARRAY[parent.object_id], false
|
|
47642
|
+
FROM ${this.tableName} AS parent
|
|
47643
|
+
WHERE $10::boolean
|
|
47644
|
+
AND $11::text IS NOT NULL
|
|
47645
|
+
AND parent.service = $1
|
|
47646
|
+
AND parent.object_type = 'tasks'
|
|
47647
|
+
AND parent.object_id = $11
|
|
47648
|
+
AND parent.deleted_at IS NULL
|
|
47649
|
+
UNION ALL
|
|
47650
|
+
SELECT ancestor.object_id,
|
|
47651
|
+
ancestor.payload,
|
|
47652
|
+
chain.path || ancestor.object_id,
|
|
47653
|
+
ancestor.object_id = ANY(chain.path)
|
|
47654
|
+
FROM parent_chain AS chain
|
|
47655
|
+
JOIN ${this.tableName} AS ancestor
|
|
47656
|
+
ON ancestor.service = $1
|
|
47657
|
+
AND ancestor.object_type = 'tasks'
|
|
47658
|
+
AND ancestor.object_id = chain.payload->>'parent_id'
|
|
47659
|
+
AND ancestor.deleted_at IS NULL
|
|
47660
|
+
WHERE NOT chain.cycle
|
|
47498
47661
|
), validation AS (
|
|
47499
47662
|
SELECT
|
|
47663
|
+
(NOT $10::boolean OR NOT $13::boolean OR EXISTS (SELECT 1 FROM locked_task)) AS task_found,
|
|
47664
|
+
(NOT $10::boolean OR NOT $13::boolean
|
|
47665
|
+
OR (SELECT (payload->>'version')::integer FROM locked_task) = $12::integer) AS version_matches,
|
|
47666
|
+
(NOT $10::boolean OR $11::text IS NULL
|
|
47667
|
+
OR EXISTS (SELECT 1 FROM parent_chain WHERE object_id = $11)) AS parent_found,
|
|
47668
|
+
(NOT $10::boolean OR $11::text IS NULL
|
|
47669
|
+
OR ($11::text <> $2
|
|
47670
|
+
AND NOT EXISTS (SELECT 1 FROM parent_chain WHERE object_id = $2)
|
|
47671
|
+
AND NOT EXISTS (SELECT 1 FROM parent_chain WHERE cycle))) AS parent_acyclic,
|
|
47500
47672
|
(SELECT count(*) FROM locked_plans) = jsonb_array_length($7::jsonb) AS all_plans_found,
|
|
47501
47673
|
($8::text IS NULL OR EXISTS (SELECT 1 FROM locked_plans WHERE object_id = $8)) AS target_plan_found,
|
|
47502
47674
|
(SELECT payload->>'project_id' FROM locked_plans WHERE object_id = $8) AS target_project_id
|
|
@@ -47517,7 +47689,13 @@ class PostgresJsonRecordStore {
|
|
|
47517
47689
|
)
|
|
47518
47690
|
SELECT $1, 'tasks', $2, guarded.payload, $4::timestamptz, NULL, $5, $6
|
|
47519
47691
|
FROM guarded
|
|
47520
|
-
WHERE guarded.
|
|
47692
|
+
WHERE guarded.task_found
|
|
47693
|
+
AND guarded.version_matches
|
|
47694
|
+
AND guarded.parent_found
|
|
47695
|
+
AND guarded.parent_acyclic
|
|
47696
|
+
AND guarded.all_plans_found
|
|
47697
|
+
AND guarded.target_plan_found
|
|
47698
|
+
AND NOT guarded.project_conflict
|
|
47521
47699
|
ON CONFLICT (service, object_type, object_id) DO UPDATE SET
|
|
47522
47700
|
payload = EXCLUDED.payload,
|
|
47523
47701
|
updated_at = EXCLUDED.updated_at,
|
|
@@ -47530,8 +47708,10 @@ class PostgresJsonRecordStore {
|
|
|
47530
47708
|
AND COALESCE(${this.tableName}.version, 0) <= COALESCE(EXCLUDED.version, 0))
|
|
47531
47709
|
RETURNING payload
|
|
47532
47710
|
)
|
|
47533
|
-
SELECT guarded.
|
|
47534
|
-
|
|
47711
|
+
SELECT guarded.task_found, guarded.version_matches, guarded.parent_found, guarded.parent_acyclic,
|
|
47712
|
+
guarded.all_plans_found, guarded.target_plan_found, guarded.project_conflict,
|
|
47713
|
+
(SELECT payload FROM stored) AS payload,
|
|
47714
|
+
(SELECT payload FROM locked_task) AS current_payload
|
|
47535
47715
|
FROM guarded`, [
|
|
47536
47716
|
this.service,
|
|
47537
47717
|
value.id,
|
|
@@ -47541,9 +47721,26 @@ class PostgresJsonRecordStore {
|
|
|
47541
47721
|
numberValue3(value.version),
|
|
47542
47722
|
jsonbParam(planIds),
|
|
47543
47723
|
targetPlanId,
|
|
47544
|
-
explicitProject
|
|
47724
|
+
explicitProject,
|
|
47725
|
+
Boolean(parentGuard),
|
|
47726
|
+
parentGuard?.parentId ?? null,
|
|
47727
|
+
parentGuard?.expectedVersion ?? null,
|
|
47728
|
+
parentGuard?.operation === "update"
|
|
47545
47729
|
]);
|
|
47546
47730
|
const row = result.rows[0];
|
|
47731
|
+
if (parentGuard && !row?.task_found) {
|
|
47732
|
+
throw new TaskNotFoundError(value.id);
|
|
47733
|
+
}
|
|
47734
|
+
if (parentGuard && !row?.version_matches) {
|
|
47735
|
+
const current = row?.current_payload ? payloadRecord2(row.current_payload) : await this.get("tasks", value.id);
|
|
47736
|
+
throw new VersionConflictError(value.id, parentGuard.expectedVersion, current?.version ?? -1);
|
|
47737
|
+
}
|
|
47738
|
+
if (parentGuard && !row?.parent_found && parentGuard.parentId) {
|
|
47739
|
+
throw new TaskNotFoundError(parentGuard.parentId);
|
|
47740
|
+
}
|
|
47741
|
+
if (parentGuard && !row?.parent_acyclic && parentGuard.parentId) {
|
|
47742
|
+
throw new ResourceConflictError("TASK_PARENT_CYCLE", `TASK_PARENT_CYCLE: assigning parent ${parentGuard.parentId} to task ${value.id} would create or retain a parent cycle`);
|
|
47743
|
+
}
|
|
47547
47744
|
if (!row?.all_plans_found || !row.target_plan_found) {
|
|
47548
47745
|
throw new PlanProjectLinkError("PLAN_PROJECT_LINK_PLAN_NOT_FOUND", `Plan membership changed through a missing plan: ${targetPlanId ?? planIds.join(", ")}`, { plan_ids: planIds, target_plan_id: targetPlanId });
|
|
47549
47746
|
}
|
|
@@ -47551,6 +47748,8 @@ class PostgresJsonRecordStore {
|
|
|
47551
47748
|
throw new ResourceConflictError("PLAN_PROJECT_LINK_CONFLICT", `Task project conflicts with linked plan ${targetPlanId}`);
|
|
47552
47749
|
}
|
|
47553
47750
|
if (!row.payload) {
|
|
47751
|
+
if (row.current_payload)
|
|
47752
|
+
return payloadRecord2(row.current_payload);
|
|
47554
47753
|
return await requireRecord("tasks", value.id, this);
|
|
47555
47754
|
}
|
|
47556
47755
|
return payloadRecord2(row.payload);
|
|
@@ -47876,6 +48075,80 @@ class PostgresJsonRecordStore {
|
|
|
47876
48075
|
version: numberValue3(existing["version"])
|
|
47877
48076
|
}, context);
|
|
47878
48077
|
}
|
|
48078
|
+
async deleteTaskHierarchy(id, context = {}) {
|
|
48079
|
+
await this.ensureSchema();
|
|
48080
|
+
return this.withTaskParentIntegrityTransaction(async (client) => {
|
|
48081
|
+
const timestamp4 = new Date().toISOString();
|
|
48082
|
+
const result = await client.query(`/* todos:task-parent-integrity-delete */ WITH RECURSIVE
|
|
48083
|
+
task_tree(object_id, path, cycle) AS (
|
|
48084
|
+
SELECT task.object_id, ARRAY[task.object_id], false
|
|
48085
|
+
FROM ${this.tableName} AS task
|
|
48086
|
+
WHERE task.service = $1
|
|
48087
|
+
AND task.object_type = 'tasks'
|
|
48088
|
+
AND task.object_id = $2
|
|
48089
|
+
AND task.deleted_at IS NULL
|
|
48090
|
+
UNION ALL
|
|
48091
|
+
SELECT child.object_id,
|
|
48092
|
+
tree.path || child.object_id,
|
|
48093
|
+
child.object_id = ANY(tree.path)
|
|
48094
|
+
FROM task_tree AS tree
|
|
48095
|
+
JOIN ${this.tableName} AS child
|
|
48096
|
+
ON child.service = $1
|
|
48097
|
+
AND child.object_type = 'tasks'
|
|
48098
|
+
AND child.payload->>'parent_id' = tree.object_id
|
|
48099
|
+
AND child.deleted_at IS NULL
|
|
48100
|
+
WHERE NOT tree.cycle
|
|
48101
|
+
), tombstoned AS (
|
|
48102
|
+
UPDATE ${this.tableName} AS task
|
|
48103
|
+
SET deleted_at = $3::timestamptz,
|
|
48104
|
+
updated_at = $3::timestamptz,
|
|
48105
|
+
source_machine_id = $4
|
|
48106
|
+
WHERE task.service = $1
|
|
48107
|
+
AND task.object_type = 'tasks'
|
|
48108
|
+
AND task.deleted_at IS NULL
|
|
48109
|
+
AND task.object_id IN (
|
|
48110
|
+
SELECT object_id FROM task_tree WHERE NOT cycle
|
|
48111
|
+
)
|
|
48112
|
+
RETURNING task.object_id
|
|
48113
|
+
), tombstoned_related AS (
|
|
48114
|
+
UPDATE ${this.tableName} AS related
|
|
48115
|
+
SET deleted_at = $3::timestamptz,
|
|
48116
|
+
updated_at = $3::timestamptz,
|
|
48117
|
+
source_machine_id = $4
|
|
48118
|
+
WHERE related.service = $1
|
|
48119
|
+
AND related.deleted_at IS NULL
|
|
48120
|
+
AND (
|
|
48121
|
+
(
|
|
48122
|
+
related.object_type = 'dependencies'
|
|
48123
|
+
AND (
|
|
48124
|
+
related.payload->>'task_id' IN (
|
|
48125
|
+
SELECT object_id FROM task_tree WHERE NOT cycle
|
|
48126
|
+
)
|
|
48127
|
+
OR related.payload->>'depends_on' IN (
|
|
48128
|
+
SELECT object_id FROM task_tree WHERE NOT cycle
|
|
48129
|
+
)
|
|
48130
|
+
)
|
|
48131
|
+
)
|
|
48132
|
+
OR (
|
|
48133
|
+
related.object_type IN ('comments', 'verifications', 'commits', 'refs')
|
|
48134
|
+
AND related.payload->>'task_id' IN (
|
|
48135
|
+
SELECT object_id FROM task_tree WHERE NOT cycle
|
|
48136
|
+
)
|
|
48137
|
+
)
|
|
48138
|
+
)
|
|
48139
|
+
RETURNING related.object_id
|
|
48140
|
+
)
|
|
48141
|
+
SELECT EXISTS (SELECT 1 FROM task_tree WHERE object_id = $2) AS found,
|
|
48142
|
+
(SELECT count(*) FROM tombstoned) AS deleted_count,
|
|
48143
|
+
(SELECT count(*) FROM tombstoned_related) AS related_deleted_count`, [
|
|
48144
|
+
this.service,
|
|
48145
|
+
id,
|
|
48146
|
+
timestamp4,
|
|
48147
|
+
context.requestId ?? this.sourceMachineId ?? null
|
|
48148
|
+
]);
|
|
48149
|
+
return Boolean(result.rows[0]?.found);
|
|
48150
|
+
});
|
|
48151
|
+
}
|
|
47879
48152
|
async getPlanProjectLinkReceipt(receiptId) {
|
|
47880
48153
|
const value = await this.get("plan_project_link_receipts", receiptId);
|
|
47881
48154
|
return value ? assertPlanProjectLinkReceipt(value) : null;
|
|
@@ -48296,9 +48569,8 @@ class PostgresJsonRecordStore {
|
|
|
48296
48569
|
}
|
|
48297
48570
|
async function createTask3(input, store, context) {
|
|
48298
48571
|
const timestamp4 = new Date().toISOString();
|
|
48299
|
-
|
|
48300
|
-
|
|
48301
|
-
}
|
|
48572
|
+
const taskId = randomUUID4();
|
|
48573
|
+
await assertTaskParentIntegrityAsync(taskId, input.parent_id, (id) => store.get("tasks", id));
|
|
48302
48574
|
const linkedPlan = input.plan_id ? await store.get("plans", input.plan_id) : null;
|
|
48303
48575
|
const requestedProjectId = input.project_id ?? context?.projectId ?? null;
|
|
48304
48576
|
if (linkedPlan?.project_id && requestedProjectId && requestedProjectId !== linkedPlan.project_id) {
|
|
@@ -48307,7 +48579,7 @@ async function createTask3(input, store, context) {
|
|
|
48307
48579
|
const effectiveProjectId = linkedPlan?.project_id ?? requestedProjectId;
|
|
48308
48580
|
const shortId = effectiveProjectId ? await nextTaskShortId2(effectiveProjectId, store, context) : null;
|
|
48309
48581
|
const task2 = {
|
|
48310
|
-
id:
|
|
48582
|
+
id: taskId,
|
|
48311
48583
|
short_id: shortId,
|
|
48312
48584
|
project_id: effectiveProjectId,
|
|
48313
48585
|
parent_id: input.parent_id ?? null,
|
|
@@ -48363,15 +48635,16 @@ async function createTask3(input, store, context) {
|
|
|
48363
48635
|
synced_at: null,
|
|
48364
48636
|
archived_at: null
|
|
48365
48637
|
};
|
|
48366
|
-
const storedTask = await store.upsertTaskWithPlanMembershipGuard(task2, task2.plan_id ? [task2.plan_id] : [], input.project_id !== undefined || context?.projectId !== undefined, context);
|
|
48638
|
+
const storedTask = await store.upsertTaskWithPlanMembershipGuard(task2, task2.plan_id ? [task2.plan_id] : [], input.project_id !== undefined || context?.projectId !== undefined, context, input.parent_id ? { operation: "create", expectedVersion: 0, parentId: input.parent_id } : undefined);
|
|
48367
48639
|
await logTaskChange2(storedTask.id, "created", "status", null, storedTask.status, storedTask.assigned_by ?? storedTask.agent_id, store, context);
|
|
48368
48640
|
return storedTask;
|
|
48369
48641
|
}
|
|
48370
|
-
async function updateTask2(id, input, store) {
|
|
48642
|
+
async function updateTask2(id, input, store, context) {
|
|
48371
48643
|
const existing = await requireRecord("tasks", id, store);
|
|
48372
48644
|
if (existing.version !== input.version) {
|
|
48373
|
-
throw new
|
|
48645
|
+
throw new VersionConflictError(id, input.version, existing.version);
|
|
48374
48646
|
}
|
|
48647
|
+
await assertTaskParentIntegrityAsync(id, input.parent_id, (candidateId) => store.get("tasks", candidateId));
|
|
48375
48648
|
const effectivePlanId = input.plan_id !== undefined ? input.plan_id : existing.plan_id;
|
|
48376
48649
|
const linkedPlan = effectivePlanId ? await store.get("plans", effectivePlanId) : null;
|
|
48377
48650
|
if (linkedPlan?.project_id) {
|
|
@@ -48396,10 +48669,19 @@ async function updateTask2(id, input, store) {
|
|
|
48396
48669
|
metadata: input.metadata ?? existing.metadata,
|
|
48397
48670
|
requires_approval: input.requires_approval ?? existing.requires_approval,
|
|
48398
48671
|
task_list_id: input.task_list_id !== undefined ? input.task_list_id : existing.task_list_id,
|
|
48672
|
+
parent_id: input.parent_id !== undefined ? input.parent_id : existing.parent_id,
|
|
48399
48673
|
created_by: existing.created_by,
|
|
48400
48674
|
completed_at: reopened ? null : input.completed_at !== undefined ? input.completed_at : existing.completed_at
|
|
48401
48675
|
};
|
|
48402
|
-
|
|
48676
|
+
const storedTask = await store.upsertTaskWithPlanMembershipGuard(task2, [existing.plan_id, effectivePlanId].filter((planId) => Boolean(planId)), input.project_id !== undefined, context, {
|
|
48677
|
+
operation: "update",
|
|
48678
|
+
expectedVersion: input.version,
|
|
48679
|
+
parentId: input.parent_id !== undefined ? input.parent_id : existing.parent_id
|
|
48680
|
+
});
|
|
48681
|
+
if (input.parent_id !== undefined && input.parent_id !== existing.parent_id) {
|
|
48682
|
+
await logTaskChange2(id, "update", "parent_id", existing.parent_id, input.parent_id, existing.assigned_to ?? existing.agent_id, store, context);
|
|
48683
|
+
}
|
|
48684
|
+
return storedTask;
|
|
48403
48685
|
}
|
|
48404
48686
|
async function startTask2(id, agentId, store) {
|
|
48405
48687
|
const task2 = await requireRecord("tasks", id, store);
|
|
@@ -48463,7 +48745,11 @@ async function patchTask(task2, patch, store) {
|
|
|
48463
48745
|
version: task2.version + 1,
|
|
48464
48746
|
updated_at: new Date().toISOString()
|
|
48465
48747
|
};
|
|
48466
|
-
return store.upsertTaskWithPlanMembershipGuard(updated, [task2.plan_id, updated.plan_id].filter((planId) => Boolean(planId)), Object.prototype.hasOwnProperty.call(patch, "project_id")
|
|
48748
|
+
return store.upsertTaskWithPlanMembershipGuard(updated, [task2.plan_id, updated.plan_id].filter((planId) => Boolean(planId)), Object.prototype.hasOwnProperty.call(patch, "project_id"), {}, {
|
|
48749
|
+
operation: "update",
|
|
48750
|
+
expectedVersion: task2.version,
|
|
48751
|
+
parentId: updated.parent_id
|
|
48752
|
+
});
|
|
48467
48753
|
}
|
|
48468
48754
|
function sameCloudLockHolder(stored, incoming) {
|
|
48469
48755
|
if (!stored || !incoming)
|
|
@@ -49166,6 +49452,7 @@ var init_postgres_adapter = __esm(() => {
|
|
|
49166
49452
|
init_postgres_sync();
|
|
49167
49453
|
init_integrity();
|
|
49168
49454
|
init_redaction();
|
|
49455
|
+
init_task_parent_integrity();
|
|
49169
49456
|
init_audit_history_import();
|
|
49170
49457
|
init_canonical();
|
|
49171
49458
|
TASK_ORDER_BY = `ORDER BY ${TASK_ORDER_TIEBREAK}`;
|
|
@@ -49651,21 +49938,6 @@ var init_postgres = __esm(() => {
|
|
|
49651
49938
|
init_types3();
|
|
49652
49939
|
});
|
|
49653
49940
|
|
|
49654
|
-
// src/project-registration/types.ts
|
|
49655
|
-
var TODOS_PROJECT_REGISTRATION_ROUTE = "todos.project-registration.v1", TODOS_PROJECT_REGISTRATION_CALLER_ROUTE = "projects.full-registration.v1", TodosProjectRegistrationError;
|
|
49656
|
-
var init_types4 = __esm(() => {
|
|
49657
|
-
TodosProjectRegistrationError = class TodosProjectRegistrationError extends Error {
|
|
49658
|
-
code;
|
|
49659
|
-
details;
|
|
49660
|
-
constructor(code, message, details = {}) {
|
|
49661
|
-
super(message);
|
|
49662
|
-
this.code = code;
|
|
49663
|
-
this.details = details;
|
|
49664
|
-
this.name = "TodosProjectRegistrationError";
|
|
49665
|
-
}
|
|
49666
|
-
};
|
|
49667
|
-
});
|
|
49668
|
-
|
|
49669
49941
|
// src/project-registration/postgres.ts
|
|
49670
49942
|
function safeIdentifier(value, field) {
|
|
49671
49943
|
if (!/^[a-z_][a-z0-9_]*$/.test(value)) {
|
|
@@ -49943,6 +50215,7 @@ class PostgresTodosProjectRegistrationTransaction {
|
|
|
49943
50215
|
AND (payload->>'path' = $2 OR payload->>'task_list_id' = $3)
|
|
49944
50216
|
ORDER BY payload->>'created_at' ASC, object_id ASC
|
|
49945
50217
|
LIMIT 1
|
|
50218
|
+
FOR UPDATE
|
|
49946
50219
|
`, [this.service, path, taskListSlug]);
|
|
49947
50220
|
return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
|
|
49948
50221
|
}
|
|
@@ -49953,6 +50226,7 @@ class PostgresTodosProjectRegistrationTransaction {
|
|
|
49953
50226
|
AND payload->>'project_id' = $2 AND payload->>'slug' = $3
|
|
49954
50227
|
ORDER BY payload->>'created_at' ASC, object_id ASC
|
|
49955
50228
|
LIMIT 1
|
|
50229
|
+
FOR UPDATE
|
|
49956
50230
|
`, [this.service, projectId, slug]);
|
|
49957
50231
|
return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
|
|
49958
50232
|
}
|
|
@@ -49963,10 +50237,24 @@ class PostgresTodosProjectRegistrationTransaction {
|
|
|
49963
50237
|
return await this.storage.taskLists.create(input);
|
|
49964
50238
|
}
|
|
49965
50239
|
async getProject(id) {
|
|
49966
|
-
|
|
50240
|
+
const result = await this.client.query(`
|
|
50241
|
+
SELECT payload FROM ${this.tableName}
|
|
50242
|
+
WHERE service = $1 AND object_type = 'projects' AND object_id = $2
|
|
50243
|
+
AND deleted_at IS NULL
|
|
50244
|
+
LIMIT 1
|
|
50245
|
+
FOR SHARE
|
|
50246
|
+
`, [this.service, id]);
|
|
50247
|
+
return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
|
|
49967
50248
|
}
|
|
49968
50249
|
async getTaskList(id) {
|
|
49969
|
-
|
|
50250
|
+
const result = await this.client.query(`
|
|
50251
|
+
SELECT payload FROM ${this.tableName}
|
|
50252
|
+
WHERE service = $1 AND object_type = 'task_lists' AND object_id = $2
|
|
50253
|
+
AND deleted_at IS NULL
|
|
50254
|
+
LIMIT 1
|
|
50255
|
+
FOR SHARE
|
|
50256
|
+
`, [this.service, id]);
|
|
50257
|
+
return result.rows[0] ? parsePayload3(result.rows[0].payload) : null;
|
|
49970
50258
|
}
|
|
49971
50259
|
async lockCompensationWrites() {
|
|
49972
50260
|
await this.client.query(`LOCK TABLE ${this.tableName} IN SHARE ROW EXCLUSIVE MODE`);
|
|
@@ -50046,6 +50334,96 @@ class PostgresTodosProjectRegistrationBackend {
|
|
|
50046
50334
|
async getTaskList(id) {
|
|
50047
50335
|
return (await this.direct()).getTaskList(id);
|
|
50048
50336
|
}
|
|
50337
|
+
async getProjectResourceCollectionRevision(input) {
|
|
50338
|
+
await this.ensureSchema();
|
|
50339
|
+
const result = await this.client.query(`
|
|
50340
|
+
WITH resources(kind_rank, target_id, revision) AS (
|
|
50341
|
+
SELECT 0, object_id, COALESCE(payload->>'updated_at', updated_at::text)
|
|
50342
|
+
FROM ${this.tableName}
|
|
50343
|
+
WHERE service = $1 AND object_type = 'projects'
|
|
50344
|
+
AND deleted_at IS NULL AND object_id = $2
|
|
50345
|
+
UNION ALL
|
|
50346
|
+
SELECT 1, object_id, COALESCE(payload->>'updated_at', updated_at::text)
|
|
50347
|
+
FROM ${this.tableName}
|
|
50348
|
+
WHERE service = $1 AND object_type = 'task_lists'
|
|
50349
|
+
AND deleted_at IS NULL AND object_id = $3
|
|
50350
|
+
AND payload->>'project_id' = $2
|
|
50351
|
+
UNION ALL
|
|
50352
|
+
SELECT 2, object_id, COALESCE(payload->>'updated_at', updated_at::text)
|
|
50353
|
+
FROM ${this.tableName}
|
|
50354
|
+
WHERE $4::boolean AND service = $1 AND object_type = 'plans'
|
|
50355
|
+
AND deleted_at IS NULL AND payload->>'project_id' = $2
|
|
50356
|
+
UNION ALL
|
|
50357
|
+
SELECT 3, object_id, COALESCE(payload->>'updated_at', updated_at::text)
|
|
50358
|
+
FROM ${this.tableName}
|
|
50359
|
+
WHERE $4::boolean AND service = $1 AND object_type = 'tasks'
|
|
50360
|
+
AND deleted_at IS NULL AND payload->>'project_id' = $2
|
|
50361
|
+
)
|
|
50362
|
+
SELECT 'md5:' || md5(COALESCE(string_agg(
|
|
50363
|
+
kind_rank::text || chr(31) || target_id || chr(31) || revision,
|
|
50364
|
+
chr(30) ORDER BY kind_rank ASC, target_id ASC
|
|
50365
|
+
), '')) AS revision
|
|
50366
|
+
FROM resources
|
|
50367
|
+
`, [
|
|
50368
|
+
this.service,
|
|
50369
|
+
input.todos_project_id,
|
|
50370
|
+
input.task_list_id,
|
|
50371
|
+
input.include_anchors
|
|
50372
|
+
]);
|
|
50373
|
+
const revision = result.rows[0]?.revision;
|
|
50374
|
+
if (!revision) {
|
|
50375
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", "could not derive the hosted project-resource collection revision");
|
|
50376
|
+
}
|
|
50377
|
+
return revision;
|
|
50378
|
+
}
|
|
50379
|
+
async listProjectResourceCandidates(input) {
|
|
50380
|
+
await this.ensureSchema();
|
|
50381
|
+
const afterRank = input.after?.kind_rank ?? -1;
|
|
50382
|
+
const afterId = input.after?.target_id ?? "";
|
|
50383
|
+
const result = await this.client.query(`
|
|
50384
|
+
WITH resources(kind, kind_rank, target_id, parent_id, revision) AS (
|
|
50385
|
+
SELECT 'project'::text, 0, object_id, NULL::text,
|
|
50386
|
+
COALESCE(payload->>'updated_at', updated_at::text)
|
|
50387
|
+
FROM ${this.tableName}
|
|
50388
|
+
WHERE service = $1 AND object_type = 'projects'
|
|
50389
|
+
AND deleted_at IS NULL AND object_id = $2
|
|
50390
|
+
UNION ALL
|
|
50391
|
+
SELECT 'task_list'::text, 1, object_id, payload->>'project_id',
|
|
50392
|
+
COALESCE(payload->>'updated_at', updated_at::text)
|
|
50393
|
+
FROM ${this.tableName}
|
|
50394
|
+
WHERE service = $1 AND object_type = 'task_lists'
|
|
50395
|
+
AND deleted_at IS NULL AND object_id = $3
|
|
50396
|
+
AND payload->>'project_id' = $2
|
|
50397
|
+
UNION ALL
|
|
50398
|
+
SELECT 'plan'::text, 2, object_id, payload->>'project_id',
|
|
50399
|
+
COALESCE(payload->>'updated_at', updated_at::text)
|
|
50400
|
+
FROM ${this.tableName}
|
|
50401
|
+
WHERE $4::boolean AND service = $1 AND object_type = 'plans'
|
|
50402
|
+
AND deleted_at IS NULL AND payload->>'project_id' = $2
|
|
50403
|
+
UNION ALL
|
|
50404
|
+
SELECT 'task'::text, 3, object_id,
|
|
50405
|
+
COALESCE(payload->>'plan_id', payload->>'project_id'),
|
|
50406
|
+
COALESCE(payload->>'updated_at', updated_at::text)
|
|
50407
|
+
FROM ${this.tableName}
|
|
50408
|
+
WHERE $4::boolean AND service = $1 AND object_type = 'tasks'
|
|
50409
|
+
AND deleted_at IS NULL AND payload->>'project_id' = $2
|
|
50410
|
+
)
|
|
50411
|
+
SELECT kind, kind_rank, target_id, parent_id, revision
|
|
50412
|
+
FROM resources
|
|
50413
|
+
WHERE kind_rank > $5 OR (kind_rank = $5 AND target_id > $6)
|
|
50414
|
+
ORDER BY kind_rank ASC, target_id ASC
|
|
50415
|
+
LIMIT $7
|
|
50416
|
+
`, [
|
|
50417
|
+
this.service,
|
|
50418
|
+
input.todos_project_id,
|
|
50419
|
+
input.task_list_id,
|
|
50420
|
+
input.include_anchors,
|
|
50421
|
+
afterRank,
|
|
50422
|
+
afterId,
|
|
50423
|
+
input.limit
|
|
50424
|
+
]);
|
|
50425
|
+
return result.rows;
|
|
50426
|
+
}
|
|
50049
50427
|
}
|
|
50050
50428
|
var init_postgres2 = __esm(() => {
|
|
50051
50429
|
init_postgres_adapter();
|
|
@@ -50789,35 +51167,68 @@ function projectRecord(project) {
|
|
|
50789
51167
|
return {
|
|
50790
51168
|
target_id: project.id,
|
|
50791
51169
|
revision: project.updated_at,
|
|
50792
|
-
digest:
|
|
50793
|
-
id: project.id,
|
|
50794
|
-
name: project.name,
|
|
50795
|
-
path: project.path,
|
|
50796
|
-
description: project.description,
|
|
50797
|
-
task_list_id: project.task_list_id,
|
|
50798
|
-
task_prefix: project.task_prefix,
|
|
50799
|
-
task_counter: project.task_counter,
|
|
50800
|
-
created_at: project.created_at,
|
|
50801
|
-
updated_at: project.updated_at
|
|
50802
|
-
})
|
|
51170
|
+
digest: projectRegistrationDigest(project)
|
|
50803
51171
|
};
|
|
50804
51172
|
}
|
|
50805
51173
|
function taskListRecord(taskList) {
|
|
50806
51174
|
return {
|
|
50807
51175
|
target_id: taskList.id,
|
|
50808
51176
|
revision: taskList.updated_at,
|
|
50809
|
-
digest:
|
|
50810
|
-
|
|
50811
|
-
|
|
50812
|
-
|
|
50813
|
-
|
|
50814
|
-
|
|
50815
|
-
|
|
50816
|
-
|
|
50817
|
-
|
|
51177
|
+
digest: taskListRegistrationDigest(taskList)
|
|
51178
|
+
};
|
|
51179
|
+
}
|
|
51180
|
+
function boundExistingProjectRecord(project) {
|
|
51181
|
+
return {
|
|
51182
|
+
target_id: project.id,
|
|
51183
|
+
revision: project.created_at,
|
|
51184
|
+
digest: projectRegistrationDigest({
|
|
51185
|
+
...project,
|
|
51186
|
+
updated_at: project.created_at
|
|
51187
|
+
})
|
|
51188
|
+
};
|
|
51189
|
+
}
|
|
51190
|
+
function boundExistingTaskListRecord(taskList) {
|
|
51191
|
+
return {
|
|
51192
|
+
target_id: taskList.id,
|
|
51193
|
+
revision: taskList.created_at,
|
|
51194
|
+
digest: taskListRegistrationDigest({
|
|
51195
|
+
...taskList,
|
|
51196
|
+
updated_at: taskList.created_at
|
|
50818
51197
|
})
|
|
50819
51198
|
};
|
|
50820
51199
|
}
|
|
51200
|
+
function projectRegistrationDigest(project) {
|
|
51201
|
+
return digestProjectRegistrationValue({
|
|
51202
|
+
id: project.id,
|
|
51203
|
+
name: project.name,
|
|
51204
|
+
path: project.path,
|
|
51205
|
+
description: project.description,
|
|
51206
|
+
task_list_id: project.task_list_id,
|
|
51207
|
+
task_prefix: project.task_prefix,
|
|
51208
|
+
task_counter: project.task_counter,
|
|
51209
|
+
created_at: project.created_at,
|
|
51210
|
+
updated_at: project.updated_at
|
|
51211
|
+
});
|
|
51212
|
+
}
|
|
51213
|
+
function taskListRegistrationDigest(taskList) {
|
|
51214
|
+
return digestProjectRegistrationValue({
|
|
51215
|
+
id: taskList.id,
|
|
51216
|
+
project_id: taskList.project_id,
|
|
51217
|
+
slug: taskList.slug,
|
|
51218
|
+
name: taskList.name,
|
|
51219
|
+
description: taskList.description,
|
|
51220
|
+
metadata: taskList.metadata,
|
|
51221
|
+
created_at: taskList.created_at,
|
|
51222
|
+
updated_at: taskList.updated_at
|
|
51223
|
+
});
|
|
51224
|
+
}
|
|
51225
|
+
function canonicalValuesEqual(left, right) {
|
|
51226
|
+
try {
|
|
51227
|
+
return canonicalProjectRegistrationJson(left) === canonicalProjectRegistrationJson(right);
|
|
51228
|
+
} catch {
|
|
51229
|
+
return false;
|
|
51230
|
+
}
|
|
51231
|
+
}
|
|
50821
51232
|
function receiptId(input) {
|
|
50822
51233
|
return `tpr_${digestProjectRegistrationValue(input).slice(0, 40)}`;
|
|
50823
51234
|
}
|
|
@@ -50837,6 +51248,29 @@ function assertCapabilityRequest(request, capability) {
|
|
|
50837
51248
|
}
|
|
50838
51249
|
}
|
|
50839
51250
|
function normalizedCallDigest(request) {
|
|
51251
|
+
return digestProjectRegistrationValue({
|
|
51252
|
+
authority_route: request.authority_route,
|
|
51253
|
+
package_version: request.package_version,
|
|
51254
|
+
authority_id: request.authority_id,
|
|
51255
|
+
tenant_id: request.tenant_id,
|
|
51256
|
+
corpus_id: request.corpus_id,
|
|
51257
|
+
operation_id: request.operation_id,
|
|
51258
|
+
step_id: request.step_id,
|
|
51259
|
+
resource_kind: request.resource_kind,
|
|
51260
|
+
direction: request.direction,
|
|
51261
|
+
target_selector: request.target_selector,
|
|
51262
|
+
idempotency_key: request.idempotency_key,
|
|
51263
|
+
request_digest: request.request_digest,
|
|
51264
|
+
precondition_digest: request.precondition_digest,
|
|
51265
|
+
project_id: request.project_id,
|
|
51266
|
+
project_slug: request.project_slug,
|
|
51267
|
+
project_name: request.project_name,
|
|
51268
|
+
desired: request.desired,
|
|
51269
|
+
bind_existing: request.bind_existing === true,
|
|
51270
|
+
accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
|
|
51271
|
+
});
|
|
51272
|
+
}
|
|
51273
|
+
function legacyNormalizedCallDigestBeforeBindExisting(request) {
|
|
50840
51274
|
return digestProjectRegistrationValue({
|
|
50841
51275
|
authority_route: request.authority_route,
|
|
50842
51276
|
package_version: request.package_version,
|
|
@@ -50858,6 +51292,11 @@ function normalizedCallDigest(request) {
|
|
|
50858
51292
|
accepted_receipt_id: request.accepted_receipt?.receipt_id ?? null
|
|
50859
51293
|
});
|
|
50860
51294
|
}
|
|
51295
|
+
function acceptedCallMatches(request, accepted, callDigest = normalizedCallDigest(request)) {
|
|
51296
|
+
if (accepted.normalized_call_digest === callDigest)
|
|
51297
|
+
return true;
|
|
51298
|
+
return request.bind_existing !== true && accepted.normalized_call_digest === legacyNormalizedCallDigestBeforeBindExisting(request);
|
|
51299
|
+
}
|
|
50861
51300
|
function assertCommonRequest(request, capability) {
|
|
50862
51301
|
assertBounds(request);
|
|
50863
51302
|
assertResourceKind(request.resource_kind);
|
|
@@ -50899,6 +51338,9 @@ function assertCommonRequest(request, capability) {
|
|
|
50899
51338
|
if (!request.desired || typeof request.desired !== "object" || Array.isArray(request.desired)) {
|
|
50900
51339
|
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "desired must be a JSON object");
|
|
50901
51340
|
}
|
|
51341
|
+
if (request.bind_existing !== undefined && typeof request.bind_existing !== "boolean") {
|
|
51342
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "bind_existing must be boolean when supplied");
|
|
51343
|
+
}
|
|
50902
51344
|
const expectedKey = deriveTodosProjectRegistrationIdempotencyKey({
|
|
50903
51345
|
operation_id: request.operation_id,
|
|
50904
51346
|
step_id: request.step_id,
|
|
@@ -50920,7 +51362,7 @@ function assertForwardRequest(request, capability) {
|
|
|
50920
51362
|
const expectedRequestDigest = digestProjectRegistrationValue(request.desired);
|
|
50921
51363
|
const expectedPreconditionDigest = digestProjectRegistrationValue({
|
|
50922
51364
|
target_selector: request.target_selector,
|
|
50923
|
-
expected: "absent"
|
|
51365
|
+
expected: request.bind_existing === true ? "absent_or_matching_existing" : "absent"
|
|
50924
51366
|
});
|
|
50925
51367
|
if (request.request_digest !== expectedRequestDigest || request.precondition_digest !== expectedPreconditionDigest) {
|
|
50926
51368
|
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_DIGEST_MISMATCH", "request_digest or precondition_digest does not match normalized forward semantics", {
|
|
@@ -50999,7 +51441,7 @@ function receiptBase(request, callDigest, capability) {
|
|
|
50999
51441
|
normalized_call_digest: callDigest
|
|
51000
51442
|
};
|
|
51001
51443
|
}
|
|
51002
|
-
function makeAcceptedReceipt(request, callDigest, capability, record, createdAt2) {
|
|
51444
|
+
function makeAcceptedReceipt(request, callDigest, capability, record, createdAt2, createdByOperation = true) {
|
|
51003
51445
|
return makeReceipt({
|
|
51004
51446
|
...receiptBase(request, callDigest, capability),
|
|
51005
51447
|
outcome: "accepted",
|
|
@@ -51009,7 +51451,7 @@ function makeAcceptedReceipt(request, callDigest, capability, record, createdAt2
|
|
|
51009
51451
|
result_digest: record.digest,
|
|
51010
51452
|
duplicate_of_receipt_id: null,
|
|
51011
51453
|
accepted_receipt_id: request.direction === "inverse" ? request.accepted_receipt.receipt_id : null,
|
|
51012
|
-
created_by_operation:
|
|
51454
|
+
created_by_operation: createdByOperation
|
|
51013
51455
|
}, createdAt2);
|
|
51014
51456
|
}
|
|
51015
51457
|
function makeDuplicateReceipt(request, callDigest, capability, accepted, createdAt2) {
|
|
@@ -51071,6 +51513,53 @@ function bindingFor(request, callDigest, timestamp4, capability) {
|
|
|
51071
51513
|
updated_at: timestamp4
|
|
51072
51514
|
};
|
|
51073
51515
|
}
|
|
51516
|
+
function encodeProjectResourceCursor(input) {
|
|
51517
|
+
return Buffer.from(JSON.stringify({
|
|
51518
|
+
version: PROJECT_RESOURCE_CURSOR_VERSION,
|
|
51519
|
+
...input
|
|
51520
|
+
}), "utf8").toString("base64url");
|
|
51521
|
+
}
|
|
51522
|
+
function decodeProjectResourceCursor(cursor, expected) {
|
|
51523
|
+
if (!cursor)
|
|
51524
|
+
return null;
|
|
51525
|
+
let parsed;
|
|
51526
|
+
try {
|
|
51527
|
+
parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
51528
|
+
} catch {
|
|
51529
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "cursor is not a valid project-resource cursor");
|
|
51530
|
+
}
|
|
51531
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
51532
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "cursor is not a valid project-resource cursor");
|
|
51533
|
+
}
|
|
51534
|
+
const value = parsed;
|
|
51535
|
+
if (value["version"] !== PROJECT_RESOURCE_CURSOR_VERSION || value["source_project_id"] !== expected.source_project_id || value["include_anchors"] !== expected.include_anchors || !Number.isSafeInteger(value["kind_rank"]) || Number(value["kind_rank"]) < 0 || Number(value["kind_rank"]) > 3 || typeof value["target_id"] !== "string" || !UUID_PATTERN.test(value["target_id"]) || typeof value["collection_revision"] !== "string" || value["collection_revision"].length < 16 || value["collection_revision"].length > 128) {
|
|
51536
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "cursor does not match this project-resource query");
|
|
51537
|
+
}
|
|
51538
|
+
return {
|
|
51539
|
+
kind_rank: Number(value["kind_rank"]),
|
|
51540
|
+
target_id: value["target_id"],
|
|
51541
|
+
collection_revision: value["collection_revision"]
|
|
51542
|
+
};
|
|
51543
|
+
}
|
|
51544
|
+
function projectResourceFromCandidate(sourceProjectId, candidate) {
|
|
51545
|
+
const scope = candidate.kind === "project" || candidate.kind === "task_list" ? "collection" : "resource";
|
|
51546
|
+
return {
|
|
51547
|
+
source_project_id: sourceProjectId,
|
|
51548
|
+
kind: candidate.kind,
|
|
51549
|
+
scope,
|
|
51550
|
+
target_id: candidate.target_id,
|
|
51551
|
+
parent_id: candidate.parent_id,
|
|
51552
|
+
revision: candidate.revision,
|
|
51553
|
+
digest: digestProjectRegistrationValue({
|
|
51554
|
+
source_project_id: sourceProjectId,
|
|
51555
|
+
kind: candidate.kind,
|
|
51556
|
+
scope,
|
|
51557
|
+
target_id: candidate.target_id,
|
|
51558
|
+
parent_id: candidate.parent_id,
|
|
51559
|
+
revision: candidate.revision
|
|
51560
|
+
})
|
|
51561
|
+
};
|
|
51562
|
+
}
|
|
51074
51563
|
|
|
51075
51564
|
class PackageOwnedTodosProjectRegistrationAuthority {
|
|
51076
51565
|
backend;
|
|
@@ -51092,6 +51581,10 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51092
51581
|
immutable_receipts: true,
|
|
51093
51582
|
exact_terminal_lookup: true,
|
|
51094
51583
|
exact_readback: true,
|
|
51584
|
+
bind_existing_adoption: true,
|
|
51585
|
+
prior_registration_adoption_validation: true,
|
|
51586
|
+
project_resource_enumeration: true,
|
|
51587
|
+
project_resource_page_limit: PROJECT_RESOURCE_PAGE_LIMIT,
|
|
51095
51588
|
conditional_inverse: true,
|
|
51096
51589
|
ambiguous_outcome_reconciliation: true
|
|
51097
51590
|
};
|
|
@@ -51152,7 +51645,7 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51152
51645
|
if (!accepted2) {
|
|
51153
51646
|
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "duplicate receipt points to a missing accepted receipt");
|
|
51154
51647
|
}
|
|
51155
|
-
if (accepted2
|
|
51648
|
+
if (!acceptedCallMatches(request, accepted2, callDigest)) {
|
|
51156
51649
|
return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted2.target_id });
|
|
51157
51650
|
}
|
|
51158
51651
|
return this.duplicateFor(transaction, request, callDigest, accepted2);
|
|
@@ -51166,7 +51659,7 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51166
51659
|
});
|
|
51167
51660
|
if (!accepted)
|
|
51168
51661
|
return null;
|
|
51169
|
-
if (accepted
|
|
51662
|
+
if (acceptedCallMatches(request, accepted, callDigest)) {
|
|
51170
51663
|
return this.duplicateFor(transaction, request, callDigest, accepted);
|
|
51171
51664
|
}
|
|
51172
51665
|
return this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted.target_id });
|
|
@@ -51177,6 +51670,12 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51177
51670
|
const slug2 = taskListSlug(request.project_slug);
|
|
51178
51671
|
const conflict2 = await transaction.findProjectConflict(path, slug2);
|
|
51179
51672
|
if (conflict2) {
|
|
51673
|
+
if (request.bind_existing === true && conflict2.path === path && conflict2.task_list_id === slug2) {
|
|
51674
|
+
return {
|
|
51675
|
+
record: boundExistingProjectRecord(conflict2),
|
|
51676
|
+
created_by_operation: false
|
|
51677
|
+
};
|
|
51678
|
+
}
|
|
51180
51679
|
return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict2.id });
|
|
51181
51680
|
}
|
|
51182
51681
|
await this.fault("before_object_write", request);
|
|
@@ -51188,7 +51687,10 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51188
51687
|
task_prefix: deterministicTaskPrefix(request.project_slug)
|
|
51189
51688
|
});
|
|
51190
51689
|
await this.fault("after_object_write", request);
|
|
51191
|
-
return
|
|
51690
|
+
return {
|
|
51691
|
+
record: projectRecord(project),
|
|
51692
|
+
created_by_operation: true
|
|
51693
|
+
};
|
|
51192
51694
|
}
|
|
51193
51695
|
const todosProjectId = String(request.desired["todos_project_id"]);
|
|
51194
51696
|
const sourceBinding = await transaction.getBinding(authorityScope(this.capabilityValue), "project", request.project_id);
|
|
@@ -51202,6 +51704,12 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51202
51704
|
const slug = taskListSlug(request.project_slug);
|
|
51203
51705
|
const conflict = await transaction.findTaskListConflict(todosProjectId, slug);
|
|
51204
51706
|
if (conflict) {
|
|
51707
|
+
if (request.bind_existing === true && conflict.project_id === todosProjectId && conflict.slug === slug) {
|
|
51708
|
+
return {
|
|
51709
|
+
record: boundExistingTaskListRecord(conflict),
|
|
51710
|
+
created_by_operation: false
|
|
51711
|
+
};
|
|
51712
|
+
}
|
|
51205
51713
|
return this.terminalFor(transaction, request, normalizedCallDigest(request), "target_already_exists", { targetId: conflict.id });
|
|
51206
51714
|
}
|
|
51207
51715
|
await this.fault("before_object_write", request);
|
|
@@ -51218,7 +51726,10 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51218
51726
|
if (taskList.project_id !== todosProjectId) {
|
|
51219
51727
|
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_CONFLICT", "task-list create did not preserve the exact full Todos project id");
|
|
51220
51728
|
}
|
|
51221
|
-
return
|
|
51729
|
+
return {
|
|
51730
|
+
record: taskListRecord(taskList),
|
|
51731
|
+
created_by_operation: true
|
|
51732
|
+
};
|
|
51222
51733
|
}
|
|
51223
51734
|
async create(request) {
|
|
51224
51735
|
const startedAt = Date.now();
|
|
@@ -51240,9 +51751,9 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51240
51751
|
const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp4, this.capabilityValue));
|
|
51241
51752
|
if (!claimed) {
|
|
51242
51753
|
const binding = await transaction.getBinding(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector);
|
|
51243
|
-
if (binding?.state === "accepted" && binding.
|
|
51754
|
+
if (binding?.state === "accepted" && binding.accepted_receipt_id) {
|
|
51244
51755
|
const accepted2 = await transaction.getReceiptById(binding.accepted_receipt_id);
|
|
51245
|
-
if (accepted2) {
|
|
51756
|
+
if (accepted2 && binding.normalized_call_digest === accepted2.normalized_call_digest && acceptedCallMatches(request, accepted2, callDigest)) {
|
|
51246
51757
|
return this.duplicateFor(transaction, request, callDigest, accepted2);
|
|
51247
51758
|
}
|
|
51248
51759
|
}
|
|
@@ -51253,15 +51764,15 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51253
51764
|
await transaction.setBindingTerminal(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, this.now());
|
|
51254
51765
|
return recordOrTerminal;
|
|
51255
51766
|
}
|
|
51256
|
-
const accepted = makeAcceptedReceipt(request, callDigest, this.capabilityValue, recordOrTerminal, this.now());
|
|
51767
|
+
const accepted = makeAcceptedReceipt(request, callDigest, this.capabilityValue, recordOrTerminal.record, this.now(), recordOrTerminal.created_by_operation);
|
|
51257
51768
|
await this.fault("before_receipt_write", request);
|
|
51258
51769
|
const stored = await insertDeterministicReceipt(transaction, accepted);
|
|
51259
51770
|
await this.fault("after_receipt_write", request);
|
|
51260
51771
|
await transaction.setBindingAccepted(authorityScope(this.capabilityValue), request.resource_kind, request.target_selector, {
|
|
51261
|
-
target_id: recordOrTerminal.target_id,
|
|
51772
|
+
target_id: recordOrTerminal.record.target_id,
|
|
51262
51773
|
accepted_receipt_id: stored.receipt_id,
|
|
51263
|
-
result_revision: recordOrTerminal.revision,
|
|
51264
|
-
result_digest: recordOrTerminal.digest,
|
|
51774
|
+
result_revision: recordOrTerminal.record.revision,
|
|
51775
|
+
result_digest: recordOrTerminal.record.digest,
|
|
51265
51776
|
updated_at: this.now()
|
|
51266
51777
|
});
|
|
51267
51778
|
return stored;
|
|
@@ -51309,7 +51820,7 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51309
51820
|
direction: request.direction
|
|
51310
51821
|
});
|
|
51311
51822
|
if (accepted) {
|
|
51312
|
-
return accepted
|
|
51823
|
+
return acceptedCallMatches(request, accepted, callDigest) ? this.duplicateFor(transaction, request, callDigest, accepted) : this.terminalFor(transaction, request, callDigest, "operation_step_semantics_changed", { targetId: accepted.target_id });
|
|
51313
51824
|
}
|
|
51314
51825
|
const timestamp4 = this.now();
|
|
51315
51826
|
const claimed = await transaction.claimBinding(bindingFor(request, callDigest, timestamp4, this.capabilityValue));
|
|
@@ -51389,6 +51900,164 @@ class PackageOwnedTodosProjectRegistrationAuthority {
|
|
|
51389
51900
|
}
|
|
51390
51901
|
return withResponseControl({ receipt: publicReceipt(receipt) }, request, startedAt);
|
|
51391
51902
|
}
|
|
51903
|
+
async listProjectResources(request) {
|
|
51904
|
+
const sourceProjectId = requireString(request.source_project_id, "source_project_id", { min: 16, max: 128, pattern: WORKSPACE_ID_PATTERN });
|
|
51905
|
+
if (!Number.isSafeInteger(request.limit) || request.limit <= 0 || request.limit > PROJECT_RESOURCE_PAGE_LIMIT) {
|
|
51906
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_BOUNDS", `limit must be an integer from 1 to ${PROJECT_RESOURCE_PAGE_LIMIT}`);
|
|
51907
|
+
}
|
|
51908
|
+
if (request.include_anchors !== undefined && typeof request.include_anchors !== "boolean") {
|
|
51909
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_INVALID_INPUT", "include_anchors must be boolean when supplied");
|
|
51910
|
+
}
|
|
51911
|
+
const includeAnchors = request.include_anchors === true;
|
|
51912
|
+
const projectBinding = await this.backend.getBinding(authorityScope(this.capabilityValue), "project", sourceProjectId);
|
|
51913
|
+
if (!projectBinding || projectBinding.state !== "accepted" || !projectBinding.target_id) {
|
|
51914
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", "no accepted Todos project binding exists for this exact Projects workspace id", { source_project_id: sourceProjectId });
|
|
51915
|
+
}
|
|
51916
|
+
const taskListBinding = await this.backend.getBinding(authorityScope(this.capabilityValue), "task_list", `${projectBinding.target_id}:default`);
|
|
51917
|
+
if (!taskListBinding || taskListBinding.state !== "accepted" || !taskListBinding.target_id) {
|
|
51918
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_RECORD_NOT_FOUND", "no accepted canonical task-list binding exists for this exact Todos project id", {
|
|
51919
|
+
source_project_id: sourceProjectId,
|
|
51920
|
+
todos_project_id: projectBinding.target_id
|
|
51921
|
+
});
|
|
51922
|
+
}
|
|
51923
|
+
const cursor = decodeProjectResourceCursor(request.cursor, {
|
|
51924
|
+
source_project_id: sourceProjectId,
|
|
51925
|
+
include_anchors: includeAnchors
|
|
51926
|
+
});
|
|
51927
|
+
const collectionInput = {
|
|
51928
|
+
todos_project_id: projectBinding.target_id,
|
|
51929
|
+
task_list_id: taskListBinding.target_id,
|
|
51930
|
+
include_anchors: includeAnchors
|
|
51931
|
+
};
|
|
51932
|
+
const collectionRevision = await this.backend.getProjectResourceCollectionRevision(collectionInput);
|
|
51933
|
+
if (cursor && cursor.collection_revision !== collectionRevision) {
|
|
51934
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_COLLECTION_CHANGED", "project-resource collection changed during pagination; restart from the first page", {
|
|
51935
|
+
source_project_id: sourceProjectId,
|
|
51936
|
+
expected_collection_revision: cursor.collection_revision,
|
|
51937
|
+
current_collection_revision: collectionRevision
|
|
51938
|
+
});
|
|
51939
|
+
}
|
|
51940
|
+
const candidates = await this.backend.listProjectResourceCandidates({
|
|
51941
|
+
...collectionInput,
|
|
51942
|
+
after: cursor ? { kind_rank: cursor.kind_rank, target_id: cursor.target_id } : null,
|
|
51943
|
+
limit: request.limit + 1
|
|
51944
|
+
});
|
|
51945
|
+
const verifiedCollectionRevision = await this.backend.getProjectResourceCollectionRevision(collectionInput);
|
|
51946
|
+
if (verifiedCollectionRevision !== collectionRevision) {
|
|
51947
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_COLLECTION_CHANGED", "project-resource collection changed while producing a page; restart from the first page", {
|
|
51948
|
+
source_project_id: sourceProjectId,
|
|
51949
|
+
expected_collection_revision: collectionRevision,
|
|
51950
|
+
current_collection_revision: verifiedCollectionRevision
|
|
51951
|
+
});
|
|
51952
|
+
}
|
|
51953
|
+
const hasMore = candidates.length > request.limit;
|
|
51954
|
+
const pageCandidates = candidates.slice(0, request.limit);
|
|
51955
|
+
const resources = pageCandidates.map((candidate) => projectResourceFromCandidate(sourceProjectId, candidate));
|
|
51956
|
+
const last = pageCandidates.at(-1);
|
|
51957
|
+
return {
|
|
51958
|
+
authority: "todos",
|
|
51959
|
+
route: this.capabilityValue.route,
|
|
51960
|
+
package_version: this.capabilityValue.package_version,
|
|
51961
|
+
authority_id: this.capabilityValue.authority_id,
|
|
51962
|
+
tenant_id: this.capabilityValue.tenant_id,
|
|
51963
|
+
corpus_id: this.capabilityValue.corpus_id,
|
|
51964
|
+
source_project_id: sourceProjectId,
|
|
51965
|
+
todos_project_id: projectBinding.target_id,
|
|
51966
|
+
task_list_id: taskListBinding.target_id,
|
|
51967
|
+
include_anchors: includeAnchors,
|
|
51968
|
+
collection_revision: collectionRevision,
|
|
51969
|
+
limit: request.limit,
|
|
51970
|
+
count: resources.length,
|
|
51971
|
+
resources,
|
|
51972
|
+
has_more: hasMore,
|
|
51973
|
+
next_cursor: hasMore && last ? encodeProjectResourceCursor({
|
|
51974
|
+
source_project_id: sourceProjectId,
|
|
51975
|
+
include_anchors: includeAnchors,
|
|
51976
|
+
collection_revision: collectionRevision,
|
|
51977
|
+
kind_rank: last.kind_rank,
|
|
51978
|
+
target_id: last.target_id
|
|
51979
|
+
}) : null,
|
|
51980
|
+
complete: !hasMore,
|
|
51981
|
+
truncated: false
|
|
51982
|
+
};
|
|
51983
|
+
}
|
|
51984
|
+
async validatePriorRegistrationAdoption(sourceRequest, sourceReceipt, currentRecord) {
|
|
51985
|
+
const startedAt = Date.now();
|
|
51986
|
+
if (!sourceRequest || typeof sourceRequest !== "object" || Array.isArray(sourceRequest) || !sourceReceipt || typeof sourceReceipt !== "object" || Array.isArray(sourceReceipt) || !currentRecord || typeof currentRecord !== "object" || Array.isArray(currentRecord)) {
|
|
51987
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ADOPTION_REJECTED", "source request, source receipt, and current record must be present objects");
|
|
51988
|
+
}
|
|
51989
|
+
requireString(sourceRequest.package_version, "package_version", {
|
|
51990
|
+
max: 128,
|
|
51991
|
+
pattern: PACKAGE_VERSION_PATTERN
|
|
51992
|
+
});
|
|
51993
|
+
assertForwardRequest(sourceRequest, {
|
|
51994
|
+
...this.capabilityValue,
|
|
51995
|
+
package_version: sourceRequest.package_version
|
|
51996
|
+
});
|
|
51997
|
+
const validation = await this.backend.transaction(async (transaction) => {
|
|
51998
|
+
const storedSource = await transaction.getReceiptById(sourceReceipt.receipt_id);
|
|
51999
|
+
if (!storedSource || !canonicalValuesEqual(publicReceipt(storedSource), sourceReceipt) || storedSource.outcome !== "accepted" && storedSource.outcome !== "duplicate_of_accepted") {
|
|
52000
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ADOPTION_REJECTED", "source receipt is not an exact immutable accepted or duplicate receipt", { receipt_id: sourceReceipt.receipt_id });
|
|
52001
|
+
}
|
|
52002
|
+
const accepted = storedSource.outcome === "accepted" ? storedSource : storedSource.duplicate_of_receipt_id ? await transaction.getReceiptById(storedSource.duplicate_of_receipt_id) : null;
|
|
52003
|
+
if (!accepted || accepted.outcome !== "accepted" || !accepted.target_id || !accepted.result_revision || !accepted.result_digest) {
|
|
52004
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ADOPTION_REJECTED", "source receipt does not resolve to one complete accepted receipt", { receipt_id: sourceReceipt.receipt_id });
|
|
52005
|
+
}
|
|
52006
|
+
const receiptLineageMatches = (receipt) => receipt.authority === "todos" && receipt.route === sourceRequest.authority_route && receipt.package_version === sourceRequest.package_version && receipt.authority_id === sourceRequest.authority_id && receipt.tenant_id === sourceRequest.tenant_id && receipt.corpus_id === sourceRequest.corpus_id && receipt.operation_id === sourceRequest.operation_id && receipt.step_id === sourceRequest.step_id && receipt.resource_kind === sourceRequest.resource_kind && receipt.direction === "forward" && receipt.target_selector === sourceRequest.target_selector && receipt.idempotency_key === sourceRequest.idempotency_key && receipt.request_digest === sourceRequest.request_digest && receipt.precondition_digest === sourceRequest.precondition_digest && acceptedCallMatches(sourceRequest, receipt);
|
|
52007
|
+
if (!receiptLineageMatches(storedSource) || !receiptLineageMatches(accepted) || storedSource.outcome === "duplicate_of_accepted" && (storedSource.duplicate_of_receipt_id !== accepted.receipt_id || storedSource.target_id !== accepted.target_id || storedSource.result_revision !== accepted.result_revision || storedSource.result_digest !== accepted.result_digest)) {
|
|
52008
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ADOPTION_REJECTED", "receipt authority, operation, request, precondition, or normalized-call lineage changed");
|
|
52009
|
+
}
|
|
52010
|
+
const binding = await transaction.getBinding(authorityScope(this.capabilityValue), sourceRequest.resource_kind, sourceRequest.target_selector);
|
|
52011
|
+
if (!binding || binding.state !== "accepted" || binding.operation_id !== sourceRequest.operation_id || binding.step_id !== sourceRequest.step_id || binding.direction !== "forward" || binding.idempotency_key !== sourceRequest.idempotency_key || binding.request_digest !== sourceRequest.request_digest || binding.precondition_digest !== sourceRequest.precondition_digest || binding.normalized_call_digest !== accepted.normalized_call_digest || binding.target_id !== accepted.target_id || binding.accepted_receipt_id !== accepted.receipt_id || binding.result_revision !== accepted.result_revision || binding.result_digest !== accepted.result_digest) {
|
|
52012
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ADOPTION_REJECTED", "accepted binding does not match the exact accepted receipt and source lineage");
|
|
52013
|
+
}
|
|
52014
|
+
const current = sourceRequest.resource_kind === "project" ? await transaction.getProject(accepted.target_id) : await transaction.getTaskList(accepted.target_id);
|
|
52015
|
+
if (!current || !canonicalValuesEqual(current, currentRecord) || current.id !== accepted.target_id || current.created_at !== accepted.result_revision) {
|
|
52016
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ADOPTION_REJECTED", "current record does not match the accepted target incarnation", { target_id: accepted.target_id });
|
|
52017
|
+
}
|
|
52018
|
+
let stableMatch = false;
|
|
52019
|
+
if (sourceRequest.resource_kind === "task_list") {
|
|
52020
|
+
stableMatch = taskListRegistrationDigest({
|
|
52021
|
+
...current,
|
|
52022
|
+
updated_at: accepted.result_revision
|
|
52023
|
+
}) === accepted.result_digest;
|
|
52024
|
+
} else {
|
|
52025
|
+
const project = current;
|
|
52026
|
+
if (!Number.isSafeInteger(project.task_counter) || project.task_counter < 0) {
|
|
52027
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ADOPTION_REJECTED", "current project task counter is not a valid monotonic registration field");
|
|
52028
|
+
}
|
|
52029
|
+
for (let priorTaskCounter = 0;priorTaskCounter <= project.task_counter; priorTaskCounter += 1) {
|
|
52030
|
+
if (Date.now() - startedAt > sourceRequest.time_budget_ms) {
|
|
52031
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_TIME_BUDGET_EXCEEDED", "prior registration adoption validation exceeded its time budget");
|
|
52032
|
+
}
|
|
52033
|
+
if (projectRegistrationDigest({
|
|
52034
|
+
...project,
|
|
52035
|
+
task_counter: priorTaskCounter,
|
|
52036
|
+
updated_at: accepted.result_revision
|
|
52037
|
+
}) === accepted.result_digest) {
|
|
52038
|
+
stableMatch = true;
|
|
52039
|
+
break;
|
|
52040
|
+
}
|
|
52041
|
+
}
|
|
52042
|
+
}
|
|
52043
|
+
if (!stableMatch) {
|
|
52044
|
+
throw new TodosProjectRegistrationError("TODOS_PROJECT_REGISTRATION_ADOPTION_REJECTED", "stable project-registration fields changed after the accepted receipt", { target_id: accepted.target_id });
|
|
52045
|
+
}
|
|
52046
|
+
return {
|
|
52047
|
+
valid: true,
|
|
52048
|
+
resource_kind: sourceRequest.resource_kind,
|
|
52049
|
+
target_id: accepted.target_id,
|
|
52050
|
+
source_receipt_id: storedSource.receipt_id,
|
|
52051
|
+
accepted_receipt_id: accepted.receipt_id,
|
|
52052
|
+
source_outcome: storedSource.outcome,
|
|
52053
|
+
created_at: current.created_at,
|
|
52054
|
+
current_revision: current.updated_at,
|
|
52055
|
+
accepted_result_digest: accepted.result_digest
|
|
52056
|
+
};
|
|
52057
|
+
});
|
|
52058
|
+
assertWithinBounds(validation, sourceRequest, startedAt);
|
|
52059
|
+
return validation;
|
|
52060
|
+
}
|
|
51392
52061
|
async storedAcceptedReceipt(request, supplied) {
|
|
51393
52062
|
const stored = await this.backend.getReceiptById(supplied.receipt_id);
|
|
51394
52063
|
if (!stored || stored.outcome !== "accepted" || !stored.created_by_operation || canonicalProjectRegistrationJson(publicReceipt(stored)) !== canonicalProjectRegistrationJson(supplied)) {
|
|
@@ -51557,7 +52226,7 @@ function createPostgresTodosProjectRegistrationAuthority(client, options = {}) {
|
|
|
51557
52226
|
cursorTableName
|
|
51558
52227
|
}), authorityOptions);
|
|
51559
52228
|
}
|
|
51560
|
-
var UUID_PATTERN, WORKSPACE_ID_PATTERN, OPERATION_PATTERN, STEP_PATTERN, AUTHORITY_ROUTE_PATTERN, PACKAGE_VERSION_PATTERN, SHA256_PATTERN, IDEMPOTENCY_PATTERN, WriteBoundaryError;
|
|
52229
|
+
var UUID_PATTERN, WORKSPACE_ID_PATTERN, OPERATION_PATTERN, STEP_PATTERN, AUTHORITY_ROUTE_PATTERN, PACKAGE_VERSION_PATTERN, SHA256_PATTERN, IDEMPOTENCY_PATTERN, PROJECT_RESOURCE_PAGE_LIMIT = 500, PROJECT_RESOURCE_CURSOR_VERSION = 1, WriteBoundaryError;
|
|
51561
52230
|
var init_authority = __esm(() => {
|
|
51562
52231
|
init_package_version();
|
|
51563
52232
|
init_postgres2();
|
|
@@ -51627,6 +52296,20 @@ async function handleTodosProjectRegistrationHttpRequest(req, url, authority, ba
|
|
|
51627
52296
|
if ((action === "" || action === "capability") && method === "GET") {
|
|
51628
52297
|
return json({ capability: await authority.capability() });
|
|
51629
52298
|
}
|
|
52299
|
+
if (action === "resources" && method === "GET") {
|
|
52300
|
+
const sourceProjectId = url.searchParams.get("source_project_id");
|
|
52301
|
+
const limit = Number(url.searchParams.get("limit") ?? "100");
|
|
52302
|
+
const includeAnchorsRaw = url.searchParams.get("include_anchors");
|
|
52303
|
+
const includeAnchors = includeAnchorsRaw === null ? false : includeAnchorsRaw === "true" ? true : includeAnchorsRaw === "false" ? false : includeAnchorsRaw;
|
|
52304
|
+
return json({
|
|
52305
|
+
page: await authority.listProjectResources({
|
|
52306
|
+
source_project_id: sourceProjectId,
|
|
52307
|
+
limit,
|
|
52308
|
+
include_anchors: includeAnchors,
|
|
52309
|
+
cursor: url.searchParams.get("cursor") ?? undefined
|
|
52310
|
+
})
|
|
52311
|
+
});
|
|
52312
|
+
}
|
|
51630
52313
|
if (method !== "POST")
|
|
51631
52314
|
return json({ error: "method not allowed" }, 405);
|
|
51632
52315
|
const body = await readJson(req);
|
|
@@ -51649,6 +52332,12 @@ async function handleTodosProjectRegistrationHttpRequest(req, url, authority, ba
|
|
|
51649
52332
|
record: await authority.readExact(body)
|
|
51650
52333
|
});
|
|
51651
52334
|
}
|
|
52335
|
+
if (action === "validate-prior-adoption") {
|
|
52336
|
+
const input = body;
|
|
52337
|
+
return json({
|
|
52338
|
+
validation: await authority.validatePriorRegistrationAdoption(input.source_request, input.source_receipt, input.current_record)
|
|
52339
|
+
});
|
|
52340
|
+
}
|
|
51652
52341
|
if (action === "compensate") {
|
|
51653
52342
|
return json({
|
|
51654
52343
|
receipt: await authority.compensate(body)
|
|
@@ -51681,6 +52370,7 @@ async function handleTodosProjectRegistrationHttpRequest(req, url, authority, ba
|
|
|
51681
52370
|
var JSON_HEADERS;
|
|
51682
52371
|
var init_http2 = __esm(() => {
|
|
51683
52372
|
init_types4();
|
|
52373
|
+
init_adoption_validation();
|
|
51684
52374
|
JSON_HEADERS = { "Content-Type": "application/json" };
|
|
51685
52375
|
});
|
|
51686
52376
|
|
|
@@ -51688,13 +52378,14 @@ var init_http2 = __esm(() => {
|
|
|
51688
52378
|
var init_project_registration = __esm(() => {
|
|
51689
52379
|
init_authority();
|
|
51690
52380
|
init_http2();
|
|
52381
|
+
init_adoption_validation();
|
|
51691
52382
|
init_postgres2();
|
|
51692
52383
|
init_sqlite();
|
|
51693
52384
|
init_types4();
|
|
51694
52385
|
});
|
|
51695
52386
|
|
|
51696
52387
|
// src/task-manifest/types.ts
|
|
51697
|
-
var TODOS_TASK_MANIFEST_ROUTE = "todos.task-manifest.v1", TODOS_TASK_MANIFEST_SCHEMA_VERSION = 1, TodosTaskManifestError;
|
|
52388
|
+
var TODOS_TASK_MANIFEST_ROUTE = "todos.task-manifest.v1", TODOS_TASK_MANIFEST_CALLER_ROUTE = "accounts.task-manifest.v1", TODOS_TASK_MANIFEST_SCHEMA_VERSION = 1, TODOS_TASK_MANIFEST_PLAN_SLUG_PROVENANCE = "deterministic-v1", TodosTaskManifestError;
|
|
51698
52389
|
var init_types5 = __esm(() => {
|
|
51699
52390
|
TodosTaskManifestError = class TodosTaskManifestError extends Error {
|
|
51700
52391
|
code;
|
|
@@ -51781,7 +52472,7 @@ function parseTodosTaskManifestBindingLookup(input) {
|
|
|
51781
52472
|
}
|
|
51782
52473
|
return parsed.data;
|
|
51783
52474
|
}
|
|
51784
|
-
var TODOS_TASK_MANIFEST_BOUNDS, key, identifier, uuid2, scalar, boundedScalarRecord = (limit, field) => exports_external.record(exports_external.string().max(200), scalar).superRefine((value, context) => {
|
|
52475
|
+
var TODOS_TASK_MANIFEST_BOUNDS, key, identifier, digest, idempotencyKey, uuid2, scalar, boundedScalarRecord = (limit, field) => exports_external.record(exports_external.string().max(200), scalar).superRefine((value, context) => {
|
|
51785
52476
|
if (Object.keys(value).length > limit) {
|
|
51786
52477
|
context.addIssue({ code: exports_external.ZodIssueCode.custom, message: `${field} exceeds ${limit} fields` });
|
|
51787
52478
|
}
|
|
@@ -51803,6 +52494,8 @@ var init_schema2 = __esm(() => {
|
|
|
51803
52494
|
};
|
|
51804
52495
|
key = exports_external.string().min(1).max(96).regex(/^[a-z][a-z0-9_-]*$/);
|
|
51805
52496
|
identifier = exports_external.string().min(1).max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._:-]*$/);
|
|
52497
|
+
digest = exports_external.string().length(64).regex(/^[0-9a-f]{64}$/);
|
|
52498
|
+
idempotencyKey = exports_external.string().length(52).regex(/^tmk_[0-9a-f]{48}$/);
|
|
51806
52499
|
uuid2 = exports_external.string().uuid();
|
|
51807
52500
|
scalar = exports_external.union([exports_external.string().max(4096), exports_external.number().finite(), exports_external.boolean(), exports_external.null()]);
|
|
51808
52501
|
comment = exports_external.object({
|
|
@@ -51840,7 +52533,9 @@ var init_schema2 = __esm(() => {
|
|
|
51840
52533
|
schema = exports_external.object({
|
|
51841
52534
|
version: exports_external.literal(1),
|
|
51842
52535
|
operation_id: identifier,
|
|
51843
|
-
|
|
52536
|
+
step_id: identifier,
|
|
52537
|
+
idempotency_key: idempotencyKey,
|
|
52538
|
+
precondition_digest: digest,
|
|
51844
52539
|
project_id: uuid2,
|
|
51845
52540
|
task_list_id: uuid2.optional(),
|
|
51846
52541
|
if_binding_version: exports_external.number().int().min(0).optional(),
|
|
@@ -51856,7 +52551,10 @@ var init_schema2 = __esm(() => {
|
|
|
51856
52551
|
}).strict();
|
|
51857
52552
|
compensationSchema = exports_external.object({
|
|
51858
52553
|
receipt_id: uuid2,
|
|
51859
|
-
|
|
52554
|
+
operation_id: identifier,
|
|
52555
|
+
step_id: identifier,
|
|
52556
|
+
idempotency_key: idempotencyKey,
|
|
52557
|
+
precondition_digest: digest,
|
|
51860
52558
|
if_binding_version: exports_external.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
|
|
51861
52559
|
}).strict();
|
|
51862
52560
|
bindingLookupSchema = exports_external.object({
|
|
@@ -51874,6 +52572,7 @@ function taskManifestPlanSlug(manifest, planId) {
|
|
|
51874
52572
|
const base = normalizeSlug(manifest.plan.key) || normalizeSlug(manifest.plan.name) || "plan";
|
|
51875
52573
|
return `${base}-${planId}`;
|
|
51876
52574
|
}
|
|
52575
|
+
var TASK_MANIFEST_DETERMINISTIC_SLUG_PROVENANCE = "deterministic-v1";
|
|
51877
52576
|
var init_plan_slug = () => {};
|
|
51878
52577
|
|
|
51879
52578
|
// src/task-manifest/backend.ts
|
|
@@ -51887,11 +52586,13 @@ function validateTaskManifestBindingLookupRows(rows, tenantId, planId) {
|
|
|
51887
52586
|
const row = rows[0];
|
|
51888
52587
|
const bindingVersion = Number(row.binding_version);
|
|
51889
52588
|
const state = row.state;
|
|
51890
|
-
if (row.binding_tenant_id !== tenantId || row.receipt_tenant_id !== tenantId || row.binding_plan_id !== planId || row.receipt_plan_id !== planId || row.receipt_authority !== "todos" || row.receipt_route !== "todos.task-manifest.v1" || Number(row.receipt_schema_version) !== 1 || row.receipt_kind !== "apply" || row.binding_operation_id !== row.receipt_operation_id || typeof row.apply_receipt_id !== "string" || !UUID_PATTERN2.test(row.apply_receipt_id) || !Number.isSafeInteger(bindingVersion) || bindingVersion < 1 || state !== "applied" && state !== "compensated") {
|
|
52589
|
+
if (row.binding_tenant_id !== tenantId || row.receipt_tenant_id !== tenantId || row.binding_plan_id !== planId || row.receipt_plan_id !== planId || row.receipt_authority !== "todos" || row.receipt_route !== "todos.task-manifest.v1" || Number(row.receipt_schema_version) !== 1 || row.receipt_kind !== "apply" || row.binding_operation_id !== row.receipt_operation_id || row.binding_step_id !== row.receipt_step_id || typeof row.binding_operation_id !== "string" || typeof row.binding_step_id !== "string" || typeof row.apply_receipt_id !== "string" || !UUID_PATTERN2.test(row.apply_receipt_id) || !Number.isSafeInteger(bindingVersion) || bindingVersion < 1 || state !== "applied" && state !== "compensated") {
|
|
51891
52590
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_LOOKUP_CONFLICT", "Task-manifest binding and immutable apply receipt disagree", { plan_id: planId });
|
|
51892
52591
|
}
|
|
51893
52592
|
return {
|
|
51894
52593
|
plan_id: planId,
|
|
52594
|
+
operation_id: row.binding_operation_id,
|
|
52595
|
+
step_id: row.binding_step_id,
|
|
51895
52596
|
apply_receipt_id: row.apply_receipt_id,
|
|
51896
52597
|
binding_version: bindingVersion,
|
|
51897
52598
|
state
|
|
@@ -51944,9 +52645,15 @@ function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
|
|
|
51944
52645
|
schema_version integer NOT NULL CHECK(schema_version = 1),
|
|
51945
52646
|
kind text NOT NULL CHECK(kind IN ('apply', 'compensate')),
|
|
51946
52647
|
operation_id text NOT NULL,
|
|
52648
|
+
step_id text NOT NULL,
|
|
51947
52649
|
idempotency_key text NOT NULL,
|
|
51948
52650
|
request_digest text NOT NULL,
|
|
52651
|
+
precondition_digest text NOT NULL,
|
|
51949
52652
|
result_digest text NOT NULL,
|
|
52653
|
+
slug_provenance text,
|
|
52654
|
+
outcome text NOT NULL CHECK(outcome IN ('accepted', 'terminal_nonacceptance')),
|
|
52655
|
+
reason text,
|
|
52656
|
+
duplicate_of_receipt_id text,
|
|
51950
52657
|
binding_version integer NOT NULL,
|
|
51951
52658
|
apply_receipt_id text,
|
|
51952
52659
|
manifest_json jsonb,
|
|
@@ -51958,12 +52665,28 @@ function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
|
|
|
51958
52665
|
ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
|
|
51959
52666
|
`ALTER TABLE todos_task_manifest_receipts
|
|
51960
52667
|
ALTER COLUMN tenant_id DROP DEFAULT`,
|
|
52668
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
52669
|
+
ADD COLUMN IF NOT EXISTS slug_provenance text`,
|
|
52670
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
52671
|
+
ADD COLUMN IF NOT EXISTS step_id text NOT NULL DEFAULT 'legacy-apply'`,
|
|
52672
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
52673
|
+
ADD COLUMN IF NOT EXISTS precondition_digest text NOT NULL DEFAULT '${"0".repeat(64)}'`,
|
|
52674
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
52675
|
+
ADD COLUMN IF NOT EXISTS outcome text NOT NULL DEFAULT 'accepted'`,
|
|
52676
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
52677
|
+
ADD COLUMN IF NOT EXISTS reason text`,
|
|
52678
|
+
`ALTER TABLE todos_task_manifest_receipts
|
|
52679
|
+
ADD COLUMN IF NOT EXISTS duplicate_of_receipt_id text`,
|
|
51961
52680
|
`CREATE TABLE IF NOT EXISTS todos_task_manifest_bindings (
|
|
51962
52681
|
operation_id text PRIMARY KEY,
|
|
51963
52682
|
tenant_id text NOT NULL,
|
|
52683
|
+
step_id text NOT NULL,
|
|
51964
52684
|
idempotency_key text NOT NULL UNIQUE,
|
|
51965
52685
|
request_digest text NOT NULL,
|
|
52686
|
+
precondition_digest text NOT NULL,
|
|
51966
52687
|
result_digest text NOT NULL,
|
|
52688
|
+
slug_provenance text,
|
|
52689
|
+
outcome text NOT NULL CHECK(outcome IN ('accepted', 'terminal_nonacceptance')),
|
|
51967
52690
|
apply_receipt_id text NOT NULL UNIQUE REFERENCES todos_task_manifest_receipts(receipt_id),
|
|
51968
52691
|
manifest_json jsonb NOT NULL,
|
|
51969
52692
|
result_json jsonb NOT NULL,
|
|
@@ -51977,6 +52700,14 @@ function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
|
|
|
51977
52700
|
ADD COLUMN IF NOT EXISTS tenant_id text NOT NULL DEFAULT ${tenantDefault}`,
|
|
51978
52701
|
`ALTER TABLE todos_task_manifest_bindings
|
|
51979
52702
|
ALTER COLUMN tenant_id DROP DEFAULT`,
|
|
52703
|
+
`ALTER TABLE todos_task_manifest_bindings
|
|
52704
|
+
ADD COLUMN IF NOT EXISTS slug_provenance text`,
|
|
52705
|
+
`ALTER TABLE todos_task_manifest_bindings
|
|
52706
|
+
ADD COLUMN IF NOT EXISTS step_id text NOT NULL DEFAULT 'legacy-apply'`,
|
|
52707
|
+
`ALTER TABLE todos_task_manifest_bindings
|
|
52708
|
+
ADD COLUMN IF NOT EXISTS precondition_digest text NOT NULL DEFAULT '${"0".repeat(64)}'`,
|
|
52709
|
+
`ALTER TABLE todos_task_manifest_bindings
|
|
52710
|
+
ADD COLUMN IF NOT EXISTS outcome text NOT NULL DEFAULT 'accepted'`,
|
|
51980
52711
|
`CREATE TABLE IF NOT EXISTS todos_task_manifest_outbox (
|
|
51981
52712
|
id text PRIMARY KEY,
|
|
51982
52713
|
apply_receipt_id text NOT NULL REFERENCES todos_task_manifest_receipts(receipt_id),
|
|
@@ -51988,10 +52719,37 @@ function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
|
|
|
51988
52719
|
created_at timestamptz NOT NULL,
|
|
51989
52720
|
delivered_at timestamptz
|
|
51990
52721
|
)`,
|
|
52722
|
+
`CREATE TABLE IF NOT EXISTS todos_task_manifest_terminal_receipts (
|
|
52723
|
+
receipt_id text PRIMARY KEY,
|
|
52724
|
+
tenant_id text NOT NULL,
|
|
52725
|
+
authority text NOT NULL CHECK(authority = 'todos'),
|
|
52726
|
+
route text NOT NULL,
|
|
52727
|
+
schema_version integer NOT NULL CHECK(schema_version = 1),
|
|
52728
|
+
kind text NOT NULL CHECK(kind IN ('apply', 'compensate')),
|
|
52729
|
+
operation_id text NOT NULL,
|
|
52730
|
+
step_id text NOT NULL,
|
|
52731
|
+
idempotency_key text NOT NULL,
|
|
52732
|
+
request_digest text NOT NULL,
|
|
52733
|
+
precondition_digest text NOT NULL,
|
|
52734
|
+
result_digest text NOT NULL,
|
|
52735
|
+
outcome text NOT NULL CHECK(outcome = 'terminal_nonacceptance'),
|
|
52736
|
+
reason text NOT NULL,
|
|
52737
|
+
binding_version integer NOT NULL,
|
|
52738
|
+
apply_receipt_id text,
|
|
52739
|
+
manifest_json jsonb,
|
|
52740
|
+
result_json jsonb NOT NULL,
|
|
52741
|
+
created_at timestamptz NOT NULL
|
|
52742
|
+
)`,
|
|
51991
52743
|
`CREATE INDEX IF NOT EXISTS todos_task_manifest_outbox_receipt_idx
|
|
51992
52744
|
ON todos_task_manifest_outbox(apply_receipt_id, status)`,
|
|
51993
52745
|
`CREATE INDEX IF NOT EXISTS todos_task_manifest_receipts_tenant_idx
|
|
51994
52746
|
ON todos_task_manifest_receipts(tenant_id, receipt_id, kind)`,
|
|
52747
|
+
`DROP INDEX IF EXISTS todos_task_manifest_terminal_receipts_lookup_idx`,
|
|
52748
|
+
`DROP INDEX IF EXISTS todos_task_manifest_terminal_receipts_identity_idx`,
|
|
52749
|
+
`CREATE INDEX IF NOT EXISTS todos_task_manifest_terminal_receipts_lookup_idx
|
|
52750
|
+
ON todos_task_manifest_terminal_receipts(tenant_id, kind, operation_id, step_id)`,
|
|
52751
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS todos_task_manifest_terminal_receipts_identity_idx
|
|
52752
|
+
ON todos_task_manifest_terminal_receipts(tenant_id, kind, operation_id, step_id)`,
|
|
51995
52753
|
`CREATE INDEX IF NOT EXISTS todos_task_manifest_bindings_tenant_plan_idx
|
|
51996
52754
|
ON todos_task_manifest_bindings(
|
|
51997
52755
|
tenant_id,
|
|
@@ -52004,6 +52762,10 @@ function postgresTodosTaskManifestSchemaSql(tenantId = "default") {
|
|
|
52004
52762
|
`DROP TRIGGER IF EXISTS todos_task_manifest_receipts_immutable ON todos_task_manifest_receipts`,
|
|
52005
52763
|
`CREATE TRIGGER todos_task_manifest_receipts_immutable
|
|
52006
52764
|
BEFORE UPDATE OR DELETE ON todos_task_manifest_receipts
|
|
52765
|
+
FOR EACH ROW EXECUTE FUNCTION todos_task_manifest_receipts_immutable()`,
|
|
52766
|
+
`DROP TRIGGER IF EXISTS todos_task_manifest_terminal_receipts_immutable ON todos_task_manifest_terminal_receipts`,
|
|
52767
|
+
`CREATE TRIGGER todos_task_manifest_terminal_receipts_immutable
|
|
52768
|
+
BEFORE UPDATE OR DELETE ON todos_task_manifest_terminal_receipts
|
|
52007
52769
|
FOR EACH ROW EXECUTE FUNCTION todos_task_manifest_receipts_immutable()`
|
|
52008
52770
|
];
|
|
52009
52771
|
}
|
|
@@ -52028,6 +52790,39 @@ function safeIdentifier2(value, field) {
|
|
|
52028
52790
|
function parseJson2(value) {
|
|
52029
52791
|
return typeof value === "string" ? JSON.parse(value) : value;
|
|
52030
52792
|
}
|
|
52793
|
+
function parseApplyResult(value, duplicate) {
|
|
52794
|
+
const parsed = parseJson2(value);
|
|
52795
|
+
return {
|
|
52796
|
+
...parsed,
|
|
52797
|
+
duplicate,
|
|
52798
|
+
receipt: {
|
|
52799
|
+
...parsed.receipt,
|
|
52800
|
+
step_id: parsed.receipt.step_id ?? "legacy-apply",
|
|
52801
|
+
precondition_digest: parsed.receipt.precondition_digest ?? "0".repeat(64),
|
|
52802
|
+
outcome: parsed.receipt.outcome ?? "accepted",
|
|
52803
|
+
reason: parsed.receipt.reason ?? null,
|
|
52804
|
+
duplicate_of_receipt_id: parsed.receipt.duplicate_of_receipt_id ?? null
|
|
52805
|
+
}
|
|
52806
|
+
};
|
|
52807
|
+
}
|
|
52808
|
+
function validatePostgresPlanSlug(manifest, planId, slug, provenance) {
|
|
52809
|
+
if (provenance === TASK_MANIFEST_DETERMINISTIC_SLUG_PROVENANCE) {
|
|
52810
|
+
const expected = taskManifestPlanSlug(manifest, planId);
|
|
52811
|
+
if (slug !== expected) {
|
|
52812
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: plan slug changed since apply");
|
|
52813
|
+
}
|
|
52814
|
+
return expected;
|
|
52815
|
+
}
|
|
52816
|
+
if (provenance !== null && provenance !== undefined) {
|
|
52817
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: unknown plan slug provenance");
|
|
52818
|
+
}
|
|
52819
|
+
if (slug === null || slug === undefined)
|
|
52820
|
+
return null;
|
|
52821
|
+
if (slug !== null && slug !== undefined) {
|
|
52822
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: legacy PostgreSQL plan slug must be NULL");
|
|
52823
|
+
}
|
|
52824
|
+
return null;
|
|
52825
|
+
}
|
|
52031
52826
|
function timestamp4(value) {
|
|
52032
52827
|
return value instanceof Date ? value.toISOString() : new Date(String(value)).toISOString();
|
|
52033
52828
|
}
|
|
@@ -52035,6 +52830,41 @@ function fault(faults, point) {
|
|
|
52035
52830
|
if (faults.points.has(point))
|
|
52036
52831
|
throw new Error(`Injected task-manifest fault at ${point}`);
|
|
52037
52832
|
}
|
|
52833
|
+
function terminalApplyResult(input, reason) {
|
|
52834
|
+
const receipt = {
|
|
52835
|
+
receipt_id: input.terminal_receipt_id,
|
|
52836
|
+
authority: "todos",
|
|
52837
|
+
route: "todos.task-manifest.v1",
|
|
52838
|
+
schema_version: 1,
|
|
52839
|
+
kind: "apply",
|
|
52840
|
+
operation_id: input.manifest.operation_id,
|
|
52841
|
+
step_id: input.manifest.step_id,
|
|
52842
|
+
idempotency_key: input.manifest.idempotency_key,
|
|
52843
|
+
request_digest: input.request_digest,
|
|
52844
|
+
precondition_digest: input.manifest.precondition_digest,
|
|
52845
|
+
result_digest: canonicalDigest({
|
|
52846
|
+
outcome: "terminal_nonacceptance",
|
|
52847
|
+
reason,
|
|
52848
|
+
operation_id: input.manifest.operation_id,
|
|
52849
|
+
step_id: input.manifest.step_id,
|
|
52850
|
+
request_digest: input.request_digest
|
|
52851
|
+
}),
|
|
52852
|
+
outcome: "terminal_nonacceptance",
|
|
52853
|
+
reason,
|
|
52854
|
+
duplicate_of_receipt_id: null,
|
|
52855
|
+
binding_version: 0,
|
|
52856
|
+
apply_receipt_id: null,
|
|
52857
|
+
created_at: input.now
|
|
52858
|
+
};
|
|
52859
|
+
return {
|
|
52860
|
+
duplicate: false,
|
|
52861
|
+
receipt,
|
|
52862
|
+
graph: input.graph,
|
|
52863
|
+
readback: { plans: 0, tasks: 0, dependencies: 0, comments: 0, verifications: 0, complete: true },
|
|
52864
|
+
outbox_ids: [],
|
|
52865
|
+
result_digest: receipt.result_digest
|
|
52866
|
+
};
|
|
52867
|
+
}
|
|
52038
52868
|
function receiptFromRow3(row) {
|
|
52039
52869
|
return {
|
|
52040
52870
|
receipt_id: String(row["receipt_id"]),
|
|
@@ -52043,9 +52873,14 @@ function receiptFromRow3(row) {
|
|
|
52043
52873
|
schema_version: 1,
|
|
52044
52874
|
kind: row["kind"],
|
|
52045
52875
|
operation_id: String(row["operation_id"]),
|
|
52876
|
+
step_id: String(row["step_id"] ?? "legacy-apply"),
|
|
52046
52877
|
idempotency_key: String(row["idempotency_key"]),
|
|
52047
52878
|
request_digest: String(row["request_digest"]),
|
|
52879
|
+
precondition_digest: String(row["precondition_digest"] ?? "0".repeat(64)),
|
|
52048
52880
|
result_digest: String(row["result_digest"]),
|
|
52881
|
+
outcome: row["outcome"] ?? "accepted",
|
|
52882
|
+
reason: row["reason"] == null ? null : row["reason"],
|
|
52883
|
+
duplicate_of_receipt_id: row["duplicate_of_receipt_id"] == null ? null : String(row["duplicate_of_receipt_id"]),
|
|
52049
52884
|
binding_version: Number(row["binding_version"]),
|
|
52050
52885
|
apply_receipt_id: row["apply_receipt_id"] == null ? null : String(row["apply_receipt_id"]),
|
|
52051
52886
|
created_at: timestamp4(row["created_at"])
|
|
@@ -52163,46 +52998,89 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52163
52998
|
now4
|
|
52164
52999
|
]);
|
|
52165
53000
|
}
|
|
53001
|
+
async persistTerminal(tx, input, reason) {
|
|
53002
|
+
const result = terminalApplyResult(input, reason);
|
|
53003
|
+
const resultJson = canonicalJson(result);
|
|
53004
|
+
await tx.query(`INSERT INTO todos_task_manifest_terminal_receipts (
|
|
53005
|
+
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, step_id,
|
|
53006
|
+
idempotency_key, request_digest, precondition_digest, result_digest, outcome, reason,
|
|
53007
|
+
binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
53008
|
+
) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'apply', $3, $4, $5, $6, $7, $8,
|
|
53009
|
+
'terminal_nonacceptance', $9, 0, NULL, $10::jsonb, $11::jsonb, $12)
|
|
53010
|
+
ON CONFLICT (tenant_id, kind, operation_id, step_id) DO NOTHING`, [
|
|
53011
|
+
result.receipt.receipt_id,
|
|
53012
|
+
this.tenantId,
|
|
53013
|
+
input.manifest.operation_id,
|
|
53014
|
+
input.manifest.step_id,
|
|
53015
|
+
input.manifest.idempotency_key,
|
|
53016
|
+
input.request_digest,
|
|
53017
|
+
input.manifest.precondition_digest,
|
|
53018
|
+
result.receipt.result_digest,
|
|
53019
|
+
reason,
|
|
53020
|
+
canonicalJson(input.manifest),
|
|
53021
|
+
resultJson,
|
|
53022
|
+
input.now
|
|
53023
|
+
]);
|
|
53024
|
+
const stored = await tx.query(`SELECT receipt_id, result_json
|
|
53025
|
+
FROM todos_task_manifest_terminal_receipts
|
|
53026
|
+
WHERE tenant_id = $1 AND kind = 'apply'
|
|
53027
|
+
AND (receipt_id = $2 OR (operation_id = $3 AND step_id = $4))
|
|
53028
|
+
ORDER BY created_at ASC, receipt_id ASC
|
|
53029
|
+
LIMIT 1`, [this.tenantId, result.receipt.receipt_id, input.manifest.operation_id, input.manifest.step_id]);
|
|
53030
|
+
return stored.rows[0] ? parseApplyResult(stored.rows[0]["result_json"], stored.rows[0]["receipt_id"] !== result.receipt.receipt_id) : result;
|
|
53031
|
+
}
|
|
52166
53032
|
async apply(input, faults) {
|
|
52167
53033
|
await this.ensureSchema();
|
|
52168
53034
|
return this.client.transaction(async (tx) => {
|
|
52169
53035
|
const { manifest } = input;
|
|
52170
53036
|
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1F${manifest.operation_id}`]);
|
|
52171
53037
|
await tx.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${this.service}\x1Fidempotency\x1F${manifest.idempotency_key}`]);
|
|
53038
|
+
const terminal = await tx.query(`SELECT result_json FROM todos_task_manifest_terminal_receipts
|
|
53039
|
+
WHERE tenant_id = $1
|
|
53040
|
+
AND kind = 'apply'
|
|
53041
|
+
AND (receipt_id = $2 OR (operation_id = $3 AND step_id = $4))
|
|
53042
|
+
ORDER BY created_at ASC, receipt_id ASC
|
|
53043
|
+
LIMIT 1`, [this.tenantId, input.terminal_receipt_id, manifest.operation_id, manifest.step_id]);
|
|
53044
|
+
if (terminal.rows[0]) {
|
|
53045
|
+
return parseApplyResult(terminal.rows[0]["result_json"], true);
|
|
53046
|
+
}
|
|
52172
53047
|
const existing = await tx.query("SELECT * FROM todos_task_manifest_bindings WHERE tenant_id = $1 AND operation_id = $2 LIMIT 1 FOR UPDATE", [this.tenantId, manifest.operation_id]);
|
|
52173
53048
|
if (existing.rows[0]) {
|
|
52174
53049
|
const binding = existing.rows[0];
|
|
52175
|
-
if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest) {
|
|
52176
|
-
|
|
53050
|
+
if (binding["idempotency_key"] !== manifest.idempotency_key || binding["request_digest"] !== input.request_digest || binding["step_id"] !== manifest.step_id || binding["precondition_digest"] !== manifest.precondition_digest) {
|
|
53051
|
+
return this.persistTerminal(tx, input, "TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT");
|
|
52177
53052
|
}
|
|
52178
53053
|
if (binding["state"] !== "applied") {
|
|
52179
|
-
|
|
53054
|
+
return this.persistTerminal(tx, input, "TODOS_TASK_MANIFEST_GRAPH_CONFLICT");
|
|
52180
53055
|
}
|
|
52181
|
-
return
|
|
53056
|
+
return parseApplyResult(binding["result_json"], true);
|
|
52182
53057
|
}
|
|
52183
53058
|
const reused = await tx.query("SELECT operation_id FROM todos_task_manifest_bindings WHERE tenant_id = $1 AND idempotency_key = $2 LIMIT 1", [this.tenantId, manifest.idempotency_key]);
|
|
52184
53059
|
if (reused.rows[0])
|
|
52185
|
-
|
|
53060
|
+
return this.persistTerminal(tx, input, "TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT");
|
|
53061
|
+
if (manifest.idempotency_key !== input.expected_idempotency_key) {
|
|
53062
|
+
return this.persistTerminal(tx, input, "TODOS_TASK_MANIFEST_IDEMPOTENCY_MISMATCH");
|
|
53063
|
+
}
|
|
52186
53064
|
if (manifest.if_binding_version !== undefined && manifest.if_binding_version !== 0) {
|
|
52187
|
-
|
|
53065
|
+
return this.persistTerminal(tx, input, "TODOS_TASK_MANIFEST_CAS_CONFLICT");
|
|
52188
53066
|
}
|
|
52189
53067
|
const project = await tx.query(`SELECT 1 AS found FROM ${this.tableName}
|
|
52190
53068
|
WHERE service = $1 AND object_type = 'projects' AND object_id = $2 AND deleted_at IS NULL LIMIT 1`, [this.service, manifest.project_id]);
|
|
52191
53069
|
if (!project.rows[0])
|
|
52192
|
-
|
|
53070
|
+
return this.persistTerminal(tx, input, "TODOS_TASK_MANIFEST_FOREIGN_REFERENCE");
|
|
52193
53071
|
if (manifest.task_list_id) {
|
|
52194
53072
|
const taskList = await tx.query(`SELECT payload FROM ${this.tableName}
|
|
52195
53073
|
WHERE service = $1 AND object_type = 'task_lists' AND object_id = $2 AND deleted_at IS NULL LIMIT 1`, [this.service, manifest.task_list_id]);
|
|
52196
53074
|
const payload = taskList.rows[0] ? parseJson2(taskList.rows[0]["payload"]) : null;
|
|
52197
53075
|
if (!payload || payload["project_id"] !== manifest.project_id) {
|
|
52198
|
-
|
|
53076
|
+
return this.persistTerminal(tx, input, "TODOS_TASK_MANIFEST_FOREIGN_REFERENCE");
|
|
52199
53077
|
}
|
|
52200
53078
|
}
|
|
52201
53079
|
const objectIds = [input.graph.plan_id, ...Object.values(input.graph.task_ids), ...input.graph.comment_ids, ...input.graph.verification_ids, ...input.graph.dependency_ids];
|
|
52202
53080
|
const conflict = await tx.query(`SELECT object_id FROM ${this.tableName}
|
|
52203
53081
|
WHERE service = $1 AND object_id IN (${placeholders2(2, objectIds.length)}) LIMIT 1`, [this.service, ...objectIds]);
|
|
52204
53082
|
if (conflict.rows[0])
|
|
52205
|
-
|
|
53083
|
+
return this.persistTerminal(tx, input, "TODOS_TASK_MANIFEST_GRAPH_CONFLICT");
|
|
52206
53084
|
await this.insertSync(tx, "plans", input.graph.plan_id, planPayload(input), input.now);
|
|
52207
53085
|
fault(faults, "after_plan_write");
|
|
52208
53086
|
for (const task3 of manifest.tasks) {
|
|
@@ -52272,9 +53150,14 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52272
53150
|
schema_version: 1,
|
|
52273
53151
|
kind: "apply",
|
|
52274
53152
|
operation_id: manifest.operation_id,
|
|
53153
|
+
step_id: manifest.step_id,
|
|
52275
53154
|
idempotency_key: manifest.idempotency_key,
|
|
52276
53155
|
request_digest: input.request_digest,
|
|
53156
|
+
precondition_digest: manifest.precondition_digest,
|
|
52277
53157
|
result_digest: input.result_digest,
|
|
53158
|
+
outcome: "accepted",
|
|
53159
|
+
reason: null,
|
|
53160
|
+
duplicate_of_receipt_id: null,
|
|
52278
53161
|
binding_version: 1,
|
|
52279
53162
|
apply_receipt_id: null,
|
|
52280
53163
|
created_at: input.now
|
|
@@ -52291,14 +53174,18 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52291
53174
|
const resultJson = canonicalJson(result);
|
|
52292
53175
|
await tx.query(`INSERT INTO todos_task_manifest_receipts (
|
|
52293
53176
|
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
|
|
52294
|
-
|
|
52295
|
-
|
|
53177
|
+
step_id, request_digest, precondition_digest, result_digest, slug_provenance, outcome,
|
|
53178
|
+
reason, duplicate_of_receipt_id, binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
53179
|
+
) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'apply', $3, $4, $5, $6, $7, $8, $9, 'accepted', NULL, NULL, 1, NULL, $10::jsonb, $11::jsonb, $12)`, [
|
|
52296
53180
|
input.receipt_id,
|
|
52297
53181
|
this.tenantId,
|
|
52298
53182
|
manifest.operation_id,
|
|
52299
53183
|
manifest.idempotency_key,
|
|
53184
|
+
manifest.step_id,
|
|
52300
53185
|
input.request_digest,
|
|
53186
|
+
manifest.precondition_digest,
|
|
52301
53187
|
input.result_digest,
|
|
53188
|
+
TASK_MANIFEST_DETERMINISTIC_SLUG_PROVENANCE,
|
|
52302
53189
|
manifestJson,
|
|
52303
53190
|
resultJson,
|
|
52304
53191
|
input.now
|
|
@@ -52317,14 +53204,18 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52317
53204
|
}
|
|
52318
53205
|
fault(faults, "after_outbox_write");
|
|
52319
53206
|
await tx.query(`INSERT INTO todos_task_manifest_bindings (
|
|
52320
|
-
operation_id, tenant_id, idempotency_key, request_digest,
|
|
52321
|
-
|
|
52322
|
-
|
|
53207
|
+
operation_id, tenant_id, step_id, idempotency_key, request_digest, precondition_digest,
|
|
53208
|
+
result_digest, slug_provenance, outcome, apply_receipt_id, manifest_json, result_json,
|
|
53209
|
+
state, version, created_at, updated_at
|
|
53210
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'accepted', $9, $10::jsonb, $11::jsonb, 'applied', 1, $12, $12)`, [
|
|
52323
53211
|
manifest.operation_id,
|
|
52324
53212
|
this.tenantId,
|
|
53213
|
+
manifest.step_id,
|
|
52325
53214
|
manifest.idempotency_key,
|
|
52326
53215
|
input.request_digest,
|
|
53216
|
+
manifest.precondition_digest,
|
|
52327
53217
|
input.result_digest,
|
|
53218
|
+
TASK_MANIFEST_DETERMINISTIC_SLUG_PROVENANCE,
|
|
52328
53219
|
input.receipt_id,
|
|
52329
53220
|
manifestJson,
|
|
52330
53221
|
resultJson,
|
|
@@ -52337,9 +53228,12 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52337
53228
|
async readExact(receiptId2) {
|
|
52338
53229
|
await this.ensureSchema();
|
|
52339
53230
|
const result = await this.client.query("SELECT result_json FROM todos_task_manifest_receipts WHERE tenant_id = $1 AND receipt_id = $2 AND kind = 'apply' LIMIT 1", [this.tenantId, receiptId2]);
|
|
52340
|
-
if (
|
|
53231
|
+
if (result.rows[0])
|
|
53232
|
+
return parseApplyResult(result.rows[0]["result_json"], false);
|
|
53233
|
+
const terminal = await this.client.query("SELECT result_json FROM todos_task_manifest_terminal_receipts WHERE tenant_id = $1 AND receipt_id = $2 AND kind = 'apply' LIMIT 1", [this.tenantId, receiptId2]);
|
|
53234
|
+
if (!terminal.rows[0])
|
|
52341
53235
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_RECEIPT_NOT_FOUND", `Apply receipt not found: ${receiptId2}`);
|
|
52342
|
-
return
|
|
53236
|
+
return parseApplyResult(terminal.rows[0]["result_json"], false);
|
|
52343
53237
|
}
|
|
52344
53238
|
async lookupBindingByPlanId(planId) {
|
|
52345
53239
|
await this.ensureSchema();
|
|
@@ -52350,6 +53244,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52350
53244
|
b.version AS binding_version,
|
|
52351
53245
|
b.tenant_id AS binding_tenant_id,
|
|
52352
53246
|
b.operation_id AS binding_operation_id,
|
|
53247
|
+
b.step_id AS binding_step_id,
|
|
52353
53248
|
b.result_json #>> '{graph,plan_id}' AS binding_plan_id,
|
|
52354
53249
|
r.tenant_id AS receipt_tenant_id,
|
|
52355
53250
|
r.authority AS receipt_authority,
|
|
@@ -52357,6 +53252,7 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52357
53252
|
r.schema_version AS receipt_schema_version,
|
|
52358
53253
|
r.kind AS receipt_kind,
|
|
52359
53254
|
r.operation_id AS receipt_operation_id,
|
|
53255
|
+
r.step_id AS receipt_step_id,
|
|
52360
53256
|
r.result_json #>> '{graph,plan_id}' AS receipt_plan_id
|
|
52361
53257
|
FROM todos_task_manifest_bindings b
|
|
52362
53258
|
LEFT JOIN todos_task_manifest_receipts r
|
|
@@ -52443,6 +53339,10 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52443
53339
|
if (!binding || Number(binding["version"]) !== input.if_binding_version) {
|
|
52444
53340
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_CAS_CONFLICT", "Binding version changed before compensation");
|
|
52445
53341
|
}
|
|
53342
|
+
const appliedReceipt = receiptFromRow3(applyRow);
|
|
53343
|
+
if (appliedReceipt.receipt_id !== input.receipt_id || appliedReceipt.operation_id !== input.operation_id || String(binding["operation_id"]) !== appliedReceipt.operation_id || String(binding["step_id"] ?? "legacy-apply") !== appliedReceipt.step_id || String(binding["idempotency_key"]) !== appliedReceipt.idempotency_key || String(binding["request_digest"]) !== appliedReceipt.request_digest || String(binding["precondition_digest"] ?? "0".repeat(64)) !== appliedReceipt.precondition_digest || String(binding["apply_receipt_id"]) !== input.receipt_id || binding["slug_provenance"] !== applyRow["slug_provenance"]) {
|
|
53344
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: receipt and binding identity disagree");
|
|
53345
|
+
}
|
|
52446
53346
|
if (binding["state"] !== "applied")
|
|
52447
53347
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Graph is not applied");
|
|
52448
53348
|
const delivered = await tx.query(`SELECT o.id FROM todos_task_manifest_outbox o
|
|
@@ -52457,12 +53357,18 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52457
53357
|
LIMIT 1`, [this.tenantId, input.receipt_id]);
|
|
52458
53358
|
if (delivered.rows[0])
|
|
52459
53359
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: delivered outbox row exists");
|
|
52460
|
-
const applyResult =
|
|
53360
|
+
const applyResult = parseApplyResult(applyRow["result_json"], false);
|
|
52461
53361
|
const manifest = parseJson2(applyRow["manifest_json"]);
|
|
53362
|
+
const manifestRecord2 = manifest;
|
|
53363
|
+
const applyStepId = typeof manifestRecord2["step_id"] === "string" ? String(manifestRecord2["step_id"]) : null;
|
|
52462
53364
|
const expectedEffects = [
|
|
52463
53365
|
{
|
|
52464
53366
|
topic: "todos.task-manifest.applied",
|
|
52465
|
-
payload: {
|
|
53367
|
+
payload: {
|
|
53368
|
+
operation_id: manifest.operation_id,
|
|
53369
|
+
...applyStepId ? { step_id: applyStepId } : {},
|
|
53370
|
+
project_id: manifest.project_id
|
|
53371
|
+
}
|
|
52466
53372
|
},
|
|
52467
53373
|
...(manifest.effects ?? []).map((effect2) => ({ topic: effect2.topic, payload: effect2.payload }))
|
|
52468
53374
|
];
|
|
@@ -52510,9 +53416,15 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52510
53416
|
}
|
|
52511
53417
|
const appliedAt = receiptFromRow3(applyRow).created_at;
|
|
52512
53418
|
const expectedPayloads = new Map;
|
|
53419
|
+
const planRow = await tx.query(`SELECT payload FROM ${this.tableName}
|
|
53420
|
+
WHERE service = $1 AND object_type = 'plans' AND object_id = $2
|
|
53421
|
+
LIMIT 1`, [this.service, applyResult.graph.plan_id]);
|
|
53422
|
+
const actualPlan = planRow.rows[0] ? parseJson2(planRow.rows[0]["payload"]) : null;
|
|
53423
|
+
const planExpected = planPayload({ manifest, graph: applyResult.graph, now: appliedAt });
|
|
53424
|
+
planExpected.slug = validatePostgresPlanSlug(manifest, applyResult.graph.plan_id, actualPlan?.["slug"], applyRow["slug_provenance"]);
|
|
52513
53425
|
expectedPayloads.set(applyResult.graph.plan_id, {
|
|
52514
53426
|
type: "plans",
|
|
52515
|
-
payload: canonicalJson(
|
|
53427
|
+
payload: canonicalJson(planExpected)
|
|
52516
53428
|
});
|
|
52517
53429
|
for (const task3 of manifest.tasks)
|
|
52518
53430
|
expectedPayloads.set(applyResult.graph.task_ids[task3.key], {
|
|
@@ -52612,14 +53524,17 @@ class PostgresTodosTaskManifestBackend {
|
|
|
52612
53524
|
const readback = await this.readback(tx, applyResult.graph);
|
|
52613
53525
|
const result = { duplicate: false, receipt, absent: true, readback };
|
|
52614
53526
|
await tx.query(`INSERT INTO todos_task_manifest_receipts (
|
|
52615
|
-
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, idempotency_key,
|
|
52616
|
-
request_digest,
|
|
52617
|
-
|
|
53527
|
+
receipt_id, tenant_id, authority, route, schema_version, kind, operation_id, step_id, idempotency_key,
|
|
53528
|
+
request_digest, precondition_digest, result_digest, slug_provenance, outcome, reason,
|
|
53529
|
+
duplicate_of_receipt_id, binding_version, apply_receipt_id, manifest_json, result_json, created_at
|
|
53530
|
+
) VALUES ($1, $2, 'todos', 'todos.task-manifest.v1', 1, 'compensate', $3, $4, $5, $6, $7, $8, NULL, 'accepted', NULL, NULL, $9, $10, NULL, $11::jsonb, $12)`, [
|
|
52618
53531
|
compensationReceiptId,
|
|
52619
53532
|
this.tenantId,
|
|
52620
53533
|
receipt.operation_id,
|
|
53534
|
+
receipt.step_id,
|
|
52621
53535
|
input.idempotency_key,
|
|
52622
53536
|
requestDigest,
|
|
53537
|
+
input.precondition_digest,
|
|
52623
53538
|
receipt.result_digest,
|
|
52624
53539
|
receipt.binding_version,
|
|
52625
53540
|
input.receipt_id,
|
|
@@ -52676,36 +53591,83 @@ function resolveTenantId(value) {
|
|
|
52676
53591
|
}
|
|
52677
53592
|
return tenantId;
|
|
52678
53593
|
}
|
|
53594
|
+
function taskManifestRequestDigest(manifest) {
|
|
53595
|
+
const { idempotency_key: _idempotencyKey, ...request } = manifest;
|
|
53596
|
+
return canonicalDigest(request);
|
|
53597
|
+
}
|
|
53598
|
+
function taskManifestCompensationRequestDigest(request) {
|
|
53599
|
+
return canonicalDigest(request);
|
|
53600
|
+
}
|
|
53601
|
+
function deriveTodosTaskManifestApplyPreconditionDigest(input) {
|
|
53602
|
+
return canonicalDigest({
|
|
53603
|
+
route: TODOS_TASK_MANIFEST_CALLER_ROUTE,
|
|
53604
|
+
direction: "apply",
|
|
53605
|
+
operation_id: input.operation_id,
|
|
53606
|
+
step_id: input.step_id,
|
|
53607
|
+
project_id: input.project_id,
|
|
53608
|
+
task_list_id: input.task_list_id ?? null,
|
|
53609
|
+
expected_binding_version: input.if_binding_version ?? 0
|
|
53610
|
+
});
|
|
53611
|
+
}
|
|
53612
|
+
function deriveTodosTaskManifestCompensationPreconditionDigest(input) {
|
|
53613
|
+
return canonicalDigest({
|
|
53614
|
+
route: TODOS_TASK_MANIFEST_CALLER_ROUTE,
|
|
53615
|
+
direction: "compensate",
|
|
53616
|
+
operation_id: input.operation_id,
|
|
53617
|
+
step_id: input.step_id,
|
|
53618
|
+
apply_receipt_id: input.receipt_id,
|
|
53619
|
+
expected_binding_version: input.if_binding_version
|
|
53620
|
+
});
|
|
53621
|
+
}
|
|
53622
|
+
function deriveTodosTaskManifestIdempotencyKey(input) {
|
|
53623
|
+
return `tmk_${canonicalDigest({
|
|
53624
|
+
route: TODOS_TASK_MANIFEST_CALLER_ROUTE,
|
|
53625
|
+
...input
|
|
53626
|
+
}).slice(0, 48)}`;
|
|
53627
|
+
}
|
|
52679
53628
|
function normalize(input, now4) {
|
|
52680
53629
|
const parsed = parseTodosTaskManifest(input);
|
|
52681
53630
|
const requestBytes = Buffer.byteLength(canonicalJson(parsed), "utf8");
|
|
52682
53631
|
if (requestBytes > TODOS_TASK_MANIFEST_BOUNDS.request_bytes) {
|
|
52683
53632
|
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_BOUNDS_EXCEEDED", `Task manifest requires ${requestBytes} bytes but the bound is ${TODOS_TASK_MANIFEST_BOUNDS.request_bytes}`, { request_bytes: requestBytes, request_byte_limit: TODOS_TASK_MANIFEST_BOUNDS.request_bytes });
|
|
52684
53633
|
}
|
|
53634
|
+
const { idempotency_key: _idempotencyKey, ...request } = parsed;
|
|
53635
|
+
const request_digest = taskManifestRequestDigest(request);
|
|
52685
53636
|
const manifest = sanitizeManifest(parsed);
|
|
53637
|
+
const expectedPreconditionDigest = deriveTodosTaskManifestApplyPreconditionDigest(manifest);
|
|
53638
|
+
if (manifest.precondition_digest !== expectedPreconditionDigest) {
|
|
53639
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_DIGEST_MISMATCH", "precondition_digest does not match the exact apply target and binding version", { expected_precondition_digest: expectedPreconditionDigest });
|
|
53640
|
+
}
|
|
53641
|
+
const expectedIdempotencyKey = deriveTodosTaskManifestIdempotencyKey({
|
|
53642
|
+
operation_id: manifest.operation_id,
|
|
53643
|
+
step_id: manifest.step_id,
|
|
53644
|
+
direction: "apply",
|
|
53645
|
+
target_selector: manifest.project_id,
|
|
53646
|
+
request_digest,
|
|
53647
|
+
precondition_digest: manifest.precondition_digest
|
|
53648
|
+
});
|
|
52686
53649
|
const task_ids = Object.fromEntries(manifest.tasks.map((task3) => [
|
|
52687
53650
|
task3.key,
|
|
52688
|
-
deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "task", task3.key)
|
|
53651
|
+
deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, manifest.step_id, "task", task3.key)
|
|
52689
53652
|
]));
|
|
52690
53653
|
const graph = {
|
|
52691
|
-
plan_id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "plan", manifest.plan.key),
|
|
53654
|
+
plan_id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, manifest.step_id, "plan", manifest.plan.key),
|
|
52692
53655
|
task_ids,
|
|
52693
|
-
comment_ids: manifest.tasks.flatMap((task3) => (task3.comments ?? []).map((_, index) => deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "comment", task3.key, String(index)))),
|
|
52694
|
-
verification_ids: manifest.tasks.flatMap((task3) => (task3.verifications ?? []).map((_, index) => deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "verification", task3.key, String(index)))),
|
|
53656
|
+
comment_ids: manifest.tasks.flatMap((task3) => (task3.comments ?? []).map((_, index) => deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, manifest.step_id, "comment", task3.key, String(index)))),
|
|
53657
|
+
verification_ids: manifest.tasks.flatMap((task3) => (task3.verifications ?? []).map((_, index) => deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, manifest.step_id, "verification", task3.key, String(index)))),
|
|
52695
53658
|
dependency_ids: (manifest.dependencies ?? []).map((edge) => `${task_ids[edge.task]}::${task_ids[edge.depends_on]}`)
|
|
52696
53659
|
};
|
|
52697
|
-
const request_digest = canonicalDigest(parsed);
|
|
52698
53660
|
const effectInputs = [
|
|
52699
53661
|
{
|
|
52700
53662
|
topic: "todos.task-manifest.applied",
|
|
52701
|
-
payload: { operation_id: manifest.operation_id, project_id: manifest.project_id }
|
|
53663
|
+
payload: { operation_id: manifest.operation_id, step_id: manifest.step_id, project_id: manifest.project_id }
|
|
52702
53664
|
},
|
|
52703
53665
|
...manifest.effects ?? []
|
|
52704
53666
|
];
|
|
52705
53667
|
const outbox = effectInputs.map((effect2, index) => {
|
|
52706
53668
|
const payload = { ...effect2.payload };
|
|
52707
53669
|
return {
|
|
52708
|
-
id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, "outbox", String(index)),
|
|
53670
|
+
id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, manifest.operation_id, manifest.step_id, "outbox", String(index)),
|
|
52709
53671
|
topic: effect2.topic,
|
|
52710
53672
|
payload,
|
|
52711
53673
|
digest: canonicalDigest({ topic: effect2.topic, payload })
|
|
@@ -52715,11 +53677,14 @@ function normalize(input, now4) {
|
|
|
52715
53677
|
return {
|
|
52716
53678
|
manifest,
|
|
52717
53679
|
request_digest,
|
|
53680
|
+
expected_idempotency_key: expectedIdempotencyKey,
|
|
52718
53681
|
result_digest,
|
|
52719
|
-
receipt_id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, "apply", manifest.operation_id, manifest.idempotency_key, request_digest),
|
|
53682
|
+
receipt_id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, "apply", manifest.operation_id, manifest.step_id, manifest.idempotency_key, request_digest),
|
|
53683
|
+
terminal_receipt_id: deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, "terminal", "apply", manifest.operation_id, manifest.step_id, manifest.idempotency_key, request_digest),
|
|
52720
53684
|
graph,
|
|
52721
53685
|
outbox,
|
|
52722
|
-
now: now4
|
|
53686
|
+
now: now4,
|
|
53687
|
+
plan_slug_provenance: TODOS_TASK_MANIFEST_PLAN_SLUG_PROVENANCE
|
|
52723
53688
|
};
|
|
52724
53689
|
}
|
|
52725
53690
|
function sanitizeManifest(manifest) {
|
|
@@ -52777,6 +53742,10 @@ class PackageOwnedTodosTaskManifestAuthority {
|
|
|
52777
53742
|
tenant_id: this.tenantId,
|
|
52778
53743
|
backend: this.backend.kind,
|
|
52779
53744
|
deterministic_ids: true,
|
|
53745
|
+
operation_step_identity: true,
|
|
53746
|
+
deterministic_idempotency_keys: true,
|
|
53747
|
+
terminal_nonacceptance_receipts: true,
|
|
53748
|
+
plan_slug_provenance: TODOS_TASK_MANIFEST_PLAN_SLUG_PROVENANCE,
|
|
52780
53749
|
immutable_receipts: true,
|
|
52781
53750
|
transactional_outbox: true,
|
|
52782
53751
|
idempotent_outbox_delivery: true,
|
|
@@ -52806,7 +53775,11 @@ class PackageOwnedTodosTaskManifestAuthority {
|
|
|
52806
53775
|
async apply(input) {
|
|
52807
53776
|
const normalized = normalize(input, this.now());
|
|
52808
53777
|
const faults = await this.prepareFaults();
|
|
52809
|
-
|
|
53778
|
+
const result = this.bounded(await this.backend.apply(normalized, faults));
|
|
53779
|
+
if (result.receipt.outcome === "terminal_nonacceptance") {
|
|
53780
|
+
throw new TodosTaskManifestError(result.receipt.reason ?? "TODOS_TASK_MANIFEST_GRAPH_CONFLICT", "Task-manifest apply reached an immutable terminal nonacceptance", { receipt: result.receipt });
|
|
53781
|
+
}
|
|
53782
|
+
return result;
|
|
52810
53783
|
}
|
|
52811
53784
|
readExact(receiptId2) {
|
|
52812
53785
|
if (!receiptId2 || receiptId2.length > 200) {
|
|
@@ -52839,18 +53812,48 @@ class PackageOwnedTodosTaskManifestAuthority {
|
|
|
52839
53812
|
async compensate(input) {
|
|
52840
53813
|
const request = parseTodosTaskManifestCompensation(input);
|
|
52841
53814
|
const applied = await this.backend.readExact(request.receipt_id);
|
|
52842
|
-
|
|
52843
|
-
|
|
53815
|
+
if (applied.receipt.outcome !== "accepted") {
|
|
53816
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_COMPENSATION_REFUSED", "Compensation refused: apply receipt is terminal nonacceptance");
|
|
53817
|
+
}
|
|
53818
|
+
if (request.operation_id !== applied.receipt.operation_id) {
|
|
53819
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_CONFLICT", "Compensation operation_id must match the accepted apply operation");
|
|
53820
|
+
}
|
|
53821
|
+
if (request.step_id === applied.receipt.step_id) {
|
|
53822
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_INVALID_INPUT", "Compensation must use a distinct step_id from apply");
|
|
53823
|
+
}
|
|
53824
|
+
const expectedPreconditionDigest = deriveTodosTaskManifestCompensationPreconditionDigest(request);
|
|
53825
|
+
if (request.precondition_digest !== expectedPreconditionDigest) {
|
|
53826
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_DIGEST_MISMATCH", "precondition_digest does not match the exact compensation receipt and binding version", { expected_precondition_digest: expectedPreconditionDigest });
|
|
53827
|
+
}
|
|
53828
|
+
const { idempotency_key: _requestIdempotencyKey, ...compensationRequestWithoutKey } = request;
|
|
53829
|
+
const requestDigest = taskManifestCompensationRequestDigest(compensationRequestWithoutKey);
|
|
53830
|
+
const expectedIdempotencyKey = deriveTodosTaskManifestIdempotencyKey({
|
|
53831
|
+
operation_id: request.operation_id,
|
|
53832
|
+
step_id: request.step_id,
|
|
53833
|
+
direction: "compensate",
|
|
53834
|
+
target_selector: request.receipt_id,
|
|
53835
|
+
request_digest: requestDigest,
|
|
53836
|
+
precondition_digest: request.precondition_digest
|
|
53837
|
+
});
|
|
53838
|
+
if (request.idempotency_key !== expectedIdempotencyKey) {
|
|
53839
|
+
throw new TodosTaskManifestError("TODOS_TASK_MANIFEST_IDEMPOTENCY_MISMATCH", "idempotency_key does not match the deterministic operation/step/compensation semantics", { expected_idempotency_key: expectedIdempotencyKey });
|
|
53840
|
+
}
|
|
53841
|
+
const compensationReceiptId = deterministicUuid(TODOS_TASK_MANIFEST_ROUTE, "compensate", request.operation_id, request.step_id, request.idempotency_key, requestDigest);
|
|
52844
53842
|
const receipt = {
|
|
52845
53843
|
receipt_id: compensationReceiptId,
|
|
52846
53844
|
authority: "todos",
|
|
52847
53845
|
route: TODOS_TASK_MANIFEST_ROUTE,
|
|
52848
53846
|
schema_version: 1,
|
|
52849
53847
|
kind: "compensate",
|
|
52850
|
-
operation_id:
|
|
53848
|
+
operation_id: request.operation_id,
|
|
53849
|
+
step_id: request.step_id,
|
|
52851
53850
|
idempotency_key: request.idempotency_key,
|
|
52852
53851
|
request_digest: requestDigest,
|
|
53852
|
+
precondition_digest: request.precondition_digest,
|
|
52853
53853
|
result_digest: canonicalDigest({ absent: true, apply_receipt_id: applied.receipt.receipt_id }),
|
|
53854
|
+
outcome: "accepted",
|
|
53855
|
+
reason: null,
|
|
53856
|
+
duplicate_of_receipt_id: null,
|
|
52854
53857
|
binding_version: request.if_binding_version + 1,
|
|
52855
53858
|
apply_receipt_id: applied.receipt.receipt_id,
|
|
52856
53859
|
created_at: this.now()
|
|
@@ -54516,9 +55519,24 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
54516
55519
|
TaskManifestBounds: taskManifestBoundsSchema,
|
|
54517
55520
|
TaskManifestCapability: taskManifestCapabilitySchema,
|
|
54518
55521
|
TaskManifestCapabilityResponse: taskManifestCapabilityResponseSchema,
|
|
55522
|
+
TaskManifest: taskManifestSchema,
|
|
55523
|
+
TaskManifestReceipt: taskManifestReceiptSchema,
|
|
55524
|
+
TaskManifestApplyResult: taskManifestApplyResultSchema,
|
|
55525
|
+
TaskManifestApplyResponse: taskManifestApplyResponseSchema,
|
|
55526
|
+
TaskManifestCompensateRequest: taskManifestCompensateRequestSchema,
|
|
55527
|
+
TaskManifestCompensationResult: taskManifestCompensationResultSchema,
|
|
55528
|
+
TaskManifestCompensateResponse: taskManifestCompensateResponseSchema,
|
|
55529
|
+
TaskManifestReadExactRequest: taskManifestReadExactRequestSchema,
|
|
54519
55530
|
TaskManifestBindingLookupRequest: taskManifestBindingLookupRequestSchema,
|
|
54520
55531
|
TaskManifestBindingLookupResult: taskManifestBindingLookupResultSchema,
|
|
54521
55532
|
TaskManifestBindingLookupResponse: taskManifestBindingLookupResponseSchema,
|
|
55533
|
+
ProjectRegistrationCapability: projectRegistrationCapabilitySchema,
|
|
55534
|
+
ProjectRegistrationReceipt: projectRegistrationReceiptSchema,
|
|
55535
|
+
ProjectRegistrationRequest: projectRegistrationRequestSchema,
|
|
55536
|
+
ProjectRegistrationLookupRequest: projectRegistrationLookupRequestSchema,
|
|
55537
|
+
PriorRegistrationAdoptionValidation: priorRegistrationAdoptionValidationSchema,
|
|
55538
|
+
ProjectResource: projectResourceSchema,
|
|
55539
|
+
ProjectResourcePage: projectResourcePageSchema,
|
|
54522
55540
|
TaskList: taskListSchema,
|
|
54523
55541
|
ProjectTaskListEnsureReceipt: projectTaskListEnsureReceiptSchema,
|
|
54524
55542
|
ProjectTaskListEnsureResult: projectTaskListEnsureResultSchema,
|
|
@@ -54561,6 +55579,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
54561
55579
|
priority: { type: "string", enum: [...TASK_PRIORITIES] },
|
|
54562
55580
|
assigned_to: { type: "string" },
|
|
54563
55581
|
project_id: { type: "string", nullable: true },
|
|
55582
|
+
parent_id: { type: "string", nullable: true },
|
|
54564
55583
|
plan_id: { type: "string", nullable: true },
|
|
54565
55584
|
task_list_id: { type: "string", nullable: true },
|
|
54566
55585
|
version: { type: "number" }
|
|
@@ -55515,6 +56534,341 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
55515
56534
|
},
|
|
55516
56535
|
security: [{ apiKey: [] }],
|
|
55517
56536
|
paths: {
|
|
56537
|
+
"/v1/project-registration/capability": {
|
|
56538
|
+
get: {
|
|
56539
|
+
operationId: "getProjectRegistrationCapability",
|
|
56540
|
+
summary: "Read the live package-owned Projects to Todos registration capability",
|
|
56541
|
+
responses: {
|
|
56542
|
+
"200": {
|
|
56543
|
+
content: {
|
|
56544
|
+
"application/json": {
|
|
56545
|
+
schema: {
|
|
56546
|
+
type: "object",
|
|
56547
|
+
additionalProperties: false,
|
|
56548
|
+
required: ["capability"],
|
|
56549
|
+
properties: {
|
|
56550
|
+
capability: {
|
|
56551
|
+
$ref: "#/components/schemas/ProjectRegistrationCapability"
|
|
56552
|
+
}
|
|
56553
|
+
}
|
|
56554
|
+
}
|
|
56555
|
+
}
|
|
56556
|
+
}
|
|
56557
|
+
}
|
|
56558
|
+
}
|
|
56559
|
+
}
|
|
56560
|
+
},
|
|
56561
|
+
"/v1/project-registration/resources": {
|
|
56562
|
+
get: {
|
|
56563
|
+
operationId: "listProjectRegistrationResources",
|
|
56564
|
+
summary: "List one bounded page of stable Todos identities for an exact Projects workspace id",
|
|
56565
|
+
parameters: [
|
|
56566
|
+
{
|
|
56567
|
+
name: "source_project_id",
|
|
56568
|
+
in: "query",
|
|
56569
|
+
required: true,
|
|
56570
|
+
schema: { type: "string" }
|
|
56571
|
+
},
|
|
56572
|
+
{
|
|
56573
|
+
name: "include_anchors",
|
|
56574
|
+
in: "query",
|
|
56575
|
+
schema: { type: "boolean", default: false }
|
|
56576
|
+
},
|
|
56577
|
+
{
|
|
56578
|
+
name: "limit",
|
|
56579
|
+
in: "query",
|
|
56580
|
+
schema: { type: "integer", minimum: 1, maximum: 500, default: 100 }
|
|
56581
|
+
},
|
|
56582
|
+
{
|
|
56583
|
+
name: "cursor",
|
|
56584
|
+
in: "query",
|
|
56585
|
+
schema: { type: "string" }
|
|
56586
|
+
}
|
|
56587
|
+
],
|
|
56588
|
+
responses: {
|
|
56589
|
+
"200": {
|
|
56590
|
+
content: {
|
|
56591
|
+
"application/json": {
|
|
56592
|
+
schema: {
|
|
56593
|
+
type: "object",
|
|
56594
|
+
additionalProperties: false,
|
|
56595
|
+
required: ["page"],
|
|
56596
|
+
properties: {
|
|
56597
|
+
page: { $ref: "#/components/schemas/ProjectResourcePage" }
|
|
56598
|
+
}
|
|
56599
|
+
}
|
|
56600
|
+
}
|
|
56601
|
+
}
|
|
56602
|
+
},
|
|
56603
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56604
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56605
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56606
|
+
}
|
|
56607
|
+
}
|
|
56608
|
+
},
|
|
56609
|
+
"/v1/project-registration/create": {
|
|
56610
|
+
post: {
|
|
56611
|
+
operationId: "createProjectRegistrationResource",
|
|
56612
|
+
summary: "Create or deterministically bind one Projects to Todos resource",
|
|
56613
|
+
requestBody: {
|
|
56614
|
+
required: true,
|
|
56615
|
+
content: {
|
|
56616
|
+
"application/json": {
|
|
56617
|
+
schema: { $ref: "#/components/schemas/ProjectRegistrationRequest" }
|
|
56618
|
+
}
|
|
56619
|
+
}
|
|
56620
|
+
},
|
|
56621
|
+
responses: {
|
|
56622
|
+
"201": {
|
|
56623
|
+
content: {
|
|
56624
|
+
"application/json": {
|
|
56625
|
+
schema: {
|
|
56626
|
+
type: "object",
|
|
56627
|
+
additionalProperties: false,
|
|
56628
|
+
required: ["receipt"],
|
|
56629
|
+
properties: {
|
|
56630
|
+
receipt: { $ref: "#/components/schemas/ProjectRegistrationReceipt" }
|
|
56631
|
+
}
|
|
56632
|
+
}
|
|
56633
|
+
}
|
|
56634
|
+
}
|
|
56635
|
+
},
|
|
56636
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56637
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56638
|
+
}
|
|
56639
|
+
}
|
|
56640
|
+
},
|
|
56641
|
+
"/v1/project-registration/read-exact": {
|
|
56642
|
+
post: {
|
|
56643
|
+
operationId: "readExactProjectRegistrationResource",
|
|
56644
|
+
summary: "Read one registered project or task list by exact full UUID",
|
|
56645
|
+
requestBody: {
|
|
56646
|
+
required: true,
|
|
56647
|
+
content: {
|
|
56648
|
+
"application/json": {
|
|
56649
|
+
schema: {
|
|
56650
|
+
type: "object",
|
|
56651
|
+
additionalProperties: false,
|
|
56652
|
+
required: [
|
|
56653
|
+
"resource_kind",
|
|
56654
|
+
"target_id",
|
|
56655
|
+
"response_byte_limit",
|
|
56656
|
+
"time_budget_ms"
|
|
56657
|
+
],
|
|
56658
|
+
properties: {
|
|
56659
|
+
resource_kind: { type: "string", enum: ["project", "task_list"] },
|
|
56660
|
+
target_id: { type: "string", format: "uuid" },
|
|
56661
|
+
...projectRegistrationBoundsProperties
|
|
56662
|
+
}
|
|
56663
|
+
}
|
|
56664
|
+
}
|
|
56665
|
+
}
|
|
56666
|
+
},
|
|
56667
|
+
responses: {
|
|
56668
|
+
"200": {
|
|
56669
|
+
content: {
|
|
56670
|
+
"application/json": {
|
|
56671
|
+
schema: {
|
|
56672
|
+
type: "object",
|
|
56673
|
+
additionalProperties: false,
|
|
56674
|
+
required: ["record"],
|
|
56675
|
+
properties: {
|
|
56676
|
+
record: {
|
|
56677
|
+
type: "object",
|
|
56678
|
+
additionalProperties: false,
|
|
56679
|
+
required: ["target_id", "revision", "digest"],
|
|
56680
|
+
properties: {
|
|
56681
|
+
target_id: { type: "string", format: "uuid" },
|
|
56682
|
+
revision: { type: "string" },
|
|
56683
|
+
digest: { type: "string" }
|
|
56684
|
+
}
|
|
56685
|
+
}
|
|
56686
|
+
}
|
|
56687
|
+
}
|
|
56688
|
+
}
|
|
56689
|
+
}
|
|
56690
|
+
},
|
|
56691
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56692
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56693
|
+
}
|
|
56694
|
+
}
|
|
56695
|
+
},
|
|
56696
|
+
"/v1/project-registration/receipts/lookup": {
|
|
56697
|
+
post: {
|
|
56698
|
+
operationId: "lookupProjectRegistrationReceipt",
|
|
56699
|
+
summary: "Recover one exact immutable terminal registration receipt",
|
|
56700
|
+
requestBody: {
|
|
56701
|
+
required: true,
|
|
56702
|
+
content: {
|
|
56703
|
+
"application/json": {
|
|
56704
|
+
schema: { $ref: "#/components/schemas/ProjectRegistrationLookupRequest" }
|
|
56705
|
+
}
|
|
56706
|
+
}
|
|
56707
|
+
},
|
|
56708
|
+
responses: {
|
|
56709
|
+
"200": {
|
|
56710
|
+
content: {
|
|
56711
|
+
"application/json": {
|
|
56712
|
+
schema: {
|
|
56713
|
+
type: "object",
|
|
56714
|
+
additionalProperties: false,
|
|
56715
|
+
required: ["receipt", "response_control"],
|
|
56716
|
+
properties: {
|
|
56717
|
+
receipt: { $ref: "#/components/schemas/ProjectRegistrationReceipt" },
|
|
56718
|
+
response_control: {
|
|
56719
|
+
type: "object",
|
|
56720
|
+
additionalProperties: false,
|
|
56721
|
+
required: [
|
|
56722
|
+
"response_byte_limit",
|
|
56723
|
+
"time_budget_ms",
|
|
56724
|
+
"response_bytes",
|
|
56725
|
+
"elapsed_ms",
|
|
56726
|
+
"complete",
|
|
56727
|
+
"truncated"
|
|
56728
|
+
],
|
|
56729
|
+
properties: {
|
|
56730
|
+
...projectRegistrationBoundsProperties,
|
|
56731
|
+
response_bytes: { type: "integer", minimum: 0 },
|
|
56732
|
+
elapsed_ms: { type: "integer", minimum: 0 },
|
|
56733
|
+
complete: { type: "boolean", enum: [true] },
|
|
56734
|
+
truncated: { type: "boolean", enum: [false] }
|
|
56735
|
+
}
|
|
56736
|
+
}
|
|
56737
|
+
}
|
|
56738
|
+
}
|
|
56739
|
+
}
|
|
56740
|
+
}
|
|
56741
|
+
},
|
|
56742
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56743
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56744
|
+
}
|
|
56745
|
+
}
|
|
56746
|
+
},
|
|
56747
|
+
"/v1/project-registration/validate-prior-adoption": {
|
|
56748
|
+
post: {
|
|
56749
|
+
operationId: "validatePriorRegistrationAdoption",
|
|
56750
|
+
summary: "Fail closed unless one prior accepted registration still matches its exact current resource",
|
|
56751
|
+
requestBody: {
|
|
56752
|
+
required: true,
|
|
56753
|
+
content: {
|
|
56754
|
+
"application/json": {
|
|
56755
|
+
schema: {
|
|
56756
|
+
type: "object",
|
|
56757
|
+
additionalProperties: false,
|
|
56758
|
+
required: ["source_request", "source_receipt", "current_record"],
|
|
56759
|
+
properties: {
|
|
56760
|
+
source_request: { $ref: "#/components/schemas/ProjectRegistrationRequest" },
|
|
56761
|
+
source_receipt: { $ref: "#/components/schemas/ProjectRegistrationReceipt" },
|
|
56762
|
+
current_record: {
|
|
56763
|
+
oneOf: [
|
|
56764
|
+
{ $ref: "#/components/schemas/Project" },
|
|
56765
|
+
{ $ref: "#/components/schemas/TaskList" }
|
|
56766
|
+
]
|
|
56767
|
+
}
|
|
56768
|
+
}
|
|
56769
|
+
}
|
|
56770
|
+
}
|
|
56771
|
+
}
|
|
56772
|
+
},
|
|
56773
|
+
responses: {
|
|
56774
|
+
"200": {
|
|
56775
|
+
content: {
|
|
56776
|
+
"application/json": {
|
|
56777
|
+
schema: {
|
|
56778
|
+
type: "object",
|
|
56779
|
+
additionalProperties: false,
|
|
56780
|
+
required: ["validation"],
|
|
56781
|
+
properties: {
|
|
56782
|
+
validation: {
|
|
56783
|
+
$ref: "#/components/schemas/PriorRegistrationAdoptionValidation"
|
|
56784
|
+
}
|
|
56785
|
+
}
|
|
56786
|
+
}
|
|
56787
|
+
}
|
|
56788
|
+
}
|
|
56789
|
+
},
|
|
56790
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56791
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56792
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56793
|
+
}
|
|
56794
|
+
}
|
|
56795
|
+
},
|
|
56796
|
+
"/v1/project-registration/compensate": {
|
|
56797
|
+
post: {
|
|
56798
|
+
operationId: "compensateProjectRegistrationResource",
|
|
56799
|
+
summary: "Conditionally remove an unchanged receipt-owned registration resource",
|
|
56800
|
+
requestBody: {
|
|
56801
|
+
required: true,
|
|
56802
|
+
content: {
|
|
56803
|
+
"application/json": {
|
|
56804
|
+
schema: { $ref: "#/components/schemas/ProjectRegistrationRequest" }
|
|
56805
|
+
}
|
|
56806
|
+
}
|
|
56807
|
+
},
|
|
56808
|
+
responses: {
|
|
56809
|
+
"201": {
|
|
56810
|
+
content: {
|
|
56811
|
+
"application/json": {
|
|
56812
|
+
schema: {
|
|
56813
|
+
type: "object",
|
|
56814
|
+
additionalProperties: false,
|
|
56815
|
+
required: ["receipt"],
|
|
56816
|
+
properties: {
|
|
56817
|
+
receipt: { $ref: "#/components/schemas/ProjectRegistrationReceipt" }
|
|
56818
|
+
}
|
|
56819
|
+
}
|
|
56820
|
+
}
|
|
56821
|
+
}
|
|
56822
|
+
},
|
|
56823
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56824
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56825
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56826
|
+
}
|
|
56827
|
+
}
|
|
56828
|
+
},
|
|
56829
|
+
"/v1/project-registration/verify-inverse": {
|
|
56830
|
+
post: {
|
|
56831
|
+
operationId: "verifyInverseProjectRegistrationResource",
|
|
56832
|
+
summary: "Verify exact absence after conditional registration compensation",
|
|
56833
|
+
requestBody: {
|
|
56834
|
+
required: true,
|
|
56835
|
+
content: {
|
|
56836
|
+
"application/json": {
|
|
56837
|
+
schema: { $ref: "#/components/schemas/ProjectRegistrationRequest" }
|
|
56838
|
+
}
|
|
56839
|
+
}
|
|
56840
|
+
},
|
|
56841
|
+
responses: {
|
|
56842
|
+
"200": {
|
|
56843
|
+
content: {
|
|
56844
|
+
"application/json": {
|
|
56845
|
+
schema: {
|
|
56846
|
+
type: "object",
|
|
56847
|
+
additionalProperties: false,
|
|
56848
|
+
required: ["verification"],
|
|
56849
|
+
properties: {
|
|
56850
|
+
verification: {
|
|
56851
|
+
type: "object",
|
|
56852
|
+
additionalProperties: false,
|
|
56853
|
+
required: ["target_id", "accepted_receipt_id", "absent", "digest"],
|
|
56854
|
+
properties: {
|
|
56855
|
+
target_id: { type: "string", format: "uuid" },
|
|
56856
|
+
accepted_receipt_id: { type: "string" },
|
|
56857
|
+
absent: { type: "boolean", enum: [true] },
|
|
56858
|
+
digest: { type: "string" }
|
|
56859
|
+
}
|
|
56860
|
+
}
|
|
56861
|
+
}
|
|
56862
|
+
}
|
|
56863
|
+
}
|
|
56864
|
+
}
|
|
56865
|
+
},
|
|
56866
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56867
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56868
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56869
|
+
}
|
|
56870
|
+
}
|
|
56871
|
+
},
|
|
55518
56872
|
"/v1/task-manifest/capability": {
|
|
55519
56873
|
get: {
|
|
55520
56874
|
operationId: "getTaskManifestCapability",
|
|
@@ -55532,6 +56886,82 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
55532
56886
|
}
|
|
55533
56887
|
}
|
|
55534
56888
|
},
|
|
56889
|
+
"/v1/task-manifest/apply": {
|
|
56890
|
+
post: {
|
|
56891
|
+
operationId: "applyTaskManifest",
|
|
56892
|
+
summary: "Apply one exact task-manifest graph through the Todos authority",
|
|
56893
|
+
requestBody: {
|
|
56894
|
+
required: true,
|
|
56895
|
+
content: {
|
|
56896
|
+
"application/json": {
|
|
56897
|
+
schema: { $ref: "#/components/schemas/TaskManifest" }
|
|
56898
|
+
}
|
|
56899
|
+
}
|
|
56900
|
+
},
|
|
56901
|
+
responses: {
|
|
56902
|
+
"201": {
|
|
56903
|
+
content: {
|
|
56904
|
+
"application/json": {
|
|
56905
|
+
schema: { $ref: "#/components/schemas/TaskManifestApplyResponse" }
|
|
56906
|
+
}
|
|
56907
|
+
}
|
|
56908
|
+
},
|
|
56909
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56910
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56911
|
+
"503": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56912
|
+
}
|
|
56913
|
+
}
|
|
56914
|
+
},
|
|
56915
|
+
"/v1/task-manifest/read-exact": {
|
|
56916
|
+
post: {
|
|
56917
|
+
operationId: "readExactTaskManifest",
|
|
56918
|
+
summary: "Read one exact immutable task-manifest apply receipt",
|
|
56919
|
+
requestBody: {
|
|
56920
|
+
required: true,
|
|
56921
|
+
content: {
|
|
56922
|
+
"application/json": {
|
|
56923
|
+
schema: { $ref: "#/components/schemas/TaskManifestReadExactRequest" }
|
|
56924
|
+
}
|
|
56925
|
+
}
|
|
56926
|
+
},
|
|
56927
|
+
responses: {
|
|
56928
|
+
"200": {
|
|
56929
|
+
content: {
|
|
56930
|
+
"application/json": {
|
|
56931
|
+
schema: { $ref: "#/components/schemas/TaskManifestApplyResponse" }
|
|
56932
|
+
}
|
|
56933
|
+
}
|
|
56934
|
+
},
|
|
56935
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56936
|
+
}
|
|
56937
|
+
}
|
|
56938
|
+
},
|
|
56939
|
+
"/v1/task-manifest/compensate": {
|
|
56940
|
+
post: {
|
|
56941
|
+
operationId: "compensateTaskManifest",
|
|
56942
|
+
summary: "Compensate one exact untouched task-manifest graph with CAS protection",
|
|
56943
|
+
requestBody: {
|
|
56944
|
+
required: true,
|
|
56945
|
+
content: {
|
|
56946
|
+
"application/json": {
|
|
56947
|
+
schema: { $ref: "#/components/schemas/TaskManifestCompensateRequest" }
|
|
56948
|
+
}
|
|
56949
|
+
}
|
|
56950
|
+
},
|
|
56951
|
+
responses: {
|
|
56952
|
+
"201": {
|
|
56953
|
+
content: {
|
|
56954
|
+
"application/json": {
|
|
56955
|
+
schema: { $ref: "#/components/schemas/TaskManifestCompensateResponse" }
|
|
56956
|
+
}
|
|
56957
|
+
}
|
|
56958
|
+
},
|
|
56959
|
+
"400": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56960
|
+
"404": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } },
|
|
56961
|
+
"409": { content: { "application/json": { schema: { $ref: "#/components/schemas/ErrorResponse" } } } }
|
|
56962
|
+
}
|
|
56963
|
+
}
|
|
56964
|
+
},
|
|
55535
56965
|
"/v1/task-manifest/bindings/lookup": {
|
|
55536
56966
|
post: {
|
|
55537
56967
|
operationId: "lookupTaskManifestBinding",
|
|
@@ -56392,7 +57822,7 @@ function buildV1OpenApiDocument(version = getPackageVersion()) {
|
|
|
56392
57822
|
}
|
|
56393
57823
|
});
|
|
56394
57824
|
}
|
|
56395
|
-
var taskSchema, taskManifestBoundsSchema, taskManifestCapabilitySchema, taskManifestCapabilityResponseSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, staleLockHandoffInputSchema, staleLockHandoffReceiptSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema;
|
|
57825
|
+
var taskSchema, taskManifestBoundsSchema, taskManifestCapabilitySchema, taskManifestCapabilityResponseSchema, projectSchema, taskManifestBindingLookupRequestSchema, taskManifestBindingLookupResultSchema, taskManifestSchema, taskManifestReceiptSchema, taskManifestApplyResultSchema, taskManifestApplyResponseSchema, taskManifestCompensateRequestSchema, taskManifestCompensationResultSchema, taskManifestCompensateResponseSchema, taskManifestReadExactRequestSchema, taskManifestBindingLookupResponseSchema, taskListSchema, projectTaskListEnsureReceiptSchema, projectTaskListEnsureResultSchema, projectTaskListRollbackResultSchema, taskCommentSchema, staleLockHandoffInputSchema, staleLockHandoffReceiptSchema, taskGitRefSchema, planSchema, planProjectLinkReceiptSchema, planProjectLinkResultSchema, planProjectLinkRollbackResultSchema, templateTaskSchema, templateSchema, templateVariableSchema, createTemplateTaskInputSchema, projectRegistrationBoundsProperties, projectRegistrationReceiptSchema, projectRegistrationCapabilitySchema, projectRegistrationRequestSchema, projectRegistrationLookupRequestSchema, priorRegistrationAdoptionValidationSchema, projectResourceSchema, projectResourcePageSchema;
|
|
56396
57826
|
var init_openapi = __esm(() => {
|
|
56397
57827
|
init_package_version();
|
|
56398
57828
|
init_types();
|
|
@@ -56454,6 +57884,10 @@ var init_openapi = __esm(() => {
|
|
|
56454
57884
|
"tenant_id",
|
|
56455
57885
|
"backend",
|
|
56456
57886
|
"deterministic_ids",
|
|
57887
|
+
"operation_step_identity",
|
|
57888
|
+
"deterministic_idempotency_keys",
|
|
57889
|
+
"terminal_nonacceptance_receipts",
|
|
57890
|
+
"plan_slug_provenance",
|
|
56457
57891
|
"immutable_receipts",
|
|
56458
57892
|
"transactional_outbox",
|
|
56459
57893
|
"idempotent_outbox_delivery",
|
|
@@ -56469,6 +57903,10 @@ var init_openapi = __esm(() => {
|
|
|
56469
57903
|
tenant_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
56470
57904
|
backend: { type: "string", enum: ["sqlite", "postgresql", "http"] },
|
|
56471
57905
|
deterministic_ids: { type: "boolean", enum: [true] },
|
|
57906
|
+
operation_step_identity: { type: "boolean", enum: [true] },
|
|
57907
|
+
deterministic_idempotency_keys: { type: "boolean", enum: [true] },
|
|
57908
|
+
terminal_nonacceptance_receipts: { type: "boolean", enum: [true] },
|
|
57909
|
+
plan_slug_provenance: { type: "string", enum: ["deterministic-v1"] },
|
|
56472
57910
|
immutable_receipts: { type: "boolean", enum: [true] },
|
|
56473
57911
|
transactional_outbox: { type: "boolean", enum: [true] },
|
|
56474
57912
|
idempotent_outbox_delivery: { type: "boolean", enum: [true] },
|
|
@@ -56522,6 +57960,8 @@ var init_openapi = __esm(() => {
|
|
|
56522
57960
|
"schema_version",
|
|
56523
57961
|
"tenant_id",
|
|
56524
57962
|
"plan_id",
|
|
57963
|
+
"operation_id",
|
|
57964
|
+
"step_id",
|
|
56525
57965
|
"apply_receipt_id",
|
|
56526
57966
|
"binding_version",
|
|
56527
57967
|
"state"
|
|
@@ -56532,11 +57972,169 @@ var init_openapi = __esm(() => {
|
|
|
56532
57972
|
schema_version: { type: "integer", enum: [1] },
|
|
56533
57973
|
tenant_id: { type: "string" },
|
|
56534
57974
|
plan_id: { type: "string", format: "uuid" },
|
|
57975
|
+
operation_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
57976
|
+
step_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
56535
57977
|
apply_receipt_id: { type: "string", format: "uuid" },
|
|
56536
57978
|
binding_version: { type: "integer", minimum: 1 },
|
|
56537
57979
|
state: { type: "string", enum: ["applied", "compensated"] }
|
|
56538
57980
|
}
|
|
56539
57981
|
};
|
|
57982
|
+
taskManifestSchema = {
|
|
57983
|
+
type: "object",
|
|
57984
|
+
additionalProperties: false,
|
|
57985
|
+
required: [
|
|
57986
|
+
"version",
|
|
57987
|
+
"operation_id",
|
|
57988
|
+
"step_id",
|
|
57989
|
+
"idempotency_key",
|
|
57990
|
+
"precondition_digest",
|
|
57991
|
+
"project_id",
|
|
57992
|
+
"plan",
|
|
57993
|
+
"tasks"
|
|
57994
|
+
],
|
|
57995
|
+
properties: {
|
|
57996
|
+
version: { type: "integer", enum: [1] },
|
|
57997
|
+
operation_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
57998
|
+
step_id: { type: "string", minLength: 1, maxLength: 200 },
|
|
57999
|
+
idempotency_key: { type: "string", pattern: "^tmk_[0-9a-f]{48}$" },
|
|
58000
|
+
precondition_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
58001
|
+
project_id: { type: "string", format: "uuid" },
|
|
58002
|
+
task_list_id: { type: "string", format: "uuid" },
|
|
58003
|
+
if_binding_version: { type: "integer", minimum: 0 },
|
|
58004
|
+
plan: {
|
|
58005
|
+
type: "object",
|
|
58006
|
+
additionalProperties: false,
|
|
58007
|
+
required: ["key", "name"],
|
|
58008
|
+
properties: {
|
|
58009
|
+
key: { type: "string", minLength: 1, maxLength: 200 },
|
|
58010
|
+
name: { type: "string", minLength: 1, maxLength: 200 },
|
|
58011
|
+
description: { type: "string" },
|
|
58012
|
+
status: { type: "string", enum: ["active", "completed", "archived"] }
|
|
58013
|
+
}
|
|
58014
|
+
},
|
|
58015
|
+
tasks: {
|
|
58016
|
+
type: "array",
|
|
58017
|
+
minItems: 1,
|
|
58018
|
+
items: {
|
|
58019
|
+
type: "object",
|
|
58020
|
+
additionalProperties: false,
|
|
58021
|
+
required: ["key", "title"],
|
|
58022
|
+
properties: {
|
|
58023
|
+
key: { type: "string", minLength: 1, maxLength: 200 },
|
|
58024
|
+
title: { type: "string", minLength: 1, maxLength: 200 },
|
|
58025
|
+
description: { type: "string" },
|
|
58026
|
+
status: { type: "string", enum: ["pending", "in_progress", "completed", "failed", "cancelled"] },
|
|
58027
|
+
priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
|
|
58028
|
+
assigned_to: { type: "string" },
|
|
58029
|
+
created_by: { type: "string" },
|
|
58030
|
+
tags: { type: "array", items: { type: "string" } },
|
|
58031
|
+
metadata: { type: "object", additionalProperties: true },
|
|
58032
|
+
comments: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
58033
|
+
verifications: { type: "array", items: { type: "object", additionalProperties: true } }
|
|
58034
|
+
}
|
|
58035
|
+
}
|
|
58036
|
+
},
|
|
58037
|
+
dependencies: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
58038
|
+
effects: { type: "array", items: { type: "object", additionalProperties: true } }
|
|
58039
|
+
}
|
|
58040
|
+
};
|
|
58041
|
+
taskManifestReceiptSchema = {
|
|
58042
|
+
type: "object",
|
|
58043
|
+
additionalProperties: false,
|
|
58044
|
+
required: [
|
|
58045
|
+
"receipt_id",
|
|
58046
|
+
"authority",
|
|
58047
|
+
"route",
|
|
58048
|
+
"schema_version",
|
|
58049
|
+
"kind",
|
|
58050
|
+
"operation_id",
|
|
58051
|
+
"step_id",
|
|
58052
|
+
"idempotency_key",
|
|
58053
|
+
"request_digest",
|
|
58054
|
+
"precondition_digest",
|
|
58055
|
+
"result_digest",
|
|
58056
|
+
"outcome",
|
|
58057
|
+
"reason",
|
|
58058
|
+
"duplicate_of_receipt_id",
|
|
58059
|
+
"binding_version",
|
|
58060
|
+
"apply_receipt_id",
|
|
58061
|
+
"created_at"
|
|
58062
|
+
],
|
|
58063
|
+
properties: {
|
|
58064
|
+
receipt_id: { type: "string", format: "uuid" },
|
|
58065
|
+
authority: { type: "string", enum: ["todos"] },
|
|
58066
|
+
route: { type: "string", enum: ["todos.task-manifest.v1"] },
|
|
58067
|
+
schema_version: { type: "integer", enum: [1] },
|
|
58068
|
+
kind: { type: "string", enum: ["apply", "compensate"] },
|
|
58069
|
+
operation_id: { type: "string" },
|
|
58070
|
+
step_id: { type: "string" },
|
|
58071
|
+
idempotency_key: { type: "string" },
|
|
58072
|
+
request_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
58073
|
+
precondition_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
58074
|
+
result_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
58075
|
+
outcome: { type: "string", enum: ["accepted", "duplicate_of_accepted", "terminal_nonacceptance"] },
|
|
58076
|
+
reason: { type: "string", nullable: true },
|
|
58077
|
+
duplicate_of_receipt_id: { type: "string", nullable: true },
|
|
58078
|
+
binding_version: { type: "integer", minimum: 0 },
|
|
58079
|
+
apply_receipt_id: { type: "string", nullable: true },
|
|
58080
|
+
created_at: { type: "string", format: "date-time" }
|
|
58081
|
+
}
|
|
58082
|
+
};
|
|
58083
|
+
taskManifestApplyResultSchema = {
|
|
58084
|
+
type: "object",
|
|
58085
|
+
additionalProperties: false,
|
|
58086
|
+
required: ["duplicate", "receipt", "graph", "readback", "outbox_ids", "result_digest"],
|
|
58087
|
+
properties: {
|
|
58088
|
+
duplicate: { type: "boolean" },
|
|
58089
|
+
receipt: { $ref: "#/components/schemas/TaskManifestReceipt" },
|
|
58090
|
+
graph: { type: "object", additionalProperties: true },
|
|
58091
|
+
readback: { type: "object", additionalProperties: true },
|
|
58092
|
+
outbox_ids: { type: "array", items: { type: "string", format: "uuid" } },
|
|
58093
|
+
result_digest: { type: "string", pattern: "^[0-9a-f]{64}$" }
|
|
58094
|
+
}
|
|
58095
|
+
};
|
|
58096
|
+
taskManifestApplyResponseSchema = {
|
|
58097
|
+
type: "object",
|
|
58098
|
+
additionalProperties: false,
|
|
58099
|
+
required: ["result"],
|
|
58100
|
+
properties: { result: { $ref: "#/components/schemas/TaskManifestApplyResult" } }
|
|
58101
|
+
};
|
|
58102
|
+
taskManifestCompensateRequestSchema = {
|
|
58103
|
+
type: "object",
|
|
58104
|
+
additionalProperties: false,
|
|
58105
|
+
required: ["receipt_id", "operation_id", "step_id", "idempotency_key", "precondition_digest", "if_binding_version"],
|
|
58106
|
+
properties: {
|
|
58107
|
+
receipt_id: { type: "string", format: "uuid" },
|
|
58108
|
+
operation_id: { type: "string" },
|
|
58109
|
+
step_id: { type: "string" },
|
|
58110
|
+
idempotency_key: { type: "string", pattern: "^tmk_[0-9a-f]{48}$" },
|
|
58111
|
+
precondition_digest: { type: "string", pattern: "^[0-9a-f]{64}$" },
|
|
58112
|
+
if_binding_version: { type: "integer", minimum: 1 }
|
|
58113
|
+
}
|
|
58114
|
+
};
|
|
58115
|
+
taskManifestCompensationResultSchema = {
|
|
58116
|
+
type: "object",
|
|
58117
|
+
additionalProperties: false,
|
|
58118
|
+
required: ["duplicate", "receipt", "absent", "readback"],
|
|
58119
|
+
properties: {
|
|
58120
|
+
duplicate: { type: "boolean" },
|
|
58121
|
+
receipt: { $ref: "#/components/schemas/TaskManifestReceipt" },
|
|
58122
|
+
absent: { type: "boolean", enum: [true] },
|
|
58123
|
+
readback: { type: "object", additionalProperties: true }
|
|
58124
|
+
}
|
|
58125
|
+
};
|
|
58126
|
+
taskManifestCompensateResponseSchema = {
|
|
58127
|
+
type: "object",
|
|
58128
|
+
additionalProperties: false,
|
|
58129
|
+
required: ["result"],
|
|
58130
|
+
properties: { result: { $ref: "#/components/schemas/TaskManifestCompensationResult" } }
|
|
58131
|
+
};
|
|
58132
|
+
taskManifestReadExactRequestSchema = {
|
|
58133
|
+
type: "object",
|
|
58134
|
+
additionalProperties: false,
|
|
58135
|
+
required: ["receipt_id"],
|
|
58136
|
+
properties: { receipt_id: { type: "string", format: "uuid" } }
|
|
58137
|
+
};
|
|
56540
58138
|
taskManifestBindingLookupResponseSchema = {
|
|
56541
58139
|
type: "object",
|
|
56542
58140
|
additionalProperties: false,
|
|
@@ -56886,6 +58484,292 @@ var init_openapi = __esm(() => {
|
|
|
56886
58484
|
metadata: { type: "object", additionalProperties: true }
|
|
56887
58485
|
}
|
|
56888
58486
|
};
|
|
58487
|
+
projectRegistrationBoundsProperties = {
|
|
58488
|
+
response_byte_limit: { type: "integer", minimum: 1 },
|
|
58489
|
+
time_budget_ms: { type: "integer", minimum: 1 }
|
|
58490
|
+
};
|
|
58491
|
+
projectRegistrationReceiptSchema = {
|
|
58492
|
+
type: "object",
|
|
58493
|
+
additionalProperties: false,
|
|
58494
|
+
required: [
|
|
58495
|
+
"receipt_id",
|
|
58496
|
+
"authority",
|
|
58497
|
+
"route",
|
|
58498
|
+
"package_version",
|
|
58499
|
+
"authority_id",
|
|
58500
|
+
"tenant_id",
|
|
58501
|
+
"corpus_id",
|
|
58502
|
+
"operation_id",
|
|
58503
|
+
"step_id",
|
|
58504
|
+
"resource_kind",
|
|
58505
|
+
"direction",
|
|
58506
|
+
"idempotency_key",
|
|
58507
|
+
"request_digest",
|
|
58508
|
+
"precondition_digest",
|
|
58509
|
+
"outcome",
|
|
58510
|
+
"reason",
|
|
58511
|
+
"target_id",
|
|
58512
|
+
"result_revision",
|
|
58513
|
+
"result_digest",
|
|
58514
|
+
"duplicate_of_receipt_id",
|
|
58515
|
+
"accepted_receipt_id",
|
|
58516
|
+
"created_by_operation",
|
|
58517
|
+
"created_at"
|
|
58518
|
+
],
|
|
58519
|
+
properties: {
|
|
58520
|
+
receipt_id: { type: "string" },
|
|
58521
|
+
authority: { type: "string", enum: ["todos"] },
|
|
58522
|
+
route: { type: "string", enum: ["todos.project-registration.v1"] },
|
|
58523
|
+
package_version: { type: "string" },
|
|
58524
|
+
authority_id: { type: "string" },
|
|
58525
|
+
tenant_id: { type: "string" },
|
|
58526
|
+
corpus_id: { type: "string" },
|
|
58527
|
+
operation_id: { type: "string" },
|
|
58528
|
+
step_id: { type: "string" },
|
|
58529
|
+
resource_kind: { type: "string", enum: ["project", "task_list"] },
|
|
58530
|
+
direction: { type: "string", enum: ["forward", "inverse"] },
|
|
58531
|
+
idempotency_key: { type: "string" },
|
|
58532
|
+
request_digest: { type: "string" },
|
|
58533
|
+
precondition_digest: { type: "string" },
|
|
58534
|
+
outcome: {
|
|
58535
|
+
type: "string",
|
|
58536
|
+
enum: ["accepted", "duplicate_of_accepted", "terminal_nonacceptance"]
|
|
58537
|
+
},
|
|
58538
|
+
reason: { type: "string", nullable: true },
|
|
58539
|
+
target_id: { type: "string", format: "uuid", nullable: true },
|
|
58540
|
+
result_revision: { type: "string", nullable: true },
|
|
58541
|
+
result_digest: { type: "string", nullable: true },
|
|
58542
|
+
duplicate_of_receipt_id: { type: "string", nullable: true },
|
|
58543
|
+
accepted_receipt_id: { type: "string", nullable: true },
|
|
58544
|
+
created_by_operation: { type: "boolean" },
|
|
58545
|
+
created_at: { type: "string", format: "date-time" }
|
|
58546
|
+
}
|
|
58547
|
+
};
|
|
58548
|
+
projectRegistrationCapabilitySchema = {
|
|
58549
|
+
type: "object",
|
|
58550
|
+
additionalProperties: false,
|
|
58551
|
+
required: [
|
|
58552
|
+
"authority",
|
|
58553
|
+
"route",
|
|
58554
|
+
"package_version",
|
|
58555
|
+
"authority_id",
|
|
58556
|
+
"tenant_id",
|
|
58557
|
+
"corpus_id",
|
|
58558
|
+
"supported_resources",
|
|
58559
|
+
"conditional_create",
|
|
58560
|
+
"immutable_receipts",
|
|
58561
|
+
"exact_terminal_lookup",
|
|
58562
|
+
"exact_readback",
|
|
58563
|
+
"bind_existing_adoption",
|
|
58564
|
+
"prior_registration_adoption_validation",
|
|
58565
|
+
"project_resource_enumeration",
|
|
58566
|
+
"project_resource_page_limit",
|
|
58567
|
+
"conditional_inverse",
|
|
58568
|
+
"ambiguous_outcome_reconciliation"
|
|
58569
|
+
],
|
|
58570
|
+
properties: {
|
|
58571
|
+
authority: { type: "string", enum: ["todos"] },
|
|
58572
|
+
route: { type: "string", enum: ["todos.project-registration.v1"] },
|
|
58573
|
+
package_version: { type: "string" },
|
|
58574
|
+
authority_id: { type: "string" },
|
|
58575
|
+
tenant_id: { type: "string" },
|
|
58576
|
+
corpus_id: { type: "string" },
|
|
58577
|
+
supported_resources: {
|
|
58578
|
+
type: "array",
|
|
58579
|
+
items: { type: "string", enum: ["project", "task_list"] }
|
|
58580
|
+
},
|
|
58581
|
+
conditional_create: { type: "boolean", enum: [true] },
|
|
58582
|
+
immutable_receipts: { type: "boolean", enum: [true] },
|
|
58583
|
+
exact_terminal_lookup: { type: "boolean", enum: [true] },
|
|
58584
|
+
exact_readback: { type: "boolean", enum: [true] },
|
|
58585
|
+
bind_existing_adoption: { type: "boolean", enum: [true] },
|
|
58586
|
+
prior_registration_adoption_validation: { type: "boolean", enum: [true] },
|
|
58587
|
+
project_resource_enumeration: { type: "boolean", enum: [true] },
|
|
58588
|
+
project_resource_page_limit: { type: "integer", minimum: 1 },
|
|
58589
|
+
conditional_inverse: { type: "boolean", enum: [true] },
|
|
58590
|
+
ambiguous_outcome_reconciliation: { type: "boolean", enum: [true] }
|
|
58591
|
+
}
|
|
58592
|
+
};
|
|
58593
|
+
projectRegistrationRequestSchema = {
|
|
58594
|
+
type: "object",
|
|
58595
|
+
additionalProperties: false,
|
|
58596
|
+
required: [
|
|
58597
|
+
"operation_id",
|
|
58598
|
+
"step_id",
|
|
58599
|
+
"resource_kind",
|
|
58600
|
+
"direction",
|
|
58601
|
+
"authority_route",
|
|
58602
|
+
"package_version",
|
|
58603
|
+
"authority_id",
|
|
58604
|
+
"tenant_id",
|
|
58605
|
+
"corpus_id",
|
|
58606
|
+
"target_selector",
|
|
58607
|
+
"idempotency_key",
|
|
58608
|
+
"request_digest",
|
|
58609
|
+
"precondition_digest",
|
|
58610
|
+
"project_id",
|
|
58611
|
+
"project_slug",
|
|
58612
|
+
"project_name",
|
|
58613
|
+
"desired",
|
|
58614
|
+
"response_byte_limit",
|
|
58615
|
+
"time_budget_ms"
|
|
58616
|
+
],
|
|
58617
|
+
properties: {
|
|
58618
|
+
operation_id: { type: "string" },
|
|
58619
|
+
step_id: { type: "string" },
|
|
58620
|
+
resource_kind: { type: "string", enum: ["project", "task_list"] },
|
|
58621
|
+
direction: { type: "string", enum: ["forward", "inverse"] },
|
|
58622
|
+
authority_route: { type: "string" },
|
|
58623
|
+
package_version: { type: "string" },
|
|
58624
|
+
authority_id: { type: "string" },
|
|
58625
|
+
tenant_id: { type: "string" },
|
|
58626
|
+
corpus_id: { type: "string" },
|
|
58627
|
+
target_selector: { type: "string" },
|
|
58628
|
+
idempotency_key: { type: "string" },
|
|
58629
|
+
request_digest: { type: "string" },
|
|
58630
|
+
precondition_digest: { type: "string" },
|
|
58631
|
+
project_id: { type: "string" },
|
|
58632
|
+
project_slug: { type: "string" },
|
|
58633
|
+
project_name: { type: "string" },
|
|
58634
|
+
desired: { type: "object", additionalProperties: true },
|
|
58635
|
+
bind_existing: { type: "boolean" },
|
|
58636
|
+
accepted_receipt: { $ref: "#/components/schemas/ProjectRegistrationReceipt" },
|
|
58637
|
+
...projectRegistrationBoundsProperties
|
|
58638
|
+
}
|
|
58639
|
+
};
|
|
58640
|
+
projectRegistrationLookupRequestSchema = {
|
|
58641
|
+
type: "object",
|
|
58642
|
+
additionalProperties: false,
|
|
58643
|
+
required: [
|
|
58644
|
+
"operation_id",
|
|
58645
|
+
"step_id",
|
|
58646
|
+
"resource_kind",
|
|
58647
|
+
"direction",
|
|
58648
|
+
"authority",
|
|
58649
|
+
"authority_route",
|
|
58650
|
+
"package_version",
|
|
58651
|
+
"authority_id",
|
|
58652
|
+
"tenant_id",
|
|
58653
|
+
"corpus_id",
|
|
58654
|
+
"target_selector",
|
|
58655
|
+
"idempotency_key",
|
|
58656
|
+
"max_items",
|
|
58657
|
+
"response_byte_limit",
|
|
58658
|
+
"time_budget_ms"
|
|
58659
|
+
],
|
|
58660
|
+
properties: {
|
|
58661
|
+
operation_id: { type: "string" },
|
|
58662
|
+
step_id: { type: "string" },
|
|
58663
|
+
resource_kind: { type: "string", enum: ["project", "task_list"] },
|
|
58664
|
+
direction: { type: "string", enum: ["forward", "inverse"] },
|
|
58665
|
+
authority: { type: "string", enum: ["todos"] },
|
|
58666
|
+
authority_route: { type: "string" },
|
|
58667
|
+
package_version: { type: "string" },
|
|
58668
|
+
authority_id: { type: "string" },
|
|
58669
|
+
tenant_id: { type: "string" },
|
|
58670
|
+
corpus_id: { type: "string" },
|
|
58671
|
+
target_selector: { type: "string" },
|
|
58672
|
+
idempotency_key: { type: "string" },
|
|
58673
|
+
target_id: { type: "string", format: "uuid" },
|
|
58674
|
+
max_items: { type: "integer", enum: [1] },
|
|
58675
|
+
...projectRegistrationBoundsProperties
|
|
58676
|
+
}
|
|
58677
|
+
};
|
|
58678
|
+
priorRegistrationAdoptionValidationSchema = {
|
|
58679
|
+
type: "object",
|
|
58680
|
+
additionalProperties: false,
|
|
58681
|
+
required: [
|
|
58682
|
+
"valid",
|
|
58683
|
+
"resource_kind",
|
|
58684
|
+
"target_id",
|
|
58685
|
+
"source_receipt_id",
|
|
58686
|
+
"accepted_receipt_id",
|
|
58687
|
+
"source_outcome",
|
|
58688
|
+
"created_at",
|
|
58689
|
+
"current_revision",
|
|
58690
|
+
"accepted_result_digest"
|
|
58691
|
+
],
|
|
58692
|
+
properties: {
|
|
58693
|
+
valid: { type: "boolean", enum: [true] },
|
|
58694
|
+
resource_kind: { type: "string", enum: ["project", "task_list"] },
|
|
58695
|
+
target_id: { type: "string", format: "uuid" },
|
|
58696
|
+
source_receipt_id: { type: "string" },
|
|
58697
|
+
accepted_receipt_id: { type: "string" },
|
|
58698
|
+
source_outcome: { type: "string", enum: ["accepted", "duplicate_of_accepted"] },
|
|
58699
|
+
created_at: { type: "string", format: "date-time" },
|
|
58700
|
+
current_revision: { type: "string", format: "date-time" },
|
|
58701
|
+
accepted_result_digest: { type: "string" }
|
|
58702
|
+
}
|
|
58703
|
+
};
|
|
58704
|
+
projectResourceSchema = {
|
|
58705
|
+
type: "object",
|
|
58706
|
+
additionalProperties: false,
|
|
58707
|
+
required: [
|
|
58708
|
+
"source_project_id",
|
|
58709
|
+
"kind",
|
|
58710
|
+
"scope",
|
|
58711
|
+
"target_id",
|
|
58712
|
+
"parent_id",
|
|
58713
|
+
"revision",
|
|
58714
|
+
"digest"
|
|
58715
|
+
],
|
|
58716
|
+
properties: {
|
|
58717
|
+
source_project_id: { type: "string" },
|
|
58718
|
+
kind: { type: "string", enum: ["project", "task_list", "plan", "task"] },
|
|
58719
|
+
scope: { type: "string", enum: ["collection", "resource"] },
|
|
58720
|
+
target_id: { type: "string", format: "uuid" },
|
|
58721
|
+
parent_id: { type: "string", format: "uuid", nullable: true },
|
|
58722
|
+
revision: { type: "string" },
|
|
58723
|
+
digest: { type: "string" }
|
|
58724
|
+
}
|
|
58725
|
+
};
|
|
58726
|
+
projectResourcePageSchema = {
|
|
58727
|
+
type: "object",
|
|
58728
|
+
additionalProperties: false,
|
|
58729
|
+
required: [
|
|
58730
|
+
"authority",
|
|
58731
|
+
"route",
|
|
58732
|
+
"package_version",
|
|
58733
|
+
"authority_id",
|
|
58734
|
+
"tenant_id",
|
|
58735
|
+
"corpus_id",
|
|
58736
|
+
"source_project_id",
|
|
58737
|
+
"todos_project_id",
|
|
58738
|
+
"task_list_id",
|
|
58739
|
+
"include_anchors",
|
|
58740
|
+
"collection_revision",
|
|
58741
|
+
"limit",
|
|
58742
|
+
"count",
|
|
58743
|
+
"resources",
|
|
58744
|
+
"has_more",
|
|
58745
|
+
"next_cursor",
|
|
58746
|
+
"complete",
|
|
58747
|
+
"truncated"
|
|
58748
|
+
],
|
|
58749
|
+
properties: {
|
|
58750
|
+
authority: { type: "string", enum: ["todos"] },
|
|
58751
|
+
route: { type: "string", enum: ["todos.project-registration.v1"] },
|
|
58752
|
+
package_version: { type: "string" },
|
|
58753
|
+
authority_id: { type: "string" },
|
|
58754
|
+
tenant_id: { type: "string" },
|
|
58755
|
+
corpus_id: { type: "string" },
|
|
58756
|
+
source_project_id: { type: "string" },
|
|
58757
|
+
todos_project_id: { type: "string", format: "uuid" },
|
|
58758
|
+
task_list_id: { type: "string", format: "uuid" },
|
|
58759
|
+
include_anchors: { type: "boolean" },
|
|
58760
|
+
collection_revision: { type: "string" },
|
|
58761
|
+
limit: { type: "integer", minimum: 1, maximum: 500 },
|
|
58762
|
+
count: { type: "integer", minimum: 0 },
|
|
58763
|
+
resources: {
|
|
58764
|
+
type: "array",
|
|
58765
|
+
items: { $ref: "#/components/schemas/ProjectResource" }
|
|
58766
|
+
},
|
|
58767
|
+
has_more: { type: "boolean" },
|
|
58768
|
+
next_cursor: { type: "string", nullable: true },
|
|
58769
|
+
complete: { type: "boolean" },
|
|
58770
|
+
truncated: { type: "boolean", enum: [false] }
|
|
58771
|
+
}
|
|
58772
|
+
};
|
|
56889
58773
|
});
|
|
56890
58774
|
|
|
56891
58775
|
// src/server/pr-groups.ts
|
|
@@ -57032,11 +58916,11 @@ function canonicalJson2(value) {
|
|
|
57032
58916
|
return `[${value.map(canonicalJson2).join(",")}]`;
|
|
57033
58917
|
return `{${Object.entries(value).filter(([, item]) => item !== undefined).sort(([left], [right]) => left.localeCompare(right)).map(([key2, item]) => `${JSON.stringify(key2)}:${canonicalJson2(item)}`).join(",")}}`;
|
|
57034
58918
|
}
|
|
57035
|
-
function
|
|
58919
|
+
function digest2(value) {
|
|
57036
58920
|
return createHash17("sha256").update(canonicalJson2(value)).digest("hex");
|
|
57037
58921
|
}
|
|
57038
58922
|
function deriveIdempotencyKey(projectId, slug) {
|
|
57039
|
-
return `ptlk_${
|
|
58923
|
+
return `ptlk_${digest2({ project_id: projectId, slug }).slice(0, 48)}`;
|
|
57040
58924
|
}
|
|
57041
58925
|
function normalizeIdempotencyKey(value, projectId, slug) {
|
|
57042
58926
|
const key2 = value?.trim() || deriveIdempotencyKey(projectId, slug);
|
|
@@ -57045,13 +58929,13 @@ function normalizeIdempotencyKey(value, projectId, slug) {
|
|
|
57045
58929
|
}
|
|
57046
58930
|
return key2;
|
|
57047
58931
|
}
|
|
57048
|
-
function receiptId2(projectId, slug,
|
|
57049
|
-
return `ptlr_${
|
|
58932
|
+
function receiptId2(projectId, slug, idempotencyKey2) {
|
|
58933
|
+
return `ptlr_${digest2({ project_id: projectId, slug, idempotency_key: idempotencyKey2 }).slice(0, 48)}`;
|
|
57050
58934
|
}
|
|
57051
58935
|
function semanticListDigest(list) {
|
|
57052
58936
|
const metadata = { ...list.metadata ?? {} };
|
|
57053
58937
|
delete metadata[RECEIPT_METADATA_KEY];
|
|
57054
|
-
return
|
|
58938
|
+
return digest2({
|
|
57055
58939
|
project_id: list.project_id,
|
|
57056
58940
|
slug: list.slug,
|
|
57057
58941
|
name: list.name,
|
|
@@ -57068,10 +58952,10 @@ function storedMarker(list) {
|
|
|
57068
58952
|
return null;
|
|
57069
58953
|
return marker;
|
|
57070
58954
|
}
|
|
57071
|
-
function receiptFor(store, project, list,
|
|
58955
|
+
function receiptFor(store, project, list, idempotencyKey2) {
|
|
57072
58956
|
const marker = storedMarker(list);
|
|
57073
58957
|
const owned = marker?.project_id === project.id && marker.slug === list.slug;
|
|
57074
|
-
if (owned && marker.idempotency_key !==
|
|
58958
|
+
if (owned && marker.idempotency_key !== idempotencyKey2) {
|
|
57075
58959
|
throw new ProjectTaskListEnsureError("PROJECT_TASK_LIST_IDEMPOTENCY_CONFLICT", "The operation-owned task list was created under a different idempotency key", {
|
|
57076
58960
|
project_id: project.id,
|
|
57077
58961
|
task_list_id: list.id,
|
|
@@ -57080,8 +58964,8 @@ function receiptFor(store, project, list, idempotencyKey) {
|
|
|
57080
58964
|
}
|
|
57081
58965
|
return {
|
|
57082
58966
|
schema_version: PROJECT_TASK_LIST_ENSURE_SCHEMA_VERSION,
|
|
57083
|
-
receipt_id: owned ? marker.receipt_id : `ptlr_existing_${
|
|
57084
|
-
idempotency_key: owned ? marker.idempotency_key :
|
|
58967
|
+
receipt_id: owned ? marker.receipt_id : `ptlr_existing_${digest2({ project_id: project.id, task_list_id: list.id }).slice(0, 39)}`,
|
|
58968
|
+
idempotency_key: owned ? marker.idempotency_key : idempotencyKey2,
|
|
57085
58969
|
project_id: project.id,
|
|
57086
58970
|
task_list_id: list.id,
|
|
57087
58971
|
slug: list.slug,
|
|
@@ -57133,20 +59017,20 @@ async function applyProjectTaskListEnsure(store, projectId, options) {
|
|
|
57133
59017
|
});
|
|
57134
59018
|
}
|
|
57135
59019
|
const slug = project.task_list_id;
|
|
57136
|
-
const
|
|
59020
|
+
const idempotencyKey2 = normalizeIdempotencyKey(options.idempotency_key, project.id, slug);
|
|
57137
59021
|
if (state.scoped) {
|
|
57138
59022
|
return {
|
|
57139
59023
|
mode: "apply",
|
|
57140
59024
|
action: "already_present",
|
|
57141
59025
|
project,
|
|
57142
59026
|
task_list: state.scoped,
|
|
57143
|
-
receipt: receiptFor(store, project, state.scoped,
|
|
59027
|
+
receipt: receiptFor(store, project, state.scoped, idempotencyKey2)
|
|
57144
59028
|
};
|
|
57145
59029
|
}
|
|
57146
59030
|
const marker = {
|
|
57147
59031
|
schema_version: PROJECT_TASK_LIST_ENSURE_SCHEMA_VERSION,
|
|
57148
|
-
receipt_id: receiptId2(project.id, slug,
|
|
57149
|
-
idempotency_key:
|
|
59032
|
+
receipt_id: receiptId2(project.id, slug, idempotencyKey2),
|
|
59033
|
+
idempotency_key: idempotencyKey2,
|
|
57150
59034
|
project_id: project.id,
|
|
57151
59035
|
slug,
|
|
57152
59036
|
result_digest: semanticListDigest({
|
|
@@ -57184,7 +59068,7 @@ async function applyProjectTaskListEnsure(store, projectId, options) {
|
|
|
57184
59068
|
action: "already_present",
|
|
57185
59069
|
project: raced.project,
|
|
57186
59070
|
task_list: raced.scoped,
|
|
57187
|
-
receipt: receiptFor(store, raced.project, raced.scoped,
|
|
59071
|
+
receipt: receiptFor(store, raced.project, raced.scoped, idempotencyKey2)
|
|
57188
59072
|
};
|
|
57189
59073
|
}
|
|
57190
59074
|
const projectReadback = await store.projects.get(project.id);
|
|
@@ -57214,7 +59098,7 @@ async function applyProjectTaskListEnsure(store, projectId, options) {
|
|
|
57214
59098
|
action: "created",
|
|
57215
59099
|
project: projectReadback,
|
|
57216
59100
|
task_list: readback,
|
|
57217
|
-
receipt: receiptFor(store, projectReadback, readback,
|
|
59101
|
+
receipt: receiptFor(store, projectReadback, readback, idempotencyKey2)
|
|
57218
59102
|
};
|
|
57219
59103
|
}
|
|
57220
59104
|
async function rollbackProjectTaskListEnsure(store, projectId, options) {
|
|
@@ -57259,7 +59143,7 @@ async function rollbackProjectTaskListEnsure(store, projectId, options) {
|
|
|
57259
59143
|
project_id: project.id,
|
|
57260
59144
|
task_list_id: list.id,
|
|
57261
59145
|
accepted_receipt_id: options.receipt_id,
|
|
57262
|
-
rollback_receipt_id: `ptlr_inverse_${
|
|
59146
|
+
rollback_receipt_id: `ptlr_inverse_${digest2({ accepted_receipt_id: options.receipt_id }).slice(0, 38)}`,
|
|
57263
59147
|
removed_at: new Date().toISOString()
|
|
57264
59148
|
};
|
|
57265
59149
|
}
|
|
@@ -57458,6 +59342,9 @@ function validateTaskPatchVocabulary(value) {
|
|
|
57458
59342
|
if (!parsed.ok)
|
|
57459
59343
|
return { ok: false, message: parsed.message };
|
|
57460
59344
|
}
|
|
59345
|
+
if (body2.parent_id !== undefined && body2.parent_id !== null && (typeof body2.parent_id !== "string" || !body2.parent_id.trim())) {
|
|
59346
|
+
return { ok: false, message: "parent_id must be a non-empty task id or null" };
|
|
59347
|
+
}
|
|
57461
59348
|
return { ok: true, patch: body2 };
|
|
57462
59349
|
}
|
|
57463
59350
|
function validateProjectPatch(value) {
|
|
@@ -57942,8 +59829,8 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
57942
59829
|
}
|
|
57943
59830
|
const created = await store.tasks.create(body2, storageContext);
|
|
57944
59831
|
const persisted = created?.id ? await store.tasks.get(created.id, storageContext) : null;
|
|
57945
|
-
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== (body2.parent_id ?? null)) {
|
|
57946
|
-
return error(500, "TASK_CREATE_PERSISTENCE_UNVERIFIED: task create was acknowledged but authoritative readback did not return the same stored task id and
|
|
59832
|
+
if (!persisted || persisted.id !== created.id || (persisted.parent_id ?? null) !== (body2.parent_id ?? null) || (persisted.plan_id ?? null) !== (body2.plan_id ?? null)) {
|
|
59833
|
+
return error(500, "TASK_CREATE_PERSISTENCE_UNVERIFIED: task create was acknowledged but authoritative readback did not return the same stored task id, parent_id, and plan_id", { code: "TASK_CREATE_PERSISTENCE_UNVERIFIED" });
|
|
57947
59834
|
}
|
|
57948
59835
|
return json5({ task: persisted }, 201);
|
|
57949
59836
|
}
|
|
@@ -58817,6 +60704,15 @@ async function handleV1Request(req, url, dependencies = {}) {
|
|
|
58817
60704
|
if (e instanceof TaskNotFoundError) {
|
|
58818
60705
|
return error(404, e.message, { code: TaskNotFoundError.code });
|
|
58819
60706
|
}
|
|
60707
|
+
if (e instanceof VersionConflictError) {
|
|
60708
|
+
return error(409, e.message, {
|
|
60709
|
+
code: VersionConflictError.code,
|
|
60710
|
+
conflict: true,
|
|
60711
|
+
task_id: e.taskId,
|
|
60712
|
+
expected_version: e.expectedVersion,
|
|
60713
|
+
current_version: e.actualVersion
|
|
60714
|
+
});
|
|
60715
|
+
}
|
|
58820
60716
|
if (e instanceof StaleLockHandoffError) {
|
|
58821
60717
|
const status2 = e.code === "STALE_LOCK_HANDOFF_INVALID_TASK_ID" || e.code === "STALE_LOCK_HANDOFF_INVALID_INPUT" ? 400 : e.code === "STALE_LOCK_HANDOFF_ACTOR_MISMATCH" ? 403 : 409;
|
|
58822
60718
|
return error(status2, e.message, {
|