deepline 0.3.392 → 0.3.394
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/bundling-sources/sdk/src/client.ts +156 -24
- package/dist/bundling-sources/sdk/src/compat.ts +9 -0
- package/dist/bundling-sources/sdk/src/http.ts +5 -1
- package/dist/bundling-sources/sdk/src/index.ts +5 -0
- package/dist/bundling-sources/sdk/src/plays/run-request.ts +182 -6
- package/dist/bundling-sources/sdk/src/release.ts +4 -1
- package/dist/bundling-sources/sdk/src/types.ts +13 -0
- package/dist/bundling-sources/shared_libs/integration-contracts/request-warnings.ts +64 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/runtime-sheet-storage-contract.ts +315 -0
- package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +41 -8
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +895 -54
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +41 -4
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-policy.ts +28 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-invocation-fence.ts +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/freshness-receipt-execution.ts +251 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +11 -3
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +30 -1
- package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +168 -10
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +252 -10
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +92 -58
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +7 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +30 -7
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1161 -316
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +28 -15
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +24 -3
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +32 -0
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +103 -23
- package/dist/bundling-sources/shared_libs/plays/core.ts +1 -1
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +172 -79
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +173 -1
- package/dist/bundling-sources/shared_libs/plays/tool-result-types.ts +18 -0
- package/dist/cli/auth-IHKISLJQ.js +39 -0
- package/dist/cli/{auth-6JSA2ZCW.mjs → auth-OSXDAVCF.mjs} +4 -4
- package/dist/cli/{billing-Q5BLQHIU.js → billing-EKLYOXUY.js} +101 -74
- package/dist/cli/{billing-MRRO643H.mjs → billing-ZNNKX3UR.mjs} +32 -5
- package/dist/cli/{chunk-4SFPVW7A.js → chunk-2MDDA7A4.js} +26 -26
- package/dist/cli/{chunk-JOFPQGXW.js → chunk-354JCXA2.js} +50 -50
- package/dist/cli/{chunk-7E66A24V.mjs → chunk-3HU47EGZ.mjs} +71 -9
- package/dist/cli/{chunk-DSWSEHRX.mjs → chunk-3HWALOL2.mjs} +1 -1
- package/dist/cli/{chunk-T7OKJ2T6.js → chunk-4P27AXEA.js} +73 -63
- package/dist/cli/{chunk-24VEN5PQ.mjs → chunk-5BB2RGAX.mjs} +190 -18
- package/dist/cli/{chunk-JSLPHFP7.js → chunk-6YAUMG27.js} +3 -2
- package/dist/cli/{chunk-MU6M3URX.mjs → chunk-BP33SIZF.mjs} +1 -1
- package/dist/cli/{chunk-WMS5U6YB.js → chunk-CS6RAIVY.js} +20 -19
- package/dist/cli/{chunk-CVLM3WMJ.js → chunk-E23BQ5SC.js} +242 -183
- package/dist/cli/{chunk-2GAHHSLB.mjs → chunk-FJGW5GXU.mjs} +3 -2
- package/dist/cli/{chunk-YN6YA7ZZ.mjs → chunk-JHIJIHFB.mjs} +79 -20
- package/dist/cli/{chunk-Q6EOHNRH.mjs → chunk-JI2B4RZP.mjs} +119 -6
- package/dist/cli/{chunk-5O4B6AUN.js → chunk-KB4SDX5S.js} +71 -9
- package/dist/cli/{chunk-RV6ALZIX.js → chunk-KGNTKMRG.js} +32 -7
- package/dist/cli/{chunk-6DBOU7UH.js → chunk-KI635JN4.js} +176 -87
- package/dist/cli/{chunk-2FQG2HU4.js → chunk-KIUMEOM5.js} +8 -8
- package/dist/cli/{chunk-Y6VOGHF2.mjs → chunk-KZZC33PY.mjs} +0 -181
- package/dist/cli/{chunk-2Q3GEFL4.mjs → chunk-M57J2SK4.mjs} +2 -2
- package/dist/cli/{chunk-R3O2CZ4H.mjs → chunk-MFB2VKBO.mjs} +2 -2
- package/dist/cli/{chunk-QGKPTBRR.js → chunk-MH73HMPV.js} +3 -3
- package/dist/cli/{chunk-BB3FNYFY.js → chunk-MLPU5SMZ.js} +2 -2
- package/dist/cli/{chunk-2G7DXFHI.mjs → chunk-NR3DFKNF.mjs} +27 -2
- package/dist/cli/{chunk-EY7RFC6A.js → chunk-QIQBYKLB.js} +15 -15
- package/dist/cli/{chunk-RIV2GS5E.js → chunk-RBJYWRCF.js} +638 -466
- package/dist/cli/{chunk-3VN4FTE3.js → chunk-RDXFGPJU.js} +1 -182
- package/dist/cli/{chunk-YAZCDBXX.mjs → chunk-TJI2EN6K.mjs} +3 -2
- package/dist/cli/chunk-UBKA33KY.mjs +310 -0
- package/dist/cli/{chunk-PAIMOGCM.js → chunk-UHNDVD3P.js} +125 -12
- package/dist/cli/{chunk-4TCT77UF.js → chunk-URWF7NGQ.js} +6 -1
- package/dist/cli/{chunk-2QLML6G4.mjs → chunk-UXFSWG3Q.mjs} +4 -4
- package/dist/cli/{chunk-UCKJQG25.js → chunk-VK6FYZLP.js} +8 -8
- package/dist/cli/{chunk-ZTACINO5.mjs → chunk-X63FDZJZ.mjs} +13 -3
- package/dist/cli/chunk-XDVT3GOM.js +310 -0
- package/dist/cli/{chunk-A3APGZFP.mjs → chunk-XRQ2AQL7.mjs} +2 -2
- package/dist/cli/{chunk-3FV4GBRK.mjs → chunk-XXUDVEXK.mjs} +97 -8
- package/dist/cli/{chunk-PQSA443V.mjs → chunk-ZI2ZNDJK.mjs} +6 -1
- package/dist/cli/{chunk-HGYBEAQP.mjs → chunk-ZYBGA4AR.mjs} +3 -3
- package/dist/cli/command-docs.js +2 -2
- package/dist/cli/command-docs.mjs +1 -1
- package/dist/cli/{csv-E4QGUIGI.mjs → csv-5AXRB35H.mjs} +3 -3
- package/dist/cli/{csv-26U5T24Z.js → csv-LU7O75WE.js} +12 -12
- package/dist/cli/{db-JTRJIAQW.mjs → db-7XP5UWEW.mjs} +6 -5
- package/dist/cli/{db-QHBFLMVL.js → db-F7YTFKVM.js} +31 -30
- package/dist/cli/{describe-FDV2V74F.js → describe-FRH32RDV.js} +24 -23
- package/dist/cli/{describe-E3DE3ZK3.mjs → describe-K4ES5LIL.mjs} +17 -16
- package/dist/cli/{executions-B3GTNPRE.mjs → executions-IEHEHJUO.mjs} +6 -5
- package/dist/cli/{executions-VZYOGVQY.js → executions-RZV5XPT2.js} +8 -7
- package/dist/cli/{feedback-CR55FJA2.js → feedback-3P65QTX3.js} +7 -7
- package/dist/cli/{feedback-4CEBR3OV.mjs → feedback-FPUVZBAN.mjs} +3 -3
- package/dist/cli/{git-NM2PHHZ4.mjs → git-GLVVP7TY.mjs} +4 -4
- package/dist/cli/git-MFFAWVVX.js +23 -0
- package/dist/cli/index.js +100 -96
- package/dist/cli/index.mjs +47 -43
- package/dist/cli/{legacy-noops-A7MSW5ND.mjs → legacy-noops-3MOI6NBM.mjs} +4 -4
- package/dist/cli/{legacy-noops-ZUA4YFIH.js → legacy-noops-XKSGZR7Q.js} +7 -7
- package/dist/cli/{monitors-NVPRQYP3.mjs → monitors-FTBGNEFG.mjs} +6 -5
- package/dist/cli/{monitors-ZE3TLZ4H.js → monitors-NZ2DYNST.js} +87 -86
- package/dist/cli/{org-U7A6DV5D.js → org-MCKDQXIV.js} +66 -65
- package/dist/cli/{org-KUBILNQV.mjs → org-V2J5RBCN.mjs} +6 -5
- package/dist/cli/{play-HRDXQ4QE.mjs → play-AJFGME3B.mjs} +11 -10
- package/dist/cli/play-WLSN2EO2.js +158 -0
- package/dist/cli/{providers-VD6YTVQZ.js → providers-2JEZ2264.js} +9 -8
- package/dist/cli/{providers-OHIWI453.mjs → providers-I6RQJZFZ.mjs} +6 -5
- package/dist/cli/{quickstart-UBV2FONF.mjs → quickstart-BIDQDMO4.mjs} +3 -3
- package/dist/cli/{quickstart-ETTCWPA4.js → quickstart-GJ2RPDU3.js} +5 -5
- package/dist/cli/run-identity-G257GQED.js +25 -0
- package/dist/cli/run-identity-MRNCEGV2.mjs +25 -0
- package/dist/cli/search-5VN4YD4I.js +27 -0
- package/dist/cli/{search-F44IZRT7.mjs → search-ELRXFWG5.mjs} +7 -6
- package/dist/cli/{secrets-CAEMRORF.js → secrets-53IT6VXD.js} +15 -14
- package/dist/cli/{secrets-R6XOJZKU.mjs → secrets-SQXGZXIC.mjs} +6 -5
- package/dist/cli/{sessions-RLN6UQS3.mjs → sessions-2X5QF7SL.mjs} +4 -4
- package/dist/cli/{sessions-6GC7OFTT.js → sessions-CAV5M77Y.js} +5 -5
- package/dist/cli/{settings-DPNVUAV2.js → settings-532WNAZO.js} +76 -75
- package/dist/cli/{settings-KIPAMRPP.mjs → settings-7P67VGCJ.mjs} +9 -8
- package/dist/cli/{setup-DTL3L5SG.js → setup-6L2VSUWJ.js} +41 -41
- package/dist/cli/{setup-TU2F7PU5.mjs → setup-QGGBV2LE.mjs} +6 -6
- package/dist/cli/{skills-UV5MYJ46.mjs → skills-3FE62WBG.mjs} +4 -4
- package/dist/cli/skills-S6NPZAN7.js +28 -0
- package/dist/cli/tools-647RLXQS.js +41 -0
- package/dist/cli/{tools-QRRNDCQ7.mjs → tools-DMR25DEF.mjs} +10 -9
- package/dist/cli/{update-4426TF35.mjs → update-6RZMHJZP.mjs} +6 -6
- package/dist/cli/update-WVTSC3EM.js +28 -0
- package/dist/cli/{workflow-N4U5VLZ3.mjs → workflow-L2DQRE6R.mjs} +7 -6
- package/dist/cli/{workflow-HNLOVISB.js → workflow-TX4F2PIX.js} +17 -16
- package/dist/{client-e6Qh5ZIg.d.mts → client-BktIPdIj.d.mts} +153 -4
- package/dist/{client-Uxuy-xQ2.d.ts → client-C3O1Ipf3.d.ts} +153 -4
- package/dist/{compiler-manifest-CY2v8ab0.d.ts → compiler-manifest-DLxUY5kI.d.mts} +124 -10
- package/dist/{compiler-manifest-CY2v8ab0.d.mts → compiler-manifest-DLxUY5kI.d.ts} +124 -10
- package/dist/{index-BZ6uzZJl.d.ts → index-B_GM9E3q.d.ts} +1 -1
- package/dist/{index-B9fBZH1g.d.mts → index-SCb0NvDb.d.mts} +1 -1
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +120 -29
- package/dist/index.mjs +111 -20
- package/dist/install-integrity.json +94 -88
- package/dist/plays/bundle-play-file.d.mts +3 -3
- package/dist/plays/bundle-play-file.d.ts +3 -3
- package/dist/plays/bundle-play-file.mjs +2 -2
- package/dist/plays/run-request.d.mts +2 -2
- package/dist/plays/run-request.d.ts +2 -2
- package/dist/plays/run-request.js +5 -2
- package/dist/plays/run-request.mjs +4 -1
- package/dist/release.d.mts +4 -1
- package/dist/release.d.ts +4 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
- package/dist/cli/auth-67TAELQL.js +0 -39
- package/dist/cli/git-I5JTK6YU.js +0 -23
- package/dist/cli/play-A2V7YDU7.js +0 -157
- package/dist/cli/search-WWXMBVPH.js +0 -26
- package/dist/cli/skills-SWOSEZLB.js +0 -28
- package/dist/cli/tools-IL2YCDQ6.js +0 -40
- package/dist/cli/update-BERDPEYR.js +0 -28
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Declarative request predicates authored by the server, with no provider knowledge in clients. */
|
|
2
|
+
export type ToolRequestCondition =
|
|
3
|
+
| {
|
|
4
|
+
field: string;
|
|
5
|
+
operator: 'equals';
|
|
6
|
+
value: string | number | boolean | null;
|
|
7
|
+
}
|
|
8
|
+
| { field: string; operator: 'number_equals'; value: number }
|
|
9
|
+
| { field: string; operator: 'present_nonblank' }
|
|
10
|
+
| { all: readonly ToolRequestCondition[] }
|
|
11
|
+
| { any: readonly ToolRequestCondition[] };
|
|
12
|
+
|
|
13
|
+
export type ToolRequestWarningRule = {
|
|
14
|
+
code: string;
|
|
15
|
+
/** Customer-safe Deepline billing guidance; never upstream spend. */
|
|
16
|
+
message: string;
|
|
17
|
+
when: ToolRequestCondition;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function matchesToolRequestCondition(
|
|
21
|
+
condition: ToolRequestCondition,
|
|
22
|
+
payload: Record<string, unknown> | null | undefined,
|
|
23
|
+
): boolean {
|
|
24
|
+
if ('all' in condition) {
|
|
25
|
+
return condition.all.every((entry) =>
|
|
26
|
+
matchesToolRequestCondition(entry, payload),
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
if ('any' in condition) {
|
|
30
|
+
return condition.any.some((entry) =>
|
|
31
|
+
matchesToolRequestCondition(entry, payload),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const value = payload?.[condition.field];
|
|
35
|
+
switch (condition.operator) {
|
|
36
|
+
case 'equals':
|
|
37
|
+
return value === condition.value;
|
|
38
|
+
case 'number_equals':
|
|
39
|
+
try {
|
|
40
|
+
return Number(value) === condition.value;
|
|
41
|
+
} catch {
|
|
42
|
+
// Metadata warnings run before server input validation. A malformed
|
|
43
|
+
// JSON object must still reach that structured validation boundary.
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
case 'present_nonblank':
|
|
47
|
+
// A present non-string value is significant, including an empty array.
|
|
48
|
+
// Provider policies choose this predicate explicitly when that is their
|
|
49
|
+
// existing request contract; clients must not reinterpret it as truthy.
|
|
50
|
+
return typeof value === 'string'
|
|
51
|
+
? value.trim().length > 0
|
|
52
|
+
: value !== undefined && value !== null;
|
|
53
|
+
default:
|
|
54
|
+
throw new Error('Unsupported tool request warning condition.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Ordered first-match semantics preserve provider precedence and emit at most one warning. */
|
|
59
|
+
export function firstMatchingToolRequestWarning(
|
|
60
|
+
rules: readonly ToolRequestWarningRule[] | null | undefined,
|
|
61
|
+
payload: Record<string, unknown> | null | undefined,
|
|
62
|
+
): ToolRequestWarningRule | undefined {
|
|
63
|
+
return rules?.find((rule) => matchesToolRequestCondition(rule.when, payload));
|
|
64
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Compatibility seam for callers that used the cell-meta-only contract. */
|
|
2
2
|
import { RUNTIME_SHEET_CELL_META_COLUMN } from './runtime-sheet-orm';
|
|
3
|
+
import type { RuntimeSheetOrmQuery } from './runtime-sheet-orm';
|
|
3
4
|
|
|
4
5
|
export { RUNTIME_SHEET_CELL_META_COLUMN } from './runtime-sheet-orm';
|
|
5
6
|
|
|
@@ -45,3 +46,317 @@ export function isRuntimeSheetCellMetaColumnReady(
|
|
|
45
46
|
state.column_default != null
|
|
46
47
|
);
|
|
47
48
|
}
|
|
49
|
+
|
|
50
|
+
/** Expand-only immutable history. Writers enable after tenant migration proof. */
|
|
51
|
+
export const RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE =
|
|
52
|
+
'_deepline_run_row_memberships';
|
|
53
|
+
export const RUNTIME_SHEET_RUN_MEMBERSHIP_SCHEMA_VERSION = 9;
|
|
54
|
+
/** Reader/expand release is inert; enable in its independently reviewed PR. */
|
|
55
|
+
export const RUNTIME_SHEET_RUN_MEMBERSHIP_WRITES_ENABLED = false;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Optional history is available only after all expansion DDL and grants land.
|
|
59
|
+
* Inspect its own bounded capability; never consult the admission schema floor.
|
|
60
|
+
*/
|
|
61
|
+
export async function isRuntimeSheetRunMembershipStorageAvailable(
|
|
62
|
+
query: RuntimeSheetOrmQuery,
|
|
63
|
+
schema = 'storage',
|
|
64
|
+
requireCaptureAccess = false,
|
|
65
|
+
): Promise<boolean> {
|
|
66
|
+
const result = await query<{ available: boolean }>(
|
|
67
|
+
`WITH relations AS (
|
|
68
|
+
SELECT c.oid, c.relname, c.relkind, n.oid AS schema_oid
|
|
69
|
+
FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
|
|
70
|
+
WHERE n.nspname = $1 AND c.relname IN ('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', '_deepline_run_datasets')
|
|
71
|
+
), required_columns (relation, name, type, required) AS (
|
|
72
|
+
VALUES
|
|
73
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'run_id', 'text', true),
|
|
74
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'dataset_id', 'text', true),
|
|
75
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'row_key', 'text', true),
|
|
76
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'phase', 'text', true),
|
|
77
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'generation', 'text', true),
|
|
78
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'attempt_seq', 'integer', false),
|
|
79
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'row_version', 'bigint', false),
|
|
80
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'input_index', 'bigint', false),
|
|
81
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'input_digest', 'text', true),
|
|
82
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'status', 'text', true),
|
|
83
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'row_data', 'jsonb', true),
|
|
84
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'source_ref', 'jsonb', true),
|
|
85
|
+
('${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}', 'recorded_at', 'timestamp with time zone', true),
|
|
86
|
+
('_deepline_run_datasets', 'membership_expected_rows', 'bigint', false),
|
|
87
|
+
('_deepline_run_datasets', 'membership_started_at', 'timestamp with time zone', false),
|
|
88
|
+
('_deepline_run_datasets', 'membership_completed_at', 'timestamp with time zone', false)
|
|
89
|
+
)
|
|
90
|
+
SELECT coalesce(
|
|
91
|
+
(SELECT count(*) = 2 FROM relations WHERE relkind = 'r')
|
|
92
|
+
AND NOT EXISTS (
|
|
93
|
+
SELECT 1 FROM required_columns required
|
|
94
|
+
LEFT JOIN relations relation ON relation.relname = required.relation
|
|
95
|
+
LEFT JOIN pg_attribute attribute ON attribute.attrelid = relation.oid
|
|
96
|
+
AND attribute.attname = required.name AND NOT attribute.attisdropped
|
|
97
|
+
WHERE attribute.attnum IS NULL OR attribute.attnum <= 0
|
|
98
|
+
OR format_type(attribute.atttypid, attribute.atttypmod) <> required.type
|
|
99
|
+
OR attribute.attnotnull <> required.required
|
|
100
|
+
)
|
|
101
|
+
AND EXISTS (
|
|
102
|
+
SELECT 1 FROM relations relation JOIN pg_attribute attribute ON attribute.attrelid = relation.oid
|
|
103
|
+
JOIN pg_attrdef column_default ON column_default.adrelid = relation.oid AND column_default.adnum = attribute.attnum
|
|
104
|
+
WHERE relation.relname = '${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}' AND attribute.attname = 'recorded_at' AND NOT attribute.attisdropped
|
|
105
|
+
)
|
|
106
|
+
AND EXISTS (
|
|
107
|
+
SELECT 1 FROM relations relation JOIN pg_constraint constraint_row ON constraint_row.conrelid = relation.oid
|
|
108
|
+
WHERE relation.relname = '${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}' AND constraint_row.contype = 'p'
|
|
109
|
+
AND (SELECT array_agg(attribute.attname::text ORDER BY keys.ordinality)
|
|
110
|
+
FROM unnest(constraint_row.conkey) WITH ORDINALITY keys(attnum, ordinality)
|
|
111
|
+
JOIN pg_attribute attribute ON attribute.attrelid = relation.oid AND attribute.attnum = keys.attnum)
|
|
112
|
+
= ARRAY['run_id', 'dataset_id', 'row_key', 'phase', 'generation']::text[]
|
|
113
|
+
)
|
|
114
|
+
AND NOT EXISTS (
|
|
115
|
+
SELECT 1 FROM relations relation
|
|
116
|
+
WHERE NOT has_schema_privilege(relation.schema_oid, 'USAGE')
|
|
117
|
+
OR NOT has_table_privilege(relation.oid, 'SELECT')
|
|
118
|
+
OR ($2::boolean AND relation.relname = '${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}' AND NOT has_table_privilege(relation.oid, 'INSERT'))
|
|
119
|
+
OR ($2::boolean AND relation.relname = '${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}' AND NOT has_table_privilege(relation.oid, 'UPDATE'))
|
|
120
|
+
OR ($2::boolean AND relation.relname = '_deepline_run_datasets' AND NOT has_table_privilege(relation.oid, 'UPDATE'))
|
|
121
|
+
), false) AS available`,
|
|
122
|
+
[schema, requireCaptureAccess],
|
|
123
|
+
);
|
|
124
|
+
return result.rows[0]?.available === true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export type RuntimeSheetRunRowMembership = {
|
|
128
|
+
runId: string;
|
|
129
|
+
datasetId: string;
|
|
130
|
+
rowKey: string;
|
|
131
|
+
phase: 'admitted' | 'terminal';
|
|
132
|
+
/** Durable terminal row version distinguishes repair within an attempt. */
|
|
133
|
+
generation: string;
|
|
134
|
+
attemptSeq: number | null;
|
|
135
|
+
rowVersion: number | null;
|
|
136
|
+
inputIndex: number | null;
|
|
137
|
+
inputDigest: string;
|
|
138
|
+
status: 'pending' | 'enriched' | 'failed' | 'cancelled';
|
|
139
|
+
data: Record<string, unknown>;
|
|
140
|
+
source: Record<string, unknown>;
|
|
141
|
+
recordedAt: string;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export type RuntimeSheetRunRowMembershipPage = {
|
|
145
|
+
entries: RuntimeSheetRunRowMembership[];
|
|
146
|
+
nextCursor: string | null;
|
|
147
|
+
coverage: {
|
|
148
|
+
complete: boolean;
|
|
149
|
+
expectedRows: number | null;
|
|
150
|
+
captureStartedAt: string | null;
|
|
151
|
+
captureCompletedAt: string | null;
|
|
152
|
+
reason:
|
|
153
|
+
| 'storage_not_expanded'
|
|
154
|
+
| 'legacy_or_capture_not_enabled'
|
|
155
|
+
| 'capture_in_progress'
|
|
156
|
+
| 'complete';
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/** The existing sheet/history erasure owner clears snapshots atomically too. */
|
|
161
|
+
export async function clearRuntimeSheetRunRowMembership(
|
|
162
|
+
query: RuntimeSheetOrmQuery,
|
|
163
|
+
input: { playName: string; tableNamespace: string },
|
|
164
|
+
): Promise<void> {
|
|
165
|
+
const relation = await query<{ present: boolean }>(
|
|
166
|
+
'SELECT to_regclass($1) IS NOT NULL AS present',
|
|
167
|
+
[`storage.${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}`],
|
|
168
|
+
);
|
|
169
|
+
if (relation.rows[0]?.present !== true) return;
|
|
170
|
+
await query(
|
|
171
|
+
`DELETE FROM storage.${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE} history
|
|
172
|
+
USING storage._deepline_run_datasets catalog
|
|
173
|
+
WHERE history.run_id = catalog.run_id AND history.dataset_id = catalog.dataset_id
|
|
174
|
+
AND catalog.play_name = $1 AND catalog.table_namespace = $2`,
|
|
175
|
+
[input.playName, input.tableNamespace],
|
|
176
|
+
);
|
|
177
|
+
await query(
|
|
178
|
+
`UPDATE storage._deepline_run_datasets
|
|
179
|
+
SET membership_expected_rows = NULL, membership_started_at = NULL, membership_completed_at = NULL
|
|
180
|
+
WHERE play_name = $1 AND table_namespace = $2`,
|
|
181
|
+
[input.playName, input.tableNamespace],
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function membershipCursor(
|
|
186
|
+
value: string | undefined,
|
|
187
|
+
): { rowKey: string; phase: string; generation: string } | null {
|
|
188
|
+
if (!value) return null;
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
|
|
191
|
+
if (
|
|
192
|
+
parsed.version !== 1 ||
|
|
193
|
+
typeof parsed.rowKey !== 'string' ||
|
|
194
|
+
!['admitted', 'terminal'].includes(parsed.phase) ||
|
|
195
|
+
typeof parsed.generation !== 'string'
|
|
196
|
+
)
|
|
197
|
+
throw new Error();
|
|
198
|
+
return {
|
|
199
|
+
rowKey: parsed.rowKey,
|
|
200
|
+
phase: parsed.phase,
|
|
201
|
+
generation: parsed.generation,
|
|
202
|
+
};
|
|
203
|
+
} catch {
|
|
204
|
+
throw new Error('Invalid run membership cursor.');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Read customer rows behind a cursor, never embed them in run/status metadata. */
|
|
209
|
+
export async function readRuntimeSheetRunRowMembershipPage(
|
|
210
|
+
query: RuntimeSheetOrmQuery,
|
|
211
|
+
input: { runId: string; datasetId: string; limit?: number; cursor?: string },
|
|
212
|
+
): Promise<RuntimeSheetRunRowMembershipPage> {
|
|
213
|
+
const limit = input.limit ?? 200;
|
|
214
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 1_000)
|
|
215
|
+
throw new Error('Run membership limit must be an integer from 1 to 1000.');
|
|
216
|
+
const after = membershipCursor(input.cursor);
|
|
217
|
+
if (!(await isRuntimeSheetRunMembershipStorageAvailable(query)))
|
|
218
|
+
return {
|
|
219
|
+
entries: [],
|
|
220
|
+
nextCursor: null,
|
|
221
|
+
coverage: {
|
|
222
|
+
complete: false,
|
|
223
|
+
expectedRows: null,
|
|
224
|
+
captureStartedAt: null,
|
|
225
|
+
captureCompletedAt: null,
|
|
226
|
+
reason: 'storage_not_expanded',
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
const manifest = await query<{
|
|
230
|
+
membership_expected_rows: number | string | null;
|
|
231
|
+
membership_started_at: Date | string | null;
|
|
232
|
+
membership_completed_at: Date | string | null;
|
|
233
|
+
}>(
|
|
234
|
+
`SELECT membership_expected_rows, membership_started_at, membership_completed_at
|
|
235
|
+
FROM storage._deepline_run_datasets WHERE run_id = $1 AND dataset_id = $2`,
|
|
236
|
+
[input.runId, input.datasetId],
|
|
237
|
+
);
|
|
238
|
+
const coverage = manifest.rows[0];
|
|
239
|
+
const result = await query<{
|
|
240
|
+
run_id: string;
|
|
241
|
+
dataset_id: string;
|
|
242
|
+
row_key: string;
|
|
243
|
+
phase: 'admitted' | 'terminal';
|
|
244
|
+
generation: string;
|
|
245
|
+
attempt_seq: number | null;
|
|
246
|
+
row_version: number | string | null;
|
|
247
|
+
input_index: number | string | null;
|
|
248
|
+
input_digest: string;
|
|
249
|
+
status: RuntimeSheetRunRowMembership['status'];
|
|
250
|
+
row_data: Record<string, unknown>;
|
|
251
|
+
source_ref: Record<string, unknown>;
|
|
252
|
+
recorded_at: Date | string;
|
|
253
|
+
}>(
|
|
254
|
+
`SELECT run_id, dataset_id, row_key, phase, generation, attempt_seq, row_version, input_index, input_digest, status, row_data, source_ref, recorded_at
|
|
255
|
+
FROM storage.${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}
|
|
256
|
+
WHERE run_id = $1 AND dataset_id = $2
|
|
257
|
+
AND ($3::text IS NULL OR (row_key, phase, generation) > ($3::text, $4::text, $5::text))
|
|
258
|
+
ORDER BY row_key ASC, phase ASC, generation ASC LIMIT $6`,
|
|
259
|
+
[
|
|
260
|
+
input.runId,
|
|
261
|
+
input.datasetId,
|
|
262
|
+
after?.rowKey ?? null,
|
|
263
|
+
after?.phase ?? null,
|
|
264
|
+
after?.generation ?? null,
|
|
265
|
+
limit + 1,
|
|
266
|
+
],
|
|
267
|
+
);
|
|
268
|
+
const entries = result.rows.slice(0, limit).map((row) => ({
|
|
269
|
+
runId: row.run_id,
|
|
270
|
+
datasetId: row.dataset_id,
|
|
271
|
+
rowKey: row.row_key,
|
|
272
|
+
phase: row.phase,
|
|
273
|
+
generation: row.generation,
|
|
274
|
+
attemptSeq: row.attempt_seq === null ? null : Number(row.attempt_seq),
|
|
275
|
+
rowVersion: row.row_version === null ? null : Number(row.row_version),
|
|
276
|
+
inputIndex: row.input_index === null ? null : Number(row.input_index),
|
|
277
|
+
inputDigest: row.input_digest,
|
|
278
|
+
status: row.status,
|
|
279
|
+
data: row.row_data,
|
|
280
|
+
source: row.source_ref,
|
|
281
|
+
recordedAt: new Date(row.recorded_at).toISOString(),
|
|
282
|
+
}));
|
|
283
|
+
const last = entries.at(-1);
|
|
284
|
+
const complete =
|
|
285
|
+
coverage?.membership_started_at != null &&
|
|
286
|
+
coverage.membership_completed_at != null &&
|
|
287
|
+
coverage.membership_expected_rows != null;
|
|
288
|
+
return {
|
|
289
|
+
entries,
|
|
290
|
+
nextCursor:
|
|
291
|
+
result.rows.length > limit && last
|
|
292
|
+
? Buffer.from(
|
|
293
|
+
JSON.stringify({
|
|
294
|
+
version: 1,
|
|
295
|
+
rowKey: last.rowKey,
|
|
296
|
+
phase: last.phase,
|
|
297
|
+
generation: last.generation,
|
|
298
|
+
}),
|
|
299
|
+
).toString('base64url')
|
|
300
|
+
: null,
|
|
301
|
+
coverage: {
|
|
302
|
+
complete,
|
|
303
|
+
expectedRows:
|
|
304
|
+
coverage?.membership_expected_rows == null
|
|
305
|
+
? null
|
|
306
|
+
: Number(coverage.membership_expected_rows),
|
|
307
|
+
captureStartedAt: coverage?.membership_started_at
|
|
308
|
+
? new Date(coverage.membership_started_at).toISOString()
|
|
309
|
+
: null,
|
|
310
|
+
captureCompletedAt: coverage?.membership_completed_at
|
|
311
|
+
? new Date(coverage.membership_completed_at).toISOString()
|
|
312
|
+
: null,
|
|
313
|
+
reason: complete
|
|
314
|
+
? 'complete'
|
|
315
|
+
: coverage?.membership_started_at
|
|
316
|
+
? 'capture_in_progress'
|
|
317
|
+
: 'legacy_or_capture_not_enabled',
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Future enabled writers call this inside the SAME transaction as the fenced
|
|
324
|
+
* sheet write. No runtime calls it in the expand release. Retry preserves the
|
|
325
|
+
* first snapshot; a different immutable input/result fails loudly.
|
|
326
|
+
*/
|
|
327
|
+
export async function appendRuntimeSheetRunRowMembership(
|
|
328
|
+
query: RuntimeSheetOrmQuery,
|
|
329
|
+
row: Omit<RuntimeSheetRunRowMembership, 'recordedAt'>,
|
|
330
|
+
): Promise<void> {
|
|
331
|
+
return appendRuntimeSheetRunRowMembershipBatch(query, [row]);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** A single bounded SQL write per canonical sheet batch, in its transaction. */
|
|
335
|
+
export async function appendRuntimeSheetRunRowMembershipBatch(
|
|
336
|
+
query: RuntimeSheetOrmQuery,
|
|
337
|
+
rows: ReadonlyArray<Omit<RuntimeSheetRunRowMembership, 'recordedAt'>>,
|
|
338
|
+
schema = 'storage',
|
|
339
|
+
): Promise<void> {
|
|
340
|
+
if (rows.length === 0) return;
|
|
341
|
+
const table = `"${schema.replace(/"/g, '""')}"."${RUNTIME_SHEET_RUN_MEMBERSHIP_TABLE}"`;
|
|
342
|
+
const result = await query<{ row_key: string }>(
|
|
343
|
+
`INSERT INTO ${table} AS target
|
|
344
|
+
(run_id, dataset_id, row_key, phase, generation, attempt_seq, row_version, input_index, input_digest, status, row_data, source_ref)
|
|
345
|
+
SELECT "runId", "datasetId", "rowKey", phase, generation, "attemptSeq", "rowVersion", "inputIndex", "inputDigest", status, data, source
|
|
346
|
+
FROM jsonb_to_recordset($1::jsonb) AS snapshots(
|
|
347
|
+
"runId" text, "datasetId" text, "rowKey" text, phase text, generation text,
|
|
348
|
+
"attemptSeq" integer, "rowVersion" bigint, "inputIndex" bigint, "inputDigest" text, status text, data jsonb, source jsonb)
|
|
349
|
+
ON CONFLICT (run_id, dataset_id, row_key, phase, generation) DO UPDATE SET row_key = EXCLUDED.row_key
|
|
350
|
+
WHERE target.input_index IS NOT DISTINCT FROM EXCLUDED.input_index
|
|
351
|
+
AND target.input_digest = EXCLUDED.input_digest AND target.status = EXCLUDED.status
|
|
352
|
+
AND target.row_data = EXCLUDED.row_data AND target.source_ref = EXCLUDED.source_ref
|
|
353
|
+
AND target.attempt_seq IS NOT DISTINCT FROM EXCLUDED.attempt_seq
|
|
354
|
+
AND target.row_version IS NOT DISTINCT FROM EXCLUDED.row_version
|
|
355
|
+
RETURNING row_key`,
|
|
356
|
+
[JSON.stringify(rows)],
|
|
357
|
+
);
|
|
358
|
+
if (result.rows.length !== rows.length)
|
|
359
|
+
throw new Error(
|
|
360
|
+
'Immutable run row membership conflicts with its retained input or result.',
|
|
361
|
+
);
|
|
362
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { TOOL_EXECUTE_PROVIDER_PACING_WAIT_CODE } from './tool-execute-retry-policy';
|
|
2
|
+
|
|
1
3
|
export type AsyncOperationJsonValue =
|
|
2
4
|
| string
|
|
3
5
|
| number
|
|
@@ -6,6 +8,15 @@ export type AsyncOperationJsonValue =
|
|
|
6
8
|
| readonly AsyncOperationJsonValue[]
|
|
7
9
|
| { readonly [key: string]: AsyncOperationJsonValue };
|
|
8
10
|
|
|
11
|
+
/** Shared timeout wording for the provider boundary and its keyed runtime continuation. */
|
|
12
|
+
export function formatAsyncOperationTimeout(input: {
|
|
13
|
+
startAction: string;
|
|
14
|
+
jobId: string | null;
|
|
15
|
+
timeoutDescription: string;
|
|
16
|
+
}): string {
|
|
17
|
+
return `Async operation ${input.startAction} (${input.jobId}) did not reach a terminal state within ${input.timeoutDescription}.`;
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
export type AsyncOperationTerminalOutcome =
|
|
10
21
|
| 'running'
|
|
11
22
|
| 'succeeded'
|
|
@@ -1111,7 +1122,7 @@ export async function executeAsyncOperationLifecycle<Response>(input: {
|
|
|
1111
1122
|
}): Promise<
|
|
1112
1123
|
{ kind: 'response'; response: Response } | { kind: 'timed_out' }
|
|
1113
1124
|
> => {
|
|
1114
|
-
let
|
|
1125
|
+
let pacingRefusals = 0;
|
|
1115
1126
|
while (true) {
|
|
1116
1127
|
const remainingTimeoutMs = Math.max(
|
|
1117
1128
|
0,
|
|
@@ -1131,18 +1142,40 @@ export async function executeAsyncOperationLifecycle<Response>(input: {
|
|
|
1131
1142
|
: { kind: 'response', response };
|
|
1132
1143
|
} catch (error) {
|
|
1133
1144
|
if (abortSignal.aborted) return { kind: 'timed_out' };
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1145
|
+
const refusal =
|
|
1146
|
+
error && typeof error === 'object'
|
|
1147
|
+
? (error as Record<string, unknown>)
|
|
1148
|
+
: null;
|
|
1149
|
+
// This is the companion's admission refusal, never evidence that the
|
|
1150
|
+
// original launch was not sent. Retain the current job, terminal poll
|
|
1151
|
+
// and result page while retrying only this undispatched action.
|
|
1152
|
+
if (
|
|
1153
|
+
!(error instanceof AsyncOperationCompanionPacingError) &&
|
|
1154
|
+
(refusal?.status !== 503 ||
|
|
1155
|
+
refusal.code !== TOOL_EXECUTE_PROVIDER_PACING_WAIT_CODE ||
|
|
1156
|
+
refusal.retryable !== true ||
|
|
1157
|
+
refusal.failureOrigin !== 'deepline_rate_limit' ||
|
|
1158
|
+
refusal.providerRequestDispatched !== false)
|
|
1159
|
+
) {
|
|
1160
|
+
throw error;
|
|
1161
|
+
}
|
|
1162
|
+
pacingRefusals += 1;
|
|
1163
|
+
const retryAfterMs =
|
|
1164
|
+
error instanceof AsyncOperationCompanionPacingError
|
|
1165
|
+
? error.retryAfterMs
|
|
1166
|
+
: refusal?.retryAfterMs;
|
|
1139
1167
|
const delayMs = Math.max(
|
|
1140
1168
|
1,
|
|
1141
1169
|
asyncOperationRateLimitPollDelayMs({
|
|
1142
1170
|
contract: effectiveContract,
|
|
1143
1171
|
pollAttempt: request.pollAttempt,
|
|
1144
|
-
rateLimitAttempt:
|
|
1145
|
-
retryAfterMs:
|
|
1172
|
+
rateLimitAttempt: pacingRefusals,
|
|
1173
|
+
retryAfterMs:
|
|
1174
|
+
typeof retryAfterMs === 'number' &&
|
|
1175
|
+
Number.isFinite(retryAfterMs) &&
|
|
1176
|
+
retryAfterMs >= 0
|
|
1177
|
+
? retryAfterMs
|
|
1178
|
+
: null,
|
|
1146
1179
|
}),
|
|
1147
1180
|
);
|
|
1148
1181
|
const remainingAfterRefusal = Math.max(
|