@xfey/tutti 0.1.127 → 0.1.128
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/approvals/decision-command.d.ts +2 -2
- package/dist/chat-assistant/diagnostics.d.ts +7 -2
- package/dist/chat-assistant/diagnostics.js +5 -2
- package/dist/chat-assistant/failures.d.ts +27 -0
- package/dist/chat-assistant/failures.js +102 -0
- package/dist/chat-assistant/index.d.ts +14 -2
- package/dist/chat-assistant/index.js +226 -48
- package/dist/chat-assistant/requests.d.ts +30 -0
- package/dist/chat-assistant/requests.js +58 -0
- package/dist/collaboration-ingestion/clarification.js +3 -1
- package/dist/collaboration-ingestion/main-chat.js +1 -0
- package/dist/collaboration-ingestion/types.d.ts +2 -0
- package/dist/collaboration-state/clarification-snapshot.d.ts +5 -5
- package/dist/collaboration-state/task-compile-context.d.ts +1 -1
- package/dist/collaboration-state/task-compile-context.js +11 -1
- package/dist/collaboration-state/task-compile-requests.d.ts +6 -0
- package/dist/collaboration-state/task-compile-requests.js +16 -0
- package/dist/control-plane/task-compile-continuation.js +2 -0
- package/dist/control-plane/task-compile-request-status.d.ts +6 -0
- package/dist/control-plane/task-compile-request-status.js +70 -0
- package/dist/control-plane/task-compile-start.js +40 -2
- package/dist/providers/failure-classification.d.ts +23 -0
- package/dist/providers/failure-classification.js +43 -0
- package/dist/reference-access/read-evidence.d.ts +2 -0
- package/dist/reference-access/read-evidence.js +5 -0
- package/dist/reference-access/service.d.ts +2 -0
- package/dist/reference-access/service.js +2 -0
- package/dist/runtime-failures.js +15 -1
- package/dist/server-shell/cli/external-reference-runtime.js +6 -3
- package/dist/server-shell/cli/host-server-runtime.js +4 -1
- package/dist/server-shell/cli/relay-registration.js +1 -0
- package/dist/server-shell/http/routes/project-api/messages-routes.js +22 -1
- package/dist/server-shell/http/routes/project-api/openapi-collaboration-routes.d.ts +140 -5
- package/dist/server-shell/http/routes/project-api/openapi-collaboration-routes.js +29 -2
- package/dist/server-shell/http/routes/project-api/openapi-skills-routes.d.ts +2 -0
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +6 -0
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +148 -5
- package/dist/server-shell/http/routes/project-api/schemas.d.ts +6 -0
- package/dist/server-shell/http/routes/project-api/schemas.js +1 -0
- package/dist/server-shell/http/routes/project-api/types.d.ts +1 -0
- package/dist/server-shell/http/routes/project-api/workflow-routes.js +39 -3
- package/dist/server-shell/http/routes/project-api/workspace-projections.js +1 -0
- package/dist/server-shell/http/validation.js +8 -2
- package/dist/server-shell/local-console/managed-project-service.d.ts +1 -1
- package/dist/server-shell/managed-access/transport.d.ts +1 -1
- package/migrations/0024_chat_requests.sql +19 -0
- package/migrations/0025_task_compile_requests.sql +50 -0
- package/migrations/README.md +3 -1
- package/node_modules/@tutti/relay-client/dist/host-control.d.ts +1 -0
- package/node_modules/@tutti/relay-client/dist/host-control.js +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/operation-status.d.ts +174 -0
- package/node_modules/@tutti/shared/dist/schemas/api/operation-status.js +75 -0
- package/node_modules/@tutti/shared/dist/schemas/api/primitives.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/primitives.js +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.js +2 -1
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-DyEmWwvt.js → homepage-motion-scene-BcMt0AVW.js} +1 -1
- package/web/assets/index-2eX37Jx6.js +70 -0
- package/web/assets/index-BJvKuUiT.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-By3idT4U.js +0 -70
- package/web/assets/index-DSBjOZmJ.css +0 -1
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ChatRequestStatus, type OperationFailure } from "@tutti/shared/schemas/api";
|
|
2
|
+
import type { SqliteDatabase } from "../store/index.js";
|
|
3
|
+
export type ChatRequestRecord = {
|
|
4
|
+
trigger_message_id: string;
|
|
5
|
+
status: "queued" | "running" | "succeeded" | "failed";
|
|
6
|
+
phase: "queue" | "workspace_wait" | "answer" | "settled";
|
|
7
|
+
revision: number;
|
|
8
|
+
attempt_ref: string | null;
|
|
9
|
+
response_message_id: string | null;
|
|
10
|
+
failure_json: string | null;
|
|
11
|
+
created_at: string;
|
|
12
|
+
updated_at: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function readChatRequest(db: SqliteDatabase, messageId: string): ChatRequestRecord | null;
|
|
15
|
+
export declare function chatRequestStatus(row: ChatRequestRecord): ChatRequestStatus;
|
|
16
|
+
export declare function insertChatRequest(db: SqliteDatabase, messageId: string, now: string, failure?: OperationFailure): void;
|
|
17
|
+
export declare function countQueuedChatRequests(db: SqliteDatabase): number;
|
|
18
|
+
export declare function nextQueuedChatRequest(db: SqliteDatabase): ChatRequestRecord | null;
|
|
19
|
+
/** CAS/fencing only. Lifecycle policy belongs to Chat Assistant, not the repository. */
|
|
20
|
+
export declare function updateChatRequest(db: SqliteDatabase, row: ChatRequestRecord, next: {
|
|
21
|
+
status: ChatRequestRecord["status"];
|
|
22
|
+
phase: ChatRequestRecord["phase"];
|
|
23
|
+
attemptRef?: string;
|
|
24
|
+
responseMessageId?: string;
|
|
25
|
+
failure?: OperationFailure;
|
|
26
|
+
}, now: string): ChatRequestRecord | null;
|
|
27
|
+
export declare function listRunningChatRequests(db: SqliteDatabase): ChatRequestRecord[];
|
|
28
|
+
/** Correlation only; never changes lifecycle state based on a progress callback. */
|
|
29
|
+
export declare function linkChatRequestInvocation(db: SqliteDatabase, row: ChatRequestRecord, invocationRef: string): void;
|
|
30
|
+
//# sourceMappingURL=requests.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Value } from "@sinclair/typebox/value";
|
|
2
|
+
import { ChatRequestStatusSchema, } from "@tutti/shared/schemas/api";
|
|
3
|
+
export function readChatRequest(db, messageId) {
|
|
4
|
+
return (db
|
|
5
|
+
.prepare("SELECT * FROM chat_requests WHERE trigger_message_id = ?")
|
|
6
|
+
.get(messageId) ?? null);
|
|
7
|
+
}
|
|
8
|
+
export function chatRequestStatus(row) {
|
|
9
|
+
const value = {
|
|
10
|
+
operation: { kind: "chat_answer", ref: row.trigger_message_id },
|
|
11
|
+
status: row.status,
|
|
12
|
+
phase: row.phase,
|
|
13
|
+
revision: row.revision,
|
|
14
|
+
created_at: row.created_at,
|
|
15
|
+
updated_at: row.updated_at,
|
|
16
|
+
...(row.response_message_id === null ? {} : { response_message_id: row.response_message_id }),
|
|
17
|
+
...(row.failure_json === null ? {} : { failure: JSON.parse(row.failure_json) }),
|
|
18
|
+
};
|
|
19
|
+
if (!Value.Check(ChatRequestStatusSchema, value))
|
|
20
|
+
throw new Error("chat_request_invalid");
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
export function insertChatRequest(db, messageId, now, failure) {
|
|
24
|
+
db.prepare(`INSERT INTO chat_requests
|
|
25
|
+
(trigger_message_id, status, phase, failure_json, created_at, updated_at)
|
|
26
|
+
VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(trigger_message_id) DO NOTHING`).run(messageId, failure ? "failed" : "queued", failure ? "settled" : "queue", failure ? JSON.stringify(failure) : null, now, now);
|
|
27
|
+
}
|
|
28
|
+
export function countQueuedChatRequests(db) {
|
|
29
|
+
return db
|
|
30
|
+
.prepare("SELECT count(*) AS count FROM chat_requests WHERE status = 'queued'")
|
|
31
|
+
.get().count;
|
|
32
|
+
}
|
|
33
|
+
export function nextQueuedChatRequest(db) {
|
|
34
|
+
return (db
|
|
35
|
+
.prepare("SELECT * FROM chat_requests WHERE status = 'queued' ORDER BY enqueue_order LIMIT 1")
|
|
36
|
+
.get() ?? null);
|
|
37
|
+
}
|
|
38
|
+
/** CAS/fencing only. Lifecycle policy belongs to Chat Assistant, not the repository. */
|
|
39
|
+
export function updateChatRequest(db, row, next, now) {
|
|
40
|
+
const changed = db
|
|
41
|
+
.prepare(`UPDATE chat_requests SET status = ?, phase = ?,
|
|
42
|
+
attempt_ref = ?, response_message_id = ?, failure_json = ?, updated_at = ?, revision = revision + 1
|
|
43
|
+
WHERE trigger_message_id = ? AND revision = ?`)
|
|
44
|
+
.run(next.status, next.phase, next.attemptRef ?? row.attempt_ref, next.responseMessageId ?? null, next.failure ? JSON.stringify(next.failure) : null, now, row.trigger_message_id, row.revision).changes;
|
|
45
|
+
return changed === 0 ? null : readChatRequest(db, row.trigger_message_id);
|
|
46
|
+
}
|
|
47
|
+
export function listRunningChatRequests(db) {
|
|
48
|
+
return db
|
|
49
|
+
.prepare("SELECT * FROM chat_requests WHERE status = 'running' ORDER BY created_at, trigger_message_id")
|
|
50
|
+
.all();
|
|
51
|
+
}
|
|
52
|
+
/** Correlation only; never changes lifecycle state based on a progress callback. */
|
|
53
|
+
export function linkChatRequestInvocation(db, row, invocationRef) {
|
|
54
|
+
db.prepare(`INSERT OR IGNORE INTO chat_request_invocations(message_id, attempt_ref, invocation_ref)
|
|
55
|
+
SELECT trigger_message_id, attempt_ref, ? FROM chat_requests
|
|
56
|
+
WHERE trigger_message_id = ? AND attempt_ref = ? AND status = 'running'`).run(invocationRef, row.trigger_message_id, row.attempt_ref);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=requests.js.map
|
|
@@ -21,8 +21,10 @@ export function ingestClarificationMessage(options) {
|
|
|
21
21
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
22
22
|
execute: (tx) => {
|
|
23
23
|
const result = options.controlPlane.sendClarificationRoundMessage(options.roundId, options.payload, options.author);
|
|
24
|
-
if (result.result?.message !== undefined)
|
|
24
|
+
if (result.result?.message !== undefined) {
|
|
25
25
|
options.afterCreate?.(tx, result.result.message);
|
|
26
|
+
options.chatAssistant?.register(tx, result.result.message);
|
|
27
|
+
}
|
|
26
28
|
return result;
|
|
27
29
|
},
|
|
28
30
|
});
|
|
@@ -39,6 +39,7 @@ export function ingestMainChatMessage(options) {
|
|
|
39
39
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
40
40
|
});
|
|
41
41
|
options.afterCreate?.(tx, message);
|
|
42
|
+
options.chatAssistant?.register(tx, message);
|
|
42
43
|
return {
|
|
43
44
|
disposition: { kind: "created", message_id: message.id },
|
|
44
45
|
result: { message },
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AccountAvatarProjection } from "@tutti/shared/domain";
|
|
2
2
|
import type { MessageProjection } from "@tutti/shared/schemas/api";
|
|
3
|
+
import type { SqliteDatabase } from "../store/index.js";
|
|
3
4
|
export type TrustedCollaborationAuthor = {
|
|
4
5
|
kind?: "human" | "development_operator";
|
|
5
6
|
account_ref: string;
|
|
@@ -10,6 +11,7 @@ export type ScratchpadSourceNotifier = {
|
|
|
10
11
|
notify: (message: MessageProjection) => void;
|
|
11
12
|
};
|
|
12
13
|
export type ChatAssistantEnqueuer = {
|
|
14
|
+
register: (tx: SqliteDatabase, message: MessageProjection) => void;
|
|
13
15
|
enqueue: (input: {
|
|
14
16
|
message: MessageProjection;
|
|
15
17
|
}) => unknown;
|
|
@@ -13,8 +13,9 @@ export declare function readSubmittedClarificationMessages(db: SqliteDatabase, r
|
|
|
13
13
|
is_current_round: boolean;
|
|
14
14
|
};
|
|
15
15
|
refs?: {
|
|
16
|
-
task_id?: string;
|
|
17
16
|
activity_ref?: string;
|
|
17
|
+
workflow_ref?: string;
|
|
18
|
+
task_id?: string;
|
|
18
19
|
artifact_feedback?: {
|
|
19
20
|
summary?: string;
|
|
20
21
|
kind: "artifact_feedback";
|
|
@@ -106,15 +107,14 @@ export declare function readSubmittedClarificationMessages(db: SqliteDatabase, r
|
|
|
106
107
|
blob_oid?: string;
|
|
107
108
|
path: string;
|
|
108
109
|
name: string;
|
|
109
|
-
source: "human_upload" | "tutti_output";
|
|
110
110
|
category: "file" | "image";
|
|
111
|
+
source: "human_upload" | "tutti_output";
|
|
111
112
|
size_bytes: number;
|
|
112
113
|
text_read: {
|
|
113
114
|
blocked_reason?: "unknown" | "binary" | "too_large" | "unsupported_encoding" | "restricted_path" | "secret_like";
|
|
114
115
|
can_read: boolean;
|
|
115
116
|
};
|
|
116
117
|
};
|
|
117
|
-
workflow_ref?: string;
|
|
118
118
|
clarification_round_ref?: string;
|
|
119
119
|
clarification_thread_ref?: string;
|
|
120
120
|
task_result?: {
|
|
@@ -141,10 +141,10 @@ export declare function readSubmittedClarificationMessages(db: SqliteDatabase, r
|
|
|
141
141
|
};
|
|
142
142
|
worklist_compile?: {
|
|
143
143
|
kind: "worklist_compile";
|
|
144
|
-
title: string;
|
|
145
144
|
workflow_ref: string;
|
|
145
|
+
title: string;
|
|
146
146
|
tasks: {
|
|
147
|
-
status: "failed" | "pending" | "
|
|
147
|
+
status: "failed" | "pending" | "running" | "done" | "todo";
|
|
148
148
|
task_id: string;
|
|
149
149
|
title: string;
|
|
150
150
|
summary: string;
|
|
@@ -24,7 +24,7 @@ export declare function completeTaskCompileContext(db: SqliteDatabase, input: Co
|
|
|
24
24
|
task_changes: string[];
|
|
25
25
|
receipts: {
|
|
26
26
|
compiled_tasks?: {
|
|
27
|
-
status: "failed" | "pending" | "
|
|
27
|
+
status: "failed" | "pending" | "running" | "done" | "todo";
|
|
28
28
|
task_id: string;
|
|
29
29
|
title: string;
|
|
30
30
|
summary: string;
|
|
@@ -3,6 +3,7 @@ import { readScratchpadProjection } from "./scratchpad.js";
|
|
|
3
3
|
import { readCommunicationLanguage, copyCommunicationLanguage } from "./communication-language.js";
|
|
4
4
|
import { readScratchpadSourceState } from "./scratchpad-source-state.js";
|
|
5
5
|
import { nowIso, stringifyJson } from "./serialization.js";
|
|
6
|
+
import { registerTaskCompileRequest, closeFailedTaskCompileRequest, } from "./task-compile-requests.js";
|
|
6
7
|
function cursorIsAfter(left, right) {
|
|
7
8
|
if (left === undefined) {
|
|
8
9
|
return false;
|
|
@@ -145,6 +146,7 @@ export function recordTaskCompileSubmission(db, input) {
|
|
|
145
146
|
if (readActiveTaskCompileContext(db) !== null) {
|
|
146
147
|
throw new Error("An active Task Compile Context already exists");
|
|
147
148
|
}
|
|
149
|
+
registerTaskCompileRequest(db, input.workflow_ref, input.accepted_activity_ref, nowIso(input.now));
|
|
148
150
|
db.prepare(`
|
|
149
151
|
INSERT INTO pending_task_compile_submissions (
|
|
150
152
|
id,
|
|
@@ -226,6 +228,7 @@ export function captureTaskCompileContext(db, input) {
|
|
|
226
228
|
: { created_at: lastSource.created_at, message_id: lastSource.message_id });
|
|
227
229
|
const after = submission?.source_window.after ?? sourceState.cycle_start_cursor;
|
|
228
230
|
const submittedAt = nowIso(input.now);
|
|
231
|
+
registerTaskCompileRequest(tx, input.workflow_ref, input.captured_by_activity_ref, submittedAt);
|
|
229
232
|
tx.prepare(`
|
|
230
233
|
INSERT INTO active_task_compile_context (
|
|
231
234
|
id,
|
|
@@ -337,9 +340,16 @@ export function rollbackTaskCompileContext(db, input) {
|
|
|
337
340
|
return withHostStoreTransaction(db, (tx) => {
|
|
338
341
|
const context = readActiveTaskCompileContext(tx, input.workflow_ref);
|
|
339
342
|
if (context === null) {
|
|
340
|
-
|
|
343
|
+
const pending = readPendingTaskCompileSubmission(tx, input.workflow_ref);
|
|
344
|
+
if (pending === null)
|
|
345
|
+
return false;
|
|
346
|
+
// Before capture the source cursor has not moved: release only this admission.
|
|
347
|
+
closeFailedTaskCompileRequest(tx, input.workflow_ref, nowIso(input.now));
|
|
348
|
+
tx.prepare("DELETE FROM pending_task_compile_submissions WHERE workflow_ref = ?").run(input.workflow_ref);
|
|
349
|
+
return true;
|
|
341
350
|
}
|
|
342
351
|
const rolledBackAt = nowIso(input.now);
|
|
352
|
+
closeFailedTaskCompileRequest(tx, input.workflow_ref, rolledBackAt);
|
|
343
353
|
const sourceState = readScratchpadSourceState(tx);
|
|
344
354
|
const after = context.source_window.after;
|
|
345
355
|
const through = context.source_window.through;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SqliteDatabase } from "../store/index.js";
|
|
2
|
+
/** Persistence only: callers own acceptance, stage selection and rollback. */
|
|
3
|
+
export declare function registerTaskCompileRequest(db: SqliteDatabase, workflowRef: string, activityRef: string, createdAt: string): void;
|
|
4
|
+
export declare function linkTaskCompileActivity(db: SqliteDatabase, workflowRef: string, activityRef: string): void;
|
|
5
|
+
export declare function closeFailedTaskCompileRequest(db: SqliteDatabase, workflowRef: string, now: string, code?: "task_compile_failed" | "scratchpad_empty_after_refresh"): void;
|
|
6
|
+
//# sourceMappingURL=task-compile-requests.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Persistence only: callers own acceptance, stage selection and rollback. */
|
|
2
|
+
export function registerTaskCompileRequest(db, workflowRef, activityRef, createdAt) {
|
|
3
|
+
db.prepare(`INSERT OR IGNORE INTO task_compile_requests
|
|
4
|
+
(workflow_ref, accepted_activity_ref, current_activity_ref, created_at)
|
|
5
|
+
VALUES (?, ?, ?, ?)`).run(workflowRef, activityRef, activityRef, createdAt);
|
|
6
|
+
}
|
|
7
|
+
export function linkTaskCompileActivity(db, workflowRef, activityRef) {
|
|
8
|
+
db.prepare(`UPDATE task_compile_requests SET current_activity_ref = ?
|
|
9
|
+
WHERE workflow_ref = ? AND closed_at IS NULL`).run(activityRef, workflowRef);
|
|
10
|
+
}
|
|
11
|
+
export function closeFailedTaskCompileRequest(db, workflowRef, now, code = "task_compile_failed") {
|
|
12
|
+
db.prepare(`UPDATE task_compile_requests SET failure_code = ?, closed_at = ?
|
|
13
|
+
WHERE workflow_ref = ? AND closed_at IS NULL
|
|
14
|
+
AND NOT EXISTS (SELECT 1 FROM scratchpad_receipts WHERE workflow_ref = ?)`).run(code, now, workflowRef, workflowRef);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=task-compile-requests.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { materializeClarificationSuccessor, readActiveTaskCompileContext, readClarificationRoundProjection, readWorklistProjection, rollbackTaskCompileContext, } from "../collaboration-state/index.js";
|
|
2
2
|
import { withHostStoreTransaction } from "../store/index.js";
|
|
3
|
+
import { linkTaskCompileActivity } from "../collaboration-state/task-compile-requests.js";
|
|
3
4
|
import { clarificationInvalidates } from "./invalidations.js";
|
|
4
5
|
import { applyTaskCompileProcedureOutput } from "./task-compile-output.js";
|
|
5
6
|
import { readTaskCompileClarificationRounds } from "./task-compile-clarification-context.js";
|
|
@@ -27,6 +28,7 @@ export function startControlPlaneTaskCompileContinuation(options) {
|
|
|
27
28
|
summary: "Continuing task_compile from clarification input.",
|
|
28
29
|
now: options.now,
|
|
29
30
|
onTransition: (state) => {
|
|
31
|
+
linkTaskCompileActivity(options.store.db, workflowRef, state.activity.activity_ref);
|
|
30
32
|
options.publishProcedureTransition(state);
|
|
31
33
|
if (state.transition_kind === "terminal") {
|
|
32
34
|
options.onTerminal?.(state.activity.status === "failed"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ExecutionStatusProjection, WorklistSubmissionStatus } from "@tutti/shared/schemas/api";
|
|
2
|
+
import type { SqliteDatabase } from "../store/index.js";
|
|
3
|
+
/** Pure projection of owned records. A missing receipt is never proof of failure. */
|
|
4
|
+
export declare function readTaskCompileRequestStatus(db: SqliteDatabase, execution: ExecutionStatusProjection, workflowRef?: string): WorklistSubmissionStatus | null;
|
|
5
|
+
export declare function readTaskCompileCommandRef(db: SqliteDatabase, commandId: string): string | undefined;
|
|
6
|
+
//# sourceMappingURL=task-compile-request-status.d.ts.map
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { readActiveClarificationProjection, readActiveTaskCompileContext, readPendingTaskCompileSubmission, } from "../collaboration-state/index.js";
|
|
2
|
+
/** Pure projection of owned records. A missing receipt is never proof of failure. */
|
|
3
|
+
export function readTaskCompileRequestStatus(db, execution, workflowRef) {
|
|
4
|
+
const row = workflowRef === undefined
|
|
5
|
+
? db
|
|
6
|
+
.prepare("SELECT * FROM task_compile_requests ORDER BY rowid DESC LIMIT 1")
|
|
7
|
+
.get()
|
|
8
|
+
: db
|
|
9
|
+
.prepare("SELECT * FROM task_compile_requests WHERE workflow_ref = ?")
|
|
10
|
+
.get(workflowRef);
|
|
11
|
+
if (row === undefined)
|
|
12
|
+
return null;
|
|
13
|
+
const common = {
|
|
14
|
+
operation: { kind: "worklist_submit", ref: row.workflow_ref },
|
|
15
|
+
created_at: row.created_at,
|
|
16
|
+
activity_ref: row.current_activity_ref,
|
|
17
|
+
};
|
|
18
|
+
if (db.prepare("SELECT 1 FROM scratchpad_receipts WHERE workflow_ref = ?").get(row.workflow_ref)) {
|
|
19
|
+
return { ...common, status: "succeeded", phase: "settled" };
|
|
20
|
+
}
|
|
21
|
+
if (row.failure_code !== null) {
|
|
22
|
+
return {
|
|
23
|
+
...common,
|
|
24
|
+
status: "failed",
|
|
25
|
+
phase: "settled",
|
|
26
|
+
failure: {
|
|
27
|
+
code: row.failure_code,
|
|
28
|
+
category: "internal",
|
|
29
|
+
recovery: "none",
|
|
30
|
+
diagnostic_id: row.workflow_ref,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const clarification = readActiveClarificationProjection(db);
|
|
35
|
+
if (clarification.state === "active" &&
|
|
36
|
+
clarification.thread.owner.kind === "task_compile" &&
|
|
37
|
+
clarification.thread.owner.workflow_ref === row.workflow_ref &&
|
|
38
|
+
clarification.round.status === "writable") {
|
|
39
|
+
return {
|
|
40
|
+
...common,
|
|
41
|
+
status: "waiting_input",
|
|
42
|
+
phase: "clarification",
|
|
43
|
+
round_id: clarification.round.id,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const activities = execution.active_activities ?? (execution.active_activity ? [execution.active_activity] : []);
|
|
47
|
+
const active = activities.find((entry) => entry.activity_ref === row.current_activity_ref || entry.workflow_ref === row.workflow_ref);
|
|
48
|
+
if (active !== undefined) {
|
|
49
|
+
const kind = active.workflow_kind;
|
|
50
|
+
const phase = kind === "scratchpad_refresh" ||
|
|
51
|
+
kind === "reference_summary_refresh" ||
|
|
52
|
+
kind === "project_context_bootstrap" ||
|
|
53
|
+
kind === "task_compile"
|
|
54
|
+
? kind
|
|
55
|
+
: "preparing";
|
|
56
|
+
return { ...common, activity_ref: active.activity_ref, status: "running", phase };
|
|
57
|
+
}
|
|
58
|
+
const ref = row.workflow_ref;
|
|
59
|
+
if (readPendingTaskCompileSubmission(db, ref) !== null ||
|
|
60
|
+
readActiveTaskCompileContext(db, ref) !== null) {
|
|
61
|
+
return { ...common, status: "running", phase: "preparing" };
|
|
62
|
+
}
|
|
63
|
+
return { ...common, status: "unknown", phase: "unconfirmed" };
|
|
64
|
+
}
|
|
65
|
+
export function readTaskCompileCommandRef(db, commandId) {
|
|
66
|
+
return db
|
|
67
|
+
.prepare("SELECT workflow_ref FROM task_compile_request_commands WHERE command_id = ?")
|
|
68
|
+
.get(commandId)?.workflow_ref;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=task-compile-request-status.js.map
|
|
@@ -12,10 +12,15 @@ import { enqueueControlPlaneIntent } from "./dispatch-intents.js";
|
|
|
12
12
|
import { materializePromptSourceMessages } from "./task-source-context.js";
|
|
13
13
|
import { materializeRecentReferences, readPendingRecentReferenceSummaryPaths, readRecentReferenceSources, } from "./recent-references.js";
|
|
14
14
|
import { startReferenceSummaryRefreshProcedure } from "./reference-summary-refresh.js";
|
|
15
|
+
import { linkTaskCompileActivity, closeFailedTaskCompileRequest, } from "../collaboration-state/task-compile-requests.js";
|
|
15
16
|
export function startControlPlaneTaskCompile(options) {
|
|
16
17
|
const activeContext = readActiveTaskCompileContext(options.store.db);
|
|
17
18
|
if (activeContext !== null) {
|
|
18
19
|
return {
|
|
20
|
+
result: {
|
|
21
|
+
submission_ref: activeContext.workflow_ref,
|
|
22
|
+
activity_ref: activeContext.captured_by_activity_ref,
|
|
23
|
+
},
|
|
19
24
|
disposition: {
|
|
20
25
|
kind: "already_running",
|
|
21
26
|
activity_ref: activeContext.captured_by_activity_ref,
|
|
@@ -25,6 +30,10 @@ export function startControlPlaneTaskCompile(options) {
|
|
|
25
30
|
const pendingSubmission = readPendingTaskCompileSubmission(options.store.db);
|
|
26
31
|
if (pendingSubmission !== null) {
|
|
27
32
|
return {
|
|
33
|
+
result: {
|
|
34
|
+
submission_ref: pendingSubmission.workflow_ref,
|
|
35
|
+
activity_ref: pendingSubmission.accepted_activity_ref,
|
|
36
|
+
},
|
|
28
37
|
disposition: {
|
|
29
38
|
kind: "already_running",
|
|
30
39
|
activity_ref: pendingSubmission.accepted_activity_ref,
|
|
@@ -72,7 +81,7 @@ export function startControlPlaneTaskCompile(options) {
|
|
|
72
81
|
},
|
|
73
82
|
};
|
|
74
83
|
}
|
|
75
|
-
|
|
84
|
+
const started = startScratchpadRefreshBeforeTaskCompileActivity({
|
|
76
85
|
...options,
|
|
77
86
|
runner: runnerResolution.runner,
|
|
78
87
|
sourceBatch,
|
|
@@ -86,6 +95,7 @@ export function startControlPlaneTaskCompile(options) {
|
|
|
86
95
|
now: options.now,
|
|
87
96
|
}),
|
|
88
97
|
});
|
|
98
|
+
return withSubmissionRef(started, taskCompileWorkflowRef);
|
|
89
99
|
}
|
|
90
100
|
if (!scratchpadHasSubmittableContent(scratchpad)) {
|
|
91
101
|
return { disposition: { kind: "scratchpad_empty" } };
|
|
@@ -108,7 +118,12 @@ export function startControlPlaneTaskCompile(options) {
|
|
|
108
118
|
now: options.now,
|
|
109
119
|
});
|
|
110
120
|
}
|
|
111
|
-
return started;
|
|
121
|
+
return withSubmissionRef(started, taskCompileWorkflowRef);
|
|
122
|
+
}
|
|
123
|
+
function withSubmissionRef(result, submissionRef) {
|
|
124
|
+
return result.disposition.kind === "accepted"
|
|
125
|
+
? { ...result, result: { ...result.result, submission_ref: submissionRef } }
|
|
126
|
+
: result;
|
|
112
127
|
}
|
|
113
128
|
function recordTaskCompileSubmissionIntent(options) {
|
|
114
129
|
return withHostStoreTransaction(options.store.db, (tx) => {
|
|
@@ -152,6 +167,7 @@ function startControlPlaneTaskCompileActivity(options) {
|
|
|
152
167
|
summary: "Compiling Scratchpad into Worklist tasks.",
|
|
153
168
|
now: options.now,
|
|
154
169
|
onTransition: (state) => {
|
|
170
|
+
linkTaskCompileActivity(options.store.db, options.taskCompileWorkflowRef, state.activity.activity_ref);
|
|
155
171
|
options.publishProcedureTransition(state);
|
|
156
172
|
if (state.transition_kind === "terminal") {
|
|
157
173
|
options.onChainTerminal?.(state.activity.status === "failed"
|
|
@@ -206,11 +222,16 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
|
|
|
206
222
|
summary: "Refreshing Scratchpad before submitting to Worklist.",
|
|
207
223
|
now: options.now,
|
|
208
224
|
onTransition: (state) => {
|
|
225
|
+
linkTaskCompileActivity(options.store.db, options.taskCompileWorkflowRef, state.activity.activity_ref);
|
|
209
226
|
options.publishProcedureTransition(state);
|
|
210
227
|
if (state.transition_kind !== "terminal") {
|
|
211
228
|
return;
|
|
212
229
|
}
|
|
213
230
|
if (state.activity.status !== "finished") {
|
|
231
|
+
rollbackTaskCompileContext(options.store.db, {
|
|
232
|
+
workflow_ref: options.taskCompileWorkflowRef,
|
|
233
|
+
now: options.now,
|
|
234
|
+
});
|
|
214
235
|
options.onChainTerminal?.({
|
|
215
236
|
state: "failed",
|
|
216
237
|
errorCode: "scratchpad_refresh_failed",
|
|
@@ -222,6 +243,10 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
|
|
|
222
243
|
}
|
|
223
244
|
const scratchpad = readScratchpadProjection(options.store.db);
|
|
224
245
|
if (!scratchpadHasSubmittableContent(scratchpad)) {
|
|
246
|
+
withHostStoreTransaction(options.store.db, (tx) => {
|
|
247
|
+
closeFailedTaskCompileRequest(tx, options.taskCompileWorkflowRef, options.now().toISOString(), "scratchpad_empty_after_refresh");
|
|
248
|
+
tx.prepare("DELETE FROM pending_task_compile_submissions WHERE workflow_ref = ?").run(options.taskCompileWorkflowRef);
|
|
249
|
+
});
|
|
225
250
|
options.onChainTerminal?.({
|
|
226
251
|
state: "failed",
|
|
227
252
|
errorCode: "scratchpad_empty_after_refresh",
|
|
@@ -230,6 +255,10 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
|
|
|
230
255
|
}
|
|
231
256
|
const next = startReferenceSummaryGateOrNextStage(options);
|
|
232
257
|
if (next.disposition.kind !== "accepted" && next.disposition.kind !== "already_running") {
|
|
258
|
+
rollbackTaskCompileContext(options.store.db, {
|
|
259
|
+
workflow_ref: options.taskCompileWorkflowRef,
|
|
260
|
+
now: options.now,
|
|
261
|
+
});
|
|
233
262
|
options.onChainTerminal?.({
|
|
234
263
|
state: "failed",
|
|
235
264
|
errorCode: "task_compile_stage_unavailable",
|
|
@@ -413,6 +442,10 @@ function startReferenceSummaryGateOrNextStage(options) {
|
|
|
413
442
|
});
|
|
414
443
|
registerReferenceSummaryGateWaiter(options, activeReferenceSummary.activity_ref);
|
|
415
444
|
return {
|
|
445
|
+
result: {
|
|
446
|
+
submission_ref: options.taskCompileWorkflowRef,
|
|
447
|
+
activity_ref: activeReferenceSummary.activity_ref,
|
|
448
|
+
},
|
|
416
449
|
disposition: {
|
|
417
450
|
kind: "already_running",
|
|
418
451
|
activity_ref: activeReferenceSummary.activity_ref,
|
|
@@ -514,6 +547,10 @@ function startProjectContextBootstrapActivity(options) {
|
|
|
514
547
|
return;
|
|
515
548
|
}
|
|
516
549
|
if (state.activity.status !== "finished") {
|
|
550
|
+
rollbackTaskCompileContext(options.store.db, {
|
|
551
|
+
workflow_ref: options.taskCompileWorkflowRef,
|
|
552
|
+
now: options.now,
|
|
553
|
+
});
|
|
517
554
|
options.onChainTerminal?.({ state: "failed", errorCode: "project_context_failed" });
|
|
518
555
|
return;
|
|
519
556
|
}
|
|
@@ -697,6 +734,7 @@ function readOrCaptureTaskCompileContext(input) {
|
|
|
697
734
|
captured_by_activity_ref: input.activityRef,
|
|
698
735
|
now: input.now,
|
|
699
736
|
});
|
|
737
|
+
linkTaskCompileActivity(tx, input.workflowRef, input.activityRef);
|
|
700
738
|
enqueueControlPlaneIntent(tx, {
|
|
701
739
|
kind: "task_compile_start",
|
|
702
740
|
ownerKind: "task_compile",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
declare const aliases: {
|
|
2
|
+
readonly provider_not_configured: "provider_not_configured";
|
|
3
|
+
readonly provider_auth_invalid: "provider_auth_invalid";
|
|
4
|
+
readonly provider_quota_or_billing_required: "provider_quota_or_billing_required";
|
|
5
|
+
readonly provider_rate_limited: "provider_rate_limited";
|
|
6
|
+
readonly provider_model_unavailable: "provider_model_unavailable";
|
|
7
|
+
readonly provider_network_error: "provider_network_error";
|
|
8
|
+
readonly auth_failed: "provider_auth_invalid";
|
|
9
|
+
readonly host_delegation_revoked: "provider_auth_invalid";
|
|
10
|
+
readonly authority_invalid: "provider_auth_invalid";
|
|
11
|
+
readonly gateway_rate_limited: "provider_rate_limited";
|
|
12
|
+
readonly control_queue_full: "provider_rate_limited";
|
|
13
|
+
readonly turn_timeout: "provider_timeout";
|
|
14
|
+
readonly output_parse_failed: "provider_output_invalid";
|
|
15
|
+
readonly output_validation_failed: "provider_output_invalid";
|
|
16
|
+
readonly process_lifecycle_failed: "provider_lifecycle_failed";
|
|
17
|
+
readonly process_tree_not_terminated: "provider_lifecycle_failed";
|
|
18
|
+
};
|
|
19
|
+
export type ProviderFailureCode = (typeof aliases)[keyof typeof aliases];
|
|
20
|
+
/** Facts only, never a retry policy. Do not parse provider prose or raw stderr. */
|
|
21
|
+
export declare function providerFailureCode(error: unknown): ProviderFailureCode | undefined;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=failure-classification.d.ts.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const aliases = {
|
|
2
|
+
provider_not_configured: "provider_not_configured",
|
|
3
|
+
provider_auth_invalid: "provider_auth_invalid",
|
|
4
|
+
provider_quota_or_billing_required: "provider_quota_or_billing_required",
|
|
5
|
+
provider_rate_limited: "provider_rate_limited",
|
|
6
|
+
provider_model_unavailable: "provider_model_unavailable",
|
|
7
|
+
provider_network_error: "provider_network_error",
|
|
8
|
+
auth_failed: "provider_auth_invalid",
|
|
9
|
+
host_delegation_revoked: "provider_auth_invalid",
|
|
10
|
+
authority_invalid: "provider_auth_invalid",
|
|
11
|
+
gateway_rate_limited: "provider_rate_limited",
|
|
12
|
+
control_queue_full: "provider_rate_limited",
|
|
13
|
+
turn_timeout: "provider_timeout",
|
|
14
|
+
output_parse_failed: "provider_output_invalid",
|
|
15
|
+
output_validation_failed: "provider_output_invalid",
|
|
16
|
+
process_lifecycle_failed: "provider_lifecycle_failed",
|
|
17
|
+
process_tree_not_terminated: "provider_lifecycle_failed",
|
|
18
|
+
};
|
|
19
|
+
/** Facts only, never a retry policy. Do not parse provider prose or raw stderr. */
|
|
20
|
+
export function providerFailureCode(error) {
|
|
21
|
+
if (typeof error !== "object" || error === null)
|
|
22
|
+
return undefined;
|
|
23
|
+
const value = error;
|
|
24
|
+
const code = typeof value.code === "string" ? value.code : value.reason;
|
|
25
|
+
if (typeof code === "string" && Object.hasOwn(aliases, code))
|
|
26
|
+
return aliases[code];
|
|
27
|
+
if (code !== "turn_failed" || typeof value.details !== "object" || value.details === null)
|
|
28
|
+
return undefined;
|
|
29
|
+
const details = value.details;
|
|
30
|
+
// Quota can also carry HTTP 429; the specific bounded code takes precedence.
|
|
31
|
+
if (details.turn_error_code === "insufficient_quota")
|
|
32
|
+
return "provider_quota_or_billing_required";
|
|
33
|
+
if (details.turn_error_code === "invalid_api_key")
|
|
34
|
+
return "provider_auth_invalid";
|
|
35
|
+
if (details.turn_error_code === "model_not_found")
|
|
36
|
+
return "provider_model_unavailable";
|
|
37
|
+
if (details.turn_error_code === "rate_limit_exceeded" || details.turn_error_http_status === 429)
|
|
38
|
+
return "provider_rate_limited";
|
|
39
|
+
if (details.turn_error_http_status === 401)
|
|
40
|
+
return "provider_auth_invalid";
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=failure-classification.js.map
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { SqliteDatabase } from "../store/index.js";
|
|
2
2
|
import type { ReferenceCopy } from "./cache.js";
|
|
3
3
|
import type { ReferenceInvocation } from "./service.js";
|
|
4
|
+
import type { ReferenceAuthorization } from "@tutti/shared/schemas/api";
|
|
5
|
+
export declare function recordExternalReferenceAccessRequirement(db: SqliteDatabase, invocation: ReferenceInvocation, requestRef: string, authorization: ReferenceAuthorization, now?: () => Date): void;
|
|
4
6
|
/** Prior answer context only: a source reference is not proof of freshness in a later turn. */
|
|
5
7
|
export declare function readExternalReferenceReadSources(db: SqliteDatabase, invocationRef: string): string[];
|
|
6
8
|
/** Durable observation, separate from evictable cache metadata; no content or locator. */
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export function recordExternalReferenceAccessRequirement(db, invocation, requestRef, authorization, now = () => new Date()) {
|
|
2
|
+
db.prepare(`INSERT OR IGNORE INTO external_reference_access_requirements
|
|
3
|
+
(invocation_ref, request_ref, activity_ref, authorization_ref, profile, observed_at)
|
|
4
|
+
VALUES (?, ?, ?, ?, ?, ?)`).run(invocation.reference_invocation_ref, requestRef, invocation.activity_ref ?? null, authorization.authorization_ref, authorization.profile, now().toISOString());
|
|
5
|
+
}
|
|
1
6
|
/** Prior answer context only: a source reference is not proof of freshness in a later turn. */
|
|
2
7
|
export function readExternalReferenceReadSources(db, invocationRef) {
|
|
3
8
|
return db
|
|
@@ -8,6 +8,8 @@ export type ReferenceInvocation = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare class ReferenceAuthorizationRequiredError extends Error {
|
|
10
10
|
readonly result: ReferenceAuthorizationRequired;
|
|
11
|
+
readonly code = "reference_authorization_required";
|
|
12
|
+
readonly continuation = "explicit_request";
|
|
11
13
|
constructor(result: ReferenceAuthorizationRequired);
|
|
12
14
|
}
|
|
13
15
|
export declare class ExternalReferenceAccess {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ReferenceCacheError } from "./cache.js";
|
|
2
2
|
export class ReferenceAuthorizationRequiredError extends Error {
|
|
3
3
|
result;
|
|
4
|
+
code = "reference_authorization_required";
|
|
5
|
+
continuation = "explicit_request";
|
|
4
6
|
constructor(result) {
|
|
5
7
|
super("reference_authorization_required");
|
|
6
8
|
this.result = result;
|
package/dist/runtime-failures.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { providerFailureCode } from "./providers/failure-classification.js";
|
|
1
2
|
const STABLE_FAILURE_CODE = /^[a-z][a-z0-9_]{0,63}$/u;
|
|
2
3
|
export function stableRuntimeFailureCode(error, fallback) {
|
|
3
4
|
if (typeof error === "object" &&
|
|
@@ -17,6 +18,19 @@ export function safeRuntimeFailureSummary(subject, error, fallbackCode) {
|
|
|
17
18
|
if (code === "admission_unavailable" || code === "admission_closed") {
|
|
18
19
|
return `${subject} stopped because provider execution is unavailable while the Host is closing. Retry after the Host is ready. (${code})`;
|
|
19
20
|
}
|
|
20
|
-
|
|
21
|
+
if (code === "reference_authorization_required") {
|
|
22
|
+
return `${subject} needs document access. Authorize using the group card, then review the task or request before continuing. (${code})`;
|
|
23
|
+
}
|
|
24
|
+
const providerCode = providerFailureCode(error);
|
|
25
|
+
if (providerCode === "provider_auth_invalid" ||
|
|
26
|
+
providerCode === "provider_quota_or_billing_required" ||
|
|
27
|
+
providerCode === "provider_model_unavailable" ||
|
|
28
|
+
providerCode === "provider_not_configured") {
|
|
29
|
+
return `${subject} could not use model access. Ask the project owner to check the provider or account, then review available recovery actions. (${providerCode})`;
|
|
30
|
+
}
|
|
31
|
+
if (providerCode === "provider_rate_limited") {
|
|
32
|
+
return `${subject} stopped because the model service is busy. Wait, then review available recovery actions. (${providerCode})`;
|
|
33
|
+
}
|
|
34
|
+
return `${subject} could not complete. Review its status and available recovery actions before continuing. (${code})`;
|
|
21
35
|
}
|
|
22
36
|
//# sourceMappingURL=runtime-failures.js.map
|
|
@@ -4,7 +4,7 @@ import { requestExternalReferences } from "@tutti/relay-client";
|
|
|
4
4
|
import { isExternalReferenceList, isExternalReferenceReadResult, isExternalReferenceDescriptor, isReferenceAuthorizationRequired, } from "@tutti/shared/schemas/internal";
|
|
5
5
|
import { ExternalReferenceCache } from "../../reference-access/cache.js";
|
|
6
6
|
import { ExternalReferenceAccess, ReferenceAuthorizationRequiredError, } from "../../reference-access/service.js";
|
|
7
|
-
import { recordExternalReferenceRead } from "../../reference-access/read-evidence.js";
|
|
7
|
+
import { recordExternalReferenceRead, recordExternalReferenceAccessRequirement, } from "../../reference-access/read-evidence.js";
|
|
8
8
|
import { downloadStagedUploadWithResolver, cleanupDownloadedStagedUpload, } from "../http/routes/project-api/staged-uploads.js";
|
|
9
9
|
import { readHostRegistrationSecret } from "./machine-local.js";
|
|
10
10
|
export function createExternalReferenceRuntime(options) {
|
|
@@ -14,6 +14,7 @@ export function createExternalReferenceRuntime(options) {
|
|
|
14
14
|
if (!relay)
|
|
15
15
|
throw new Error("reference_host_offline");
|
|
16
16
|
const secret = readHostRegistrationSecret(preparation.tutti_home, preparation.project_id);
|
|
17
|
+
const requestRef = `eref_${randomUUID().replaceAll("-", "")}`;
|
|
17
18
|
const result = await requestExternalReferences({
|
|
18
19
|
relayUrl: preparation.relay_url,
|
|
19
20
|
body: {
|
|
@@ -22,15 +23,17 @@ export function createExternalReferenceRuntime(options) {
|
|
|
22
23
|
host_auth: { kind: "host_registration_secret_v1", secret: secret.secret },
|
|
23
24
|
relay_project_ref: relay.relay_project_ref,
|
|
24
25
|
host_connection_ref: relay.host_connection_ref,
|
|
25
|
-
request_ref:
|
|
26
|
+
request_ref: requestRef,
|
|
26
27
|
invocation_ref: invocation.reference_invocation_ref,
|
|
27
28
|
action,
|
|
28
29
|
...(sourceRef === undefined ? {} : { source_ref: sourceRef }),
|
|
29
30
|
...(documentUrl === undefined ? {} : { document_url: documentUrl }),
|
|
30
31
|
},
|
|
31
32
|
});
|
|
32
|
-
if (isReferenceAuthorizationRequired(result))
|
|
33
|
+
if (isReferenceAuthorizationRequired(result)) {
|
|
34
|
+
recordExternalReferenceAccessRequirement(options.store.db, invocation, requestRef, result.authorization);
|
|
33
35
|
throw new ReferenceAuthorizationRequiredError(result);
|
|
36
|
+
}
|
|
34
37
|
return result;
|
|
35
38
|
}
|
|
36
39
|
return new ExternalReferenceAccess({
|