deepline 0.3.391 → 0.3.393

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.
Files changed (154) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +156 -24
  2. package/dist/bundling-sources/sdk/src/compat.ts +9 -0
  3. package/dist/bundling-sources/sdk/src/http.ts +5 -1
  4. package/dist/bundling-sources/sdk/src/index.ts +5 -0
  5. package/dist/bundling-sources/sdk/src/plays/csv-data-input.ts +3 -0
  6. package/dist/bundling-sources/sdk/src/plays/run-request.ts +182 -6
  7. package/dist/bundling-sources/sdk/src/release.ts +4 -1
  8. package/dist/bundling-sources/sdk/src/types.ts +15 -0
  9. package/dist/bundling-sources/shared_libs/integration-contracts/request-warnings.ts +64 -0
  10. package/dist/bundling-sources/shared_libs/play-data-plane/runtime-sheet-storage-contract.ts +315 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/async-operation.ts +41 -8
  12. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +418 -44
  13. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +36 -3
  14. package/dist/bundling-sources/shared_libs/play-runtime/durable-invocation-fence.ts +36 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/map-execution-frame.ts +11 -3
  16. package/dist/bundling-sources/shared_libs/play-runtime/play-runs/projection/gateway-adapter.ts +1 -0
  17. package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +19 -1
  18. package/dist/bundling-sources/shared_libs/play-runtime/run-identity.ts +26 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +158 -10
  20. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +235 -2
  21. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +92 -58
  22. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-pool.ts +7 -1
  23. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +30 -7
  24. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1161 -316
  25. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +28 -15
  26. package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +24 -3
  27. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +31 -0
  28. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +37 -4
  29. package/dist/bundling-sources/shared_libs/plays/dataset.ts +172 -79
  30. package/dist/bundling-sources/shared_libs/plays/extension-bridge.ts +57 -0
  31. package/dist/bundling-sources/shared_libs/plays/run-summary-types.ts +1 -0
  32. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +173 -1
  33. package/dist/cli/{auth-6QET7NFP.mjs → auth-5PXHA232.mjs} +4 -4
  34. package/dist/cli/auth-P3MVDTXM.js +39 -0
  35. package/dist/cli/{billing-KJAGTCFR.js → billing-JFB2XLSO.js} +101 -74
  36. package/dist/cli/{billing-HAQ2VDQX.mjs → billing-Z2N7ZAGM.mjs} +32 -5
  37. package/dist/cli/{chunk-556BMGEB.js → chunk-2HAD745V.js} +242 -183
  38. package/dist/cli/{chunk-HFTOOZGN.mjs → chunk-2LGC4EFT.mjs} +3 -3
  39. package/dist/cli/{chunk-QCWIORKR.mjs → chunk-4PT2VA3A.mjs} +2 -2
  40. package/dist/cli/{chunk-KA7X75HC.js → chunk-4XX6443T.js} +176 -87
  41. package/dist/cli/{chunk-YBZY3EZF.mjs → chunk-5KWKZIQR.mjs} +4 -4
  42. package/dist/cli/{chunk-VH4B5OYM.mjs → chunk-5PRVMXKB.mjs} +27 -2
  43. package/dist/cli/{chunk-DSWSEHRX.mjs → chunk-6I73X254.mjs} +1 -1
  44. package/dist/cli/{chunk-6L4P7Z6C.mjs → chunk-7HR7BGZL.mjs} +13 -3
  45. package/dist/cli/{chunk-4S6X74A3.js → chunk-AARX5L4B.js} +2 -2
  46. package/dist/cli/{chunk-MHLCMJ2E.js → chunk-CDFH2DYL.js} +20 -19
  47. package/dist/cli/{chunk-PFPP4BKS.mjs → chunk-FQ6VHPW4.mjs} +2 -2
  48. package/dist/cli/{chunk-5JLQPVNX.js → chunk-GSK6NHVM.js} +1 -0
  49. package/dist/cli/{chunk-TLHF54VX.mjs → chunk-HLEESTCH.mjs} +1 -1
  50. package/dist/cli/{chunk-EDWJY6T4.mjs → chunk-HQXL5526.mjs} +1 -0
  51. package/dist/cli/{chunk-Q6EOHNRH.mjs → chunk-JI2B4RZP.mjs} +119 -6
  52. package/dist/cli/{chunk-I3MKXASV.mjs → chunk-KIYU2TGL.mjs} +2 -2
  53. package/dist/cli/{chunk-Y6VOGHF2.mjs → chunk-KZZC33PY.mjs} +0 -181
  54. package/dist/cli/{chunk-XBPLTHGB.js → chunk-L2SYLGRP.js} +8 -8
  55. package/dist/cli/{chunk-K3ODOHOI.mjs → chunk-LMMEAMA7.mjs} +3 -2
  56. package/dist/cli/{chunk-KWF4HJF7.mjs → chunk-LZPCDB7U.mjs} +3 -2
  57. package/dist/cli/{chunk-I7EVBLTW.js → chunk-M7Y7JYZX.js} +32 -7
  58. package/dist/cli/{chunk-QGKPTBRR.js → chunk-MS6GUPBB.js} +3 -3
  59. package/dist/cli/{chunk-ME2DZAHC.js → chunk-OXNOAD6Y.js} +26 -26
  60. package/dist/cli/{chunk-XEV5LMS2.js → chunk-PTDECJGP.js} +50 -50
  61. package/dist/cli/{chunk-5O4B6AUN.js → chunk-PWSWAN7Q.js} +25 -2
  62. package/dist/cli/{chunk-DRZFTRZJ.mjs → chunk-Q26LUSQ2.mjs} +79 -20
  63. package/dist/cli/{chunk-3VN4FTE3.js → chunk-RDXFGPJU.js} +1 -182
  64. package/dist/cli/{chunk-7E66A24V.mjs → chunk-RKMB3N7K.mjs} +25 -2
  65. package/dist/cli/{chunk-WLODHQ7S.js → chunk-RWHWF5ZJ.js} +3 -2
  66. package/dist/cli/{chunk-H3DCZOXR.mjs → chunk-STNYIMIV.mjs} +97 -8
  67. package/dist/cli/chunk-UBKA33KY.mjs +310 -0
  68. package/dist/cli/{chunk-PAIMOGCM.js → chunk-UHNDVD3P.js} +125 -12
  69. package/dist/cli/{chunk-H2ER7QWR.mjs → chunk-UOGZUA24.mjs} +180 -18
  70. package/dist/cli/{chunk-RTH26NF3.js → chunk-VIQJIX2P.js} +73 -63
  71. package/dist/cli/{chunk-ED42XCOW.js → chunk-VIWE6WRK.js} +629 -467
  72. package/dist/cli/{chunk-RA2SDTSX.js → chunk-W4WOMXR3.js} +15 -15
  73. package/dist/cli/chunk-XDVT3GOM.js +310 -0
  74. package/dist/cli/{chunk-UOSKJIXE.js → chunk-Z3OOZM5L.js} +8 -8
  75. package/dist/cli/{csv-V7UHOBFE.mjs → csv-KJWZHKCH.mjs} +3 -3
  76. package/dist/cli/{csv-2WSF5TEM.js → csv-P3Z3YE7H.js} +12 -12
  77. package/dist/cli/{db-TD375E4N.mjs → db-NCM4CQIS.mjs} +6 -5
  78. package/dist/cli/{db-42RL4FZD.js → db-NUZGJNQC.js} +31 -30
  79. package/dist/cli/{describe-GPUKAWOZ.mjs → describe-WT6MM2GO.mjs} +18 -17
  80. package/dist/cli/{describe-7WFCBZUY.js → describe-YGSEN5WC.js} +25 -24
  81. package/dist/cli/{executions-LGVG3FPH.js → executions-PCOLHN7Q.js} +8 -7
  82. package/dist/cli/{executions-M7O63JNA.mjs → executions-XS2IPJ4W.mjs} +6 -5
  83. package/dist/cli/{feedback-PZZA2M4S.js → feedback-4MSH7TFU.js} +7 -7
  84. package/dist/cli/{feedback-6PBSKVB2.mjs → feedback-CCLTS5SV.mjs} +3 -3
  85. package/dist/cli/{git-2VIFM5DI.mjs → git-K2DHPMPC.mjs} +4 -4
  86. package/dist/cli/git-RNM45FX4.js +23 -0
  87. package/dist/cli/index.js +96 -92
  88. package/dist/cli/index.mjs +46 -42
  89. package/dist/cli/{legacy-noops-DPUBZI7N.mjs → legacy-noops-B5S4AR3P.mjs} +4 -4
  90. package/dist/cli/{legacy-noops-IQ6QTZNZ.js → legacy-noops-GPNOPFXV.js} +7 -7
  91. package/dist/cli/{monitors-KBZVQG2U.mjs → monitors-JLMAIPRG.mjs} +6 -5
  92. package/dist/cli/{monitors-6GWAZFYV.js → monitors-ZAWD7NSN.js} +87 -86
  93. package/dist/cli/{org-JKGTHXOZ.js → org-P2KN4CES.js} +66 -65
  94. package/dist/cli/{org-CP3VSJBX.mjs → org-T3JTHP4G.mjs} +6 -5
  95. package/dist/cli/{play-DD2ME4L4.mjs → play-NK6BLRGL.mjs} +12 -11
  96. package/dist/cli/play-PDXR6JFB.js +158 -0
  97. package/dist/cli/{providers-JUNOFKI7.mjs → providers-3NQRA7IT.mjs} +6 -5
  98. package/dist/cli/{providers-P34QYYG6.js → providers-5QRZN3DT.js} +9 -8
  99. package/dist/cli/{quickstart-N6SWOK7E.mjs → quickstart-PIA2JGPV.mjs} +3 -3
  100. package/dist/cli/{quickstart-UL3DEFA4.js → quickstart-R7NUZGNW.js} +5 -5
  101. package/dist/cli/run-identity-G257GQED.js +25 -0
  102. package/dist/cli/run-identity-MRNCEGV2.mjs +25 -0
  103. package/dist/cli/{search-SC7I56HX.mjs → search-AIDIIGLY.mjs} +7 -6
  104. package/dist/cli/search-QRWL7MFM.js +27 -0
  105. package/dist/cli/{secrets-6ZJE7ZPM.js → secrets-BT3PGMWN.js} +15 -14
  106. package/dist/cli/{secrets-RTARNYF5.mjs → secrets-OQNHL45O.mjs} +6 -5
  107. package/dist/cli/{sessions-6IZ2XHGW.js → sessions-EYXWOSWK.js} +5 -5
  108. package/dist/cli/{sessions-7RKV2T3O.mjs → sessions-SF7WJPJP.mjs} +4 -4
  109. package/dist/cli/{settings-YX3XJDXE.js → settings-BA3GHBOD.js} +76 -75
  110. package/dist/cli/{settings-V6PXA6HY.mjs → settings-Y57JTFDD.mjs} +9 -8
  111. package/dist/cli/{setup-V2O4UJNT.mjs → setup-7GR5GBHA.mjs} +6 -6
  112. package/dist/cli/{setup-CQ5VRNNU.js → setup-RERXYC66.js} +41 -41
  113. package/dist/cli/{skills-VHJZWEHA.mjs → skills-SJWIQAV5.mjs} +4 -4
  114. package/dist/cli/skills-V7EY75HB.js +28 -0
  115. package/dist/cli/tools-H5HLBCHH.js +41 -0
  116. package/dist/cli/{tools-OD5WKEWY.mjs → tools-ZF3JXPNL.mjs} +10 -9
  117. package/dist/cli/update-472HA3LT.js +28 -0
  118. package/dist/cli/{update-CRMNOMJ3.mjs → update-FFFYQAOJ.mjs} +6 -6
  119. package/dist/cli/{workflow-XHORSD5T.js → workflow-SZJVQH4T.js} +17 -16
  120. package/dist/cli/{workflow-EVDYNCDD.mjs → workflow-XVFEEGAS.mjs} +7 -6
  121. package/dist/{client-DpzZi4fU.d.mts → client-DU5dtHDy.d.mts} +157 -4
  122. package/dist/{client-BhK9kix9.d.ts → client-mCE88hC4.d.ts} +157 -4
  123. package/dist/{compiler-manifest-CY2v8ab0.d.ts → compiler-manifest-DE4RdAiY.d.mts} +61 -1
  124. package/dist/{compiler-manifest-CY2v8ab0.d.mts → compiler-manifest-DE4RdAiY.d.ts} +61 -1
  125. package/dist/{index-B9fBZH1g.d.mts → index-BI6nWgaV.d.mts} +1 -1
  126. package/dist/{index-BZ6uzZJl.d.ts → index-Bi-f7HFT.d.ts} +1 -1
  127. package/dist/index.d.mts +5 -5
  128. package/dist/index.d.ts +5 -5
  129. package/dist/index.js +120 -29
  130. package/dist/index.mjs +111 -20
  131. package/dist/install-integrity.json +94 -88
  132. package/dist/plays/bundle-play-file.d.mts +3 -3
  133. package/dist/plays/bundle-play-file.d.ts +3 -3
  134. package/dist/plays/bundle-play-file.mjs +2 -2
  135. package/dist/plays/csv-data-input.d.mts +2 -0
  136. package/dist/plays/csv-data-input.d.ts +2 -0
  137. package/dist/plays/csv-data-input.js +2 -2
  138. package/dist/plays/csv-data-input.mjs +1 -1
  139. package/dist/plays/run-request.d.mts +2 -2
  140. package/dist/plays/run-request.d.ts +2 -2
  141. package/dist/plays/run-request.js +5 -2
  142. package/dist/plays/run-request.mjs +4 -1
  143. package/dist/release.d.mts +4 -1
  144. package/dist/release.d.ts +4 -1
  145. package/dist/release.js +2 -2
  146. package/dist/release.mjs +1 -1
  147. package/package.json +1 -1
  148. package/dist/cli/auth-6ZUH42ZA.js +0 -39
  149. package/dist/cli/git-ZSJD3BID.js +0 -23
  150. package/dist/cli/play-G5GVMIBN.js +0 -157
  151. package/dist/cli/search-W4GXWTOM.js +0 -26
  152. package/dist/cli/skills-LBZPJTPI.js +0 -28
  153. package/dist/cli/tools-TQNM473H.js +0 -40
  154. package/dist/cli/update-QY6GAK6B.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 pacingAttempt = 0;
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
- if (!(error instanceof AsyncOperationCompanionPacingError)) throw error;
1135
- // This refusal applies only to the current companion read. The launch
1136
- // already owns its job and hold. Stay inside their original deadline
1137
- // and repeat this exact action/page, never the enclosing start request.
1138
- pacingAttempt += 1;
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: pacingAttempt,
1145
- retryAfterMs: error.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(