deepline 0.3.461 → 0.3.463
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-core.ts +7593 -0
- package/dist/bundling-sources/sdk/src/client.ts +8 -7366
- package/dist/bundling-sources/sdk/src/http-core.ts +1239 -0
- package/dist/bundling-sources/sdk/src/http.ts +6 -1231
- package/dist/bundling-sources/sdk/src/index.ts +4 -1
- package/dist/bundling-sources/sdk/src/play.ts +5 -5
- package/dist/bundling-sources/sdk/src/plays/client-core.ts +437 -0
- package/dist/bundling-sources/sdk/src/plays/client.ts +4 -421
- package/dist/bundling-sources/sdk/src/plays/input-example.ts +3 -4
- package/dist/bundling-sources/sdk/src/plays/play-description.ts +1 -1
- package/dist/bundling-sources/sdk/src/plays/run-request.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/sdk/src/server.ts +43 -0
- package/dist/bundling-sources/sdk/src/types.ts +15 -5
- package/dist/bundling-sources/shared_libs/integration-contracts/billing-entitlements.ts +15 -0
- package/dist/bundling-sources/shared_libs/monitors/billing/tamradar.ts +17 -17
- package/dist/bundling-sources/shared_libs/monitors/callback-security.ts +0 -307
- package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +3 -0
- package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +3 -2
- package/dist/bundling-sources/shared_libs/monitors/package.json +2 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-callback-verification.ts +34 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +61 -0
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +122 -2
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-dead-letter.ts +11 -1
- package/dist/bundling-sources/shared_libs/monitors/signal-radars/inbound-failure-codes.ts +24 -0
- package/dist/bundling-sources/shared_libs/observability/dlq.ts +32 -0
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +57 -3
- package/dist/bundling-sources/shared_libs/observability/scheduled-work.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/r2.ts +66 -1
- package/dist/bundling-sources/shared_libs/play-data-plane/sheet-column-storage.ts +36 -7
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +250 -11
- package/dist/bundling-sources/shared_libs/play-runtime/array-input-store.ts +319 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +991 -359
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-janitor.ts +13 -0
- package/dist/bundling-sources/shared_libs/play-runtime/daytona-sandbox-reaper.ts +110 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +155 -6
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +110 -6
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +127 -13
- package/dist/bundling-sources/shared_libs/play-runtime/execution-capabilities.ts +14 -1
- package/dist/bundling-sources/shared_libs/play-runtime/fixture-behavior.ts +57 -1
- package/dist/bundling-sources/shared_libs/play-runtime/free-company-lookup-batching.ts +218 -0
- package/dist/bundling-sources/shared_libs/play-runtime/gateway-postgres-admission.ts +42 -5
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-budget-state-backend.ts +6 -1
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +92 -31
- package/dist/bundling-sources/shared_libs/play-runtime/governor/block-reserving-budget-state-backend.ts +69 -14
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +93 -23
- package/dist/bundling-sources/shared_libs/play-runtime/governor/in-memory-rate-state-backend.ts +15 -4
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +22 -18
- package/dist/bundling-sources/shared_libs/play-runtime/governor/rate-state-backend.ts +5 -0
- package/dist/bundling-sources/shared_libs/play-runtime/launch-identity.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-memory-limits.ts +190 -14
- package/dist/bundling-sources/shared_libs/play-runtime/memory-ledger.ts +131 -0
- package/dist/bundling-sources/shared_libs/play-runtime/modal-runtime-config.ts +77 -2
- package/dist/bundling-sources/shared_libs/play-runtime/output-size-limits.ts +271 -2
- package/dist/bundling-sources/shared_libs/play-runtime/play-run-recovery-policy.ts +51 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/contracts.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runs/sandbox-cleanup/job.ts +27 -1
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-batching-registry.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/provider-text-for-log.ts +63 -0
- package/dist/bundling-sources/shared_libs/play-runtime/resource-governor.ts +19 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-event-transport.ts +112 -16
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +104 -2
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +265 -151
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-gateway-transport.ts +17 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +35 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-sheet-persistence-retry.ts +12 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-control-transport.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-labels.ts +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-launch-transport.ts +136 -70
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +237 -36
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-runtime-watchdog.ts +148 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-session-execution.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +285 -32
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/modal.ts +251 -56
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/runtime-sandbox-reconciliation.ts +29 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/sandbox-create-outcome.ts +27 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/types.ts +22 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +1397 -382
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-array-input-receipts.ts +920 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-constants.ts +16 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-environment.ts +38 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-flow.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +26 -1
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-operation-contract.ts +30 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +14 -3
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-writer.ts +31 -4
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-transition.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-compute-usage.ts +20 -7
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/contract.ts +12 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/index.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/sandbox-runs/runner-backend-adapter.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +1496 -320
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-worker-required-env.ts +31 -1
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +1389 -129
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-dispatch-authority.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/run-ledger-outbox-recovery.ts +162 -23
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-event-search.ts +499 -115
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +377 -9
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-ledger-reconciliation.ts +117 -8
- package/dist/bundling-sources/shared_libs/play-runtime/secret-resolution-retry-policy.ts +25 -0
- package/dist/bundling-sources/shared_libs/play-runtime/terminal-runtime-performance.ts +391 -0
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +6 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/mapped-calls.ts +171 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +16 -1
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +38 -12
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +33 -9
- package/dist/bundling-sources/shared_libs/play-runtime/transport-error-diagnostics.ts +28 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-storage-contract.ts +55 -5
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +41 -1
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +4 -0
- package/dist/bundling-sources/shared_libs/plays/dataset.ts +9 -3
- package/dist/bundling-sources/shared_libs/plays/deploy/manifest-contract.ts +2 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/manifest.ts +3 -2
- package/dist/bundling-sources/shared_libs/plays/deploy/project-serializer.ts +2 -2
- package/dist/bundling-sources/shared_libs/plays/run-output-summary.ts +66 -0
- package/dist/bundling-sources/shared_libs/plays/script-entry.ts +1 -1
- package/dist/bundling-sources/shared_libs/product-notifications/catalog.ts +43 -1
- package/dist/bundling-sources/shared_libs/product-notifications/content.ts +9 -0
- package/dist/bundling-sources/shared_libs/product-notifications/email.ts +91 -13
- package/dist/bundling-sources/shared_libs/product-notifications/events.ts +3 -0
- package/dist/bundling-sources/shared_libs/product-notifications/slack-delivery.ts +41 -24
- package/dist/bundling-sources/shared_libs/security/attested-client-ip.ts +91 -0
- package/dist/bundling-sources/shared_libs/security/internal-actor.ts +0 -11
- package/dist/cli/{auth-6HJXNWUG.mjs → auth-LZLBR4C3.mjs} +5 -4
- package/dist/cli/auth-XPGKBJKB.js +41 -0
- package/dist/cli/{billing-6P5HO6QG.js → billing-6TJPNJQD.js} +152 -78
- package/dist/cli/{billing-MJ42N7YF.mjs → billing-OTOP5RE4.mjs} +84 -10
- package/dist/cli/{chunk-FCAZQ7WV.js → chunk-2NISBXMN.js} +52 -50
- package/dist/cli/{chunk-PNATXKLP.js → chunk-2ZIRCBYG.js} +6 -6
- package/dist/cli/{chunk-5FDKJQZE.js → chunk-3BOIOCNX.js} +1 -1
- package/dist/cli/chunk-3BVLLJ3H.mjs +1038 -0
- package/dist/cli/{chunk-FJZILBL7.mjs → chunk-3U5EAP6E.mjs} +5 -5
- package/dist/cli/{describe-FRAVCY5F.mjs → chunk-4YZKBBLZ.mjs} +19 -36
- package/dist/cli/{chunk-QWRHGGUH.js → chunk-5DRNNC72.js} +20 -2
- package/dist/cli/{chunk-Z2GUFYT6.js → chunk-5LEAXRHV.js} +11 -11
- package/dist/cli/{chunk-5MDIZX7M.js → chunk-5LHXOIPT.js} +8 -6
- package/dist/cli/{chunk-NMJ4D2WO.js → chunk-6PMCRYDA.js} +216 -91
- package/dist/cli/{chunk-EBU7LQ64.js → chunk-6V7KVRR5.js} +3 -3
- package/dist/cli/{chunk-MGXEUZHL.mjs → chunk-7D6X7IXW.mjs} +7 -5
- package/dist/cli/{chunk-SAZFZQRI.js → chunk-7FCYPAPP.js} +2 -2
- package/dist/cli/{chunk-ACCR7POB.js → chunk-7XYDDCQ2.js} +27 -27
- package/dist/cli/{chunk-5IYOUBFZ.js → chunk-ALZBANZI.js} +28 -54
- package/dist/cli/{chunk-3DNKFH2G.mjs → chunk-BAPTQDLF.mjs} +19 -1
- package/dist/cli/chunk-BGNWI3KH.js +827 -0
- package/dist/cli/{chunk-V7REJRDZ.mjs → chunk-C25QE4CH.mjs} +1 -1
- package/dist/cli/{chunk-FGMX4HUZ.js → chunk-CBZP6J3V.js} +29 -27
- package/dist/cli/{chunk-QEYHTIV4.mjs → chunk-CTQ56DXP.mjs} +10 -1
- package/dist/cli/{chunk-IHHLTHE7.mjs → chunk-CVQ7N5CV.mjs} +267 -38
- package/dist/cli/{chunk-7DH2FD7Y.mjs → chunk-D464JW3N.mjs} +5 -85
- package/dist/cli/{chunk-MEAIOXJL.mjs → chunk-DHXCBITQ.mjs} +7 -5
- package/dist/cli/chunk-F2RHUPVH.mjs +827 -0
- package/dist/cli/chunk-F6QV42MC.mjs +91 -0
- package/dist/cli/{chunk-OZBWRICM.js → chunk-FK2HNDCN.js} +2 -2
- package/dist/cli/{chunk-BGY2PLP7.mjs → chunk-GUT7CLOL.mjs} +23 -49
- package/dist/cli/{chunk-RY4IVOU3.mjs → chunk-I624J4H5.mjs} +16 -806
- package/dist/cli/{chunk-Z2XUZC34.js → chunk-IAYSWZDJ.js} +188 -76
- package/dist/cli/{chunk-FGLKOWSC.mjs → chunk-IKWRUNAP.mjs} +1 -1
- package/dist/cli/{chunk-KCCOQ5G5.js → chunk-JVDKZJUH.js} +511 -282
- package/dist/cli/{chunk-HDFRJ4IT.mjs → chunk-JVHOS3K3.mjs} +177 -65
- package/dist/cli/chunk-LYFKBLJI.mjs +1039 -0
- package/dist/cli/{chunk-SLEUWLJE.mjs → chunk-MSY22NQW.mjs} +5 -5
- package/dist/cli/{chunk-5FY72YOV.js → chunk-NAPTMYTY.js} +8 -8
- package/dist/cli/{chunk-O6QOMHCH.js → chunk-NDHJR2WZ.js} +37 -35
- package/dist/cli/{chunk-KXMNM4AQ.js → chunk-NMU3VR5U.js} +4 -5
- package/dist/cli/chunk-NTLI6P6Y.mjs +19 -0
- package/dist/cli/{chunk-RFONC4WJ.mjs → chunk-O3BJYD6D.mjs} +3 -3
- package/dist/cli/{describe-2WQY7ZCY.js → chunk-PVY4DPPF.js} +27 -44
- package/dist/cli/{chunk-WIYWSYDS.mjs → chunk-Q2EIZKVX.mjs} +3 -3
- package/dist/cli/{chunk-VZBCEA5S.mjs → chunk-RD6IQ2JY.mjs} +223 -92
- package/dist/cli/chunk-RGOUCIIZ.js +1039 -0
- package/dist/cli/{chunk-EIMG75BT.mjs → chunk-S3A4IXBQ.mjs} +4 -5
- package/dist/cli/{chunk-QIW7TVV6.mjs → chunk-S4XQCFES.mjs} +14 -12
- package/dist/cli/chunk-SKU6ECIY.js +19 -0
- package/dist/cli/{chunk-CRYOUPEB.js → chunk-TIRBDQ3W.js} +16 -16
- package/dist/cli/{chunk-G7L27KZB.mjs → chunk-TKAQR3IB.mjs} +31 -21
- package/dist/cli/{chunk-MDJPEGVN.js → chunk-UC4SFYNU.js} +392 -261
- package/dist/cli/{chunk-3BMLICF7.mjs → chunk-UIURR26V.mjs} +13 -8
- package/dist/cli/{chunk-TNMBPMMC.js → chunk-UROQDCVW.js} +13 -4
- package/dist/cli/{chunk-R64BGGRQ.mjs → chunk-VDGTTH7K.mjs} +2 -2
- package/dist/cli/chunk-VLEVVDWG.js +91 -0
- package/dist/cli/{chunk-R2GLGLDR.mjs → chunk-VPHN2HAL.mjs} +216 -91
- package/dist/cli/chunk-VY2GBNTX.js +1038 -0
- package/dist/cli/{chunk-CFUTOWE4.js → chunk-WG3KGAII.js} +8 -88
- package/dist/cli/{chunk-QAJAJPKK.mjs → chunk-WUMIYOXQ.mjs} +4 -2
- package/dist/cli/{chunk-SCIR5JXW.js → chunk-WUZS2HK6.js} +56 -51
- package/dist/cli/{chunk-T3MVIPHG.mjs → chunk-XDWS2JJT.mjs} +1 -1
- package/dist/cli/{chunk-KGQNIA37.js → chunk-XQCTG5OO.js} +44 -34
- package/dist/cli/{chunk-KVJ5VHW5.mjs → chunk-XW2JIYG7.mjs} +2 -2
- package/dist/cli/chunk-YGGIZ3VT.js +920 -0
- package/dist/cli/command-docs.d.mts +5 -0
- package/dist/cli/command-docs.d.ts +5 -0
- package/dist/cli/command-docs.js +2 -2
- package/dist/cli/command-docs.mjs +1 -1
- package/dist/cli/{csv-NX7NO3O6.js → csv-EH5QWR4O.js} +13 -12
- package/dist/cli/{csv-DQ6GW5B5.mjs → csv-OK45Z5XQ.mjs} +4 -3
- package/dist/cli/{db-R72DFMP6.js → db-3ESJKETS.js} +30 -27
- package/dist/cli/{db-2YP33M4K.mjs → db-6Z5E4HKN.mjs} +10 -7
- package/dist/cli/describe-PNDQB7YS.mjs +48 -0
- package/dist/cli/describe-QUF7VTTR.js +48 -0
- package/dist/cli/describe-core.d.mts +153 -5
- package/dist/cli/describe-core.d.ts +153 -5
- package/dist/cli/describe-core.js +9 -5
- package/dist/cli/describe-core.mjs +10 -6
- package/dist/cli/{executions-WFBU7L2Q.mjs → executions-2NTWQFEG.mjs} +7 -5
- package/dist/cli/{executions-ZLHD4QIT.js → executions-KUOBRX5V.js} +9 -7
- package/dist/cli/{feedback-CN3HJFVF.mjs → feedback-7RPRMSGY.mjs} +6 -5
- package/dist/cli/{feedback-NDV6TPHG.js → feedback-UPHAZJ4C.js} +10 -9
- package/dist/cli/getting-started.d.mts +6 -6
- package/dist/cli/getting-started.d.ts +6 -6
- package/dist/cli/getting-started.js +2 -2
- package/dist/cli/getting-started.mjs +1 -1
- package/dist/cli/{git-QAD6E3HW.mjs → git-SCYP6WSZ.mjs} +5 -4
- package/dist/cli/git-UH653WHV.js +25 -0
- package/dist/cli/index.js +152 -142
- package/dist/cli/index.mjs +56 -46
- package/dist/cli/{legacy-noops-KVC5QJ74.js → legacy-noops-7XMFCVRG.js} +8 -7
- package/dist/cli/{legacy-noops-TPTKNBI2.mjs → legacy-noops-A4KYAGI6.mjs} +5 -4
- package/dist/cli/{monitors-SV57JFLO.mjs → monitors-FLC5J7RI.mjs} +19 -6
- package/dist/cli/{monitors-KCROV6PJ.js → monitors-NZV6IX5H.js} +103 -90
- package/dist/cli/{org-NJYX5743.js → org-IZ7UP3XF.js} +70 -67
- package/dist/cli/{org-KX6QIR7A.mjs → org-LUETJ7Z5.mjs} +9 -6
- package/dist/cli/pages-22ML3O7O.js +252 -0
- package/dist/cli/pages-EUX5EAGF.mjs +252 -0
- package/dist/cli/{play-IQQ57L7Z.mjs → play-CDHPAKKM.mjs} +512 -1508
- package/dist/cli/{play-32L7EQN4.js → play-USJBQADG.js} +1090 -2086
- package/dist/cli/{providers-4WEC2EQN.mjs → providers-CCXZ3SQF.mjs} +7 -5
- package/dist/cli/{providers-ZQV6QLVQ.js → providers-WFEUNPGC.js} +10 -8
- package/dist/cli/{quickstart-HKUSFADF.js → quickstart-O4JSUORZ.js} +13 -12
- package/dist/cli/{quickstart-RQ76RIUE.mjs → quickstart-ZUULI635.mjs} +5 -4
- package/dist/cli/run-ISDJYJMU.js +443 -0
- package/dist/cli/run-QJ4NX5F5.mjs +443 -0
- package/dist/cli/{search-ZRAVFBZC.mjs → search-IX3XKZJ3.mjs} +8 -6
- package/dist/cli/search-TIFNLRPM.js +32 -0
- package/dist/cli/{secrets-CMJOVNVH.mjs → secrets-7VUDXTUZ.mjs} +7 -5
- package/dist/cli/{secrets-J3G5Y43U.js → secrets-ELQSNCTR.js} +16 -14
- package/dist/cli/{sessions-WY7VN4AE.js → sessions-A3FG3AZX.js} +6 -5
- package/dist/cli/{sessions-MDQYY5CB.mjs → sessions-VOIQ62GB.mjs} +5 -4
- package/dist/cli/{settings-4RWZPCT5.mjs → settings-S6N6EFBG.mjs} +14 -11
- package/dist/cli/{settings-HTNTFKCE.js → settings-ZTXQEMZI.js} +80 -77
- package/dist/cli/{setup-P3GYYZWG.mjs → setup-AYPIK4JE.mjs} +12 -10
- package/dist/cli/{setup-QGCAP62X.js → setup-Y3WOIFLS.js} +53 -51
- package/dist/cli/{skills-7NAE4ICL.mjs → skills-5SF5QWNI.mjs} +7 -6
- package/dist/cli/skills-MWFRMG3H.js +31 -0
- package/dist/cli/tools-3XNZ4J64.mjs +55 -0
- package/dist/cli/tools-XEC4OD3R.js +55 -0
- package/dist/cli/{update-GODLRKNB.mjs → update-3U4OVX5R.mjs} +9 -8
- package/dist/cli/update-DWYEYTZE.js +33 -0
- package/dist/cli/{workflow-4BXOUVEO.mjs → workflow-2M5JP7SA.mjs} +10 -8
- package/dist/cli/{workflow-AO3A6LXN.js → workflow-RM7H7555.js} +20 -18
- package/dist/client-DS2IWYZF.d.ts +10 -0
- package/dist/client-DTwnz_AW.d.mts +10 -0
- package/dist/{client-CYqG23K1.d.ts → client-core-B_6HAIBy.d.ts} +385 -12
- package/dist/{client-BbU5_j7n.d.mts → client-core-DzBSlY1U.d.mts} +385 -12
- package/dist/{compiler-manifest-CWz_sNOQ.d.ts → compiler-manifest-Dcyd3-Zd.d.mts} +43 -1
- package/dist/{compiler-manifest-CWz_sNOQ.d.mts → compiler-manifest-Dcyd3-Zd.d.ts} +43 -1
- package/dist/errors-d-sTrZhe.d.mts +142 -0
- package/dist/errors-yQwfgrcX.d.ts +142 -0
- package/dist/{index-BF2JB_Ug.d.mts → index-C6oXchjN.d.mts} +1 -1
- package/dist/{index-4NPXDzlD.d.ts → index-P56zy_z5.d.ts} +1 -1
- package/dist/index.d.mts +11 -147
- package/dist/index.d.ts +11 -147
- package/dist/index.js +16 -12
- package/dist/index.mjs +15 -11
- package/dist/install-integrity.json +141 -104
- 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 -1
- package/dist/plays/play-description.d.mts +2 -2
- package/dist/plays/play-description.d.ts +2 -2
- package/dist/plays/play-description.js +2 -2
- package/dist/plays/play-description.mjs +1 -1
- package/dist/plays/run-request.d.mts +2 -2
- package/dist/plays/run-request.d.ts +2 -2
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +2 -2
- package/dist/release.mjs +1 -1
- package/dist/server.d.mts +17 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.js +76 -0
- package/dist/server.mjs +76 -0
- package/package.json +6 -1
- package/dist/cli/auth-IACERYQH.js +0 -40
- package/dist/cli/chunk-PQUUWFXY.js +0 -95
- package/dist/cli/chunk-YBWAG72P.mjs +0 -95
- package/dist/cli/chunk-YQDUODZW.js +0 -1710
- package/dist/cli/git-JVI36ZFV.js +0 -24
- package/dist/cli/search-75X5LTDE.js +0 -30
- package/dist/cli/skills-BKJ53JOQ.js +0 -30
- package/dist/cli/tools-COWMIEJ6.mjs +0 -45
- package/dist/cli/tools-TASOCE7I.js +0 -45
- package/dist/cli/update-SJIKVME6.js +0 -32
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from './runtime-pg-driver';
|
|
35
35
|
import {
|
|
36
36
|
createDeferredPlayDataset,
|
|
37
|
+
iteratePlayDatasetInputPages,
|
|
37
38
|
type PlayDataset,
|
|
38
39
|
} from '../plays/dataset';
|
|
39
40
|
import type {
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
} from '../play-data-plane/sheet-contract';
|
|
51
52
|
import {
|
|
52
53
|
RuntimeSheetColumns,
|
|
54
|
+
RuntimeSheetColumnSchemaChangeRequired,
|
|
53
55
|
type RuntimeSheetColumnTypes,
|
|
54
56
|
} from '../play-data-plane/sheet-column-storage';
|
|
55
57
|
import {
|
|
@@ -116,9 +118,27 @@ import {
|
|
|
116
118
|
} from './postgres-json';
|
|
117
119
|
import { RECEIPT_STATUS_CODE, receiptStatusFromCode } from './receipt-status';
|
|
118
120
|
import {
|
|
121
|
+
assertWorkReceiptKeyMutable,
|
|
119
122
|
WORK_RECEIPT_EXECUTION_LOCK_TABLE,
|
|
120
123
|
RUNTIME_SHEET_WRITE_INTENT_RECEIPT_KIND,
|
|
121
124
|
} from './work-receipt-storage-contract';
|
|
125
|
+
import type {
|
|
126
|
+
RuntimeArrayInputScope,
|
|
127
|
+
RuntimeArrayInputOccurrence,
|
|
128
|
+
RuntimeArrayInputManifest,
|
|
129
|
+
} from './array-input-store';
|
|
130
|
+
import { RuntimeArrayInputStoreUnavailableError } from './array-input-store';
|
|
131
|
+
import {
|
|
132
|
+
getRuntimeArrayInputManifest as getRuntimeArrayInputManifestReceipt,
|
|
133
|
+
beginRuntimeArrayInput as beginRuntimeArrayInputReceipt,
|
|
134
|
+
putRuntimeArrayInputPage as putRuntimeArrayInputPageReceipt,
|
|
135
|
+
sealRuntimeArrayInput as sealRuntimeArrayInputReceipt,
|
|
136
|
+
readRuntimeArrayInputPage as readRuntimeArrayInputPageReceipt,
|
|
137
|
+
type RuntimeArrayInputBegin,
|
|
138
|
+
type RuntimeArrayInputPutPage,
|
|
139
|
+
type RuntimeArrayInputSeal,
|
|
140
|
+
type RuntimeArrayInputReceiptPort,
|
|
141
|
+
} from './runtime-array-input-receipts';
|
|
122
142
|
import {
|
|
123
143
|
workReceiptClaimableStatusCodes,
|
|
124
144
|
workReceiptActiveOwnerPredicateSql,
|
|
@@ -135,6 +155,7 @@ import type { MapRowOutcome } from './durability-store';
|
|
|
135
155
|
import { RUNTIME_CAPACITY_POLICY } from './runtime-capacity-policy';
|
|
136
156
|
import type { RUNTIME_SHEET_ADMISSION_CAPABILITIES } from './runtime-operation-contract';
|
|
137
157
|
import {
|
|
158
|
+
chunkPreparedRuntimeSheetRows,
|
|
138
159
|
normalizeRuntimeMapInputIndex,
|
|
139
160
|
prepareRuntimeSheetRowsForJsonTransport,
|
|
140
161
|
prepareRuntimeSheetRowTransitions,
|
|
@@ -153,12 +174,17 @@ import {
|
|
|
153
174
|
LEGACY_RUNTIME_RECEIPT_WIRE_HEADER,
|
|
154
175
|
} from './runtime-contract';
|
|
155
176
|
import { PLAY_RUNTIME_API_COMPAT_PATH } from './runtime-api-paths';
|
|
177
|
+
import { RUNNER_GATEWAY_CONNECTION_RETRY_BUDGET_MS } from './runtime-constants';
|
|
156
178
|
import {
|
|
157
179
|
PLAY_RUNTIME_SHEET_ATTEMPT_LEASE_TTL_MS,
|
|
158
180
|
PLAY_RUNTIME_WORK_RECEIPT_LEASE_TTL_MS,
|
|
159
181
|
} from './lease-policy';
|
|
160
182
|
import { BoundedBatchFunction } from './bounded-batch-function';
|
|
161
|
-
import {
|
|
183
|
+
import {
|
|
184
|
+
jsonByteLengthUpTo,
|
|
185
|
+
RUNNER_TERMINAL_PUSH_MAX_BODY_BYTES,
|
|
186
|
+
RUNTIME_SHEET_START_TARGET_BODY_BYTES,
|
|
187
|
+
} from './output-size-limits';
|
|
162
188
|
import {
|
|
163
189
|
parseRuntimeTestFaultCounts,
|
|
164
190
|
PLAY_RUNTIME_TEST_FAULT_HEADER,
|
|
@@ -188,6 +214,9 @@ type RuntimeApiContext = {
|
|
|
188
214
|
disablePostgresPoolCache?: boolean | null;
|
|
189
215
|
postgresSessionUnwrapKey?: string | null;
|
|
190
216
|
abortSignal?: AbortSignal | null;
|
|
217
|
+
onSheetMapCompletion?: (
|
|
218
|
+
diagnostics: RuntimeSheetMapCompletionDiagnostics,
|
|
219
|
+
) => void | Promise<void>;
|
|
191
220
|
};
|
|
192
221
|
|
|
193
222
|
const RUNTIME_RUN_DATASET_CATALOG_TABLE = '_deepline_run_datasets';
|
|
@@ -357,6 +386,7 @@ const runtimeSheetEnsureCache = new Map<
|
|
|
357
386
|
{ expiresAt: number; promise: Promise<void> }
|
|
358
387
|
>();
|
|
359
388
|
const DIRECT_POSTGRES_BATCH_SIZE = 10_000;
|
|
389
|
+
const RUNTIME_MAP_SQL_BATCH_INPUT_BYTES = 2 * 1024 * 1024;
|
|
360
390
|
const RUNTIME_DB_SESSION_ROW_LIMIT_FLOOR = DIRECT_POSTGRES_BATCH_SIZE;
|
|
361
391
|
const APPEND_KEY_SUFFIX_LENGTH = 12;
|
|
362
392
|
// On-demand (create_db_session) mint TTL. Deliberately NOT aligned with
|
|
@@ -427,6 +457,7 @@ export class RuntimeSheetDatasetReadBatchTooLargeError extends Error {
|
|
|
427
457
|
// make progress instead of aborting a healthy queued start after 30 seconds.
|
|
428
458
|
const RUNTIME_SHEET_ADMISSION_REQUEST_TIMEOUT_MS = 10 * 60_000;
|
|
429
459
|
const RUNTIME_SHEET_COMPLETION_SERVER_OPERATION_TIMEOUT_MS = 75_000;
|
|
460
|
+
const RUNTIME_POSTGRES_ROLLBACK_CLEANUP_TIMEOUT_MS = 1_000;
|
|
430
461
|
// Server admission is bounded to 30s and the single connection attempt to
|
|
431
462
|
// 10s. Keep the transport outside that envelope so it never retries while the
|
|
432
463
|
// first server-side write can still be active.
|
|
@@ -540,10 +571,13 @@ export type RuntimeSheetTiming = {
|
|
|
540
571
|
type RuntimeMapCompletionPhase =
|
|
541
572
|
| 'dbSessionAcquire'
|
|
542
573
|
| 'poolAcquire'
|
|
574
|
+
| 'tenantAdmission'
|
|
575
|
+
| 'physicalConnect'
|
|
543
576
|
| 'transactionBegin'
|
|
544
577
|
| 'mapRowsUpdate'
|
|
545
578
|
| 'missingRowsInsert'
|
|
546
579
|
| 'failedRowsUpdate'
|
|
580
|
+
| 'membershipCapture'
|
|
547
581
|
| 'transactionCommit'
|
|
548
582
|
| 'queryClient';
|
|
549
583
|
|
|
@@ -555,12 +589,34 @@ type RuntimeMapCompletionDiagnostics = Record<
|
|
|
555
589
|
provisioningRetryAttempts: number;
|
|
556
590
|
};
|
|
557
591
|
|
|
592
|
+
/**
|
|
593
|
+
* Fixed, payload-free elapsed timings. Nested phases overlap: poolAcquire includes
|
|
594
|
+
* tenantAdmission and physicalConnect (the bounded driver connect attempt).
|
|
595
|
+
*/
|
|
596
|
+
export type RuntimeSheetMapCompletionDiagnostics =
|
|
597
|
+
RuntimeMapCompletionDiagnostics & {
|
|
598
|
+
event: 'finished';
|
|
599
|
+
outcome: 'succeeded' | 'failed';
|
|
600
|
+
inputRows: number;
|
|
601
|
+
uniqueRows: number;
|
|
602
|
+
completedRows: number;
|
|
603
|
+
failedRows: number;
|
|
604
|
+
chunks: number;
|
|
605
|
+
updatedRows: number;
|
|
606
|
+
fencedRows: number;
|
|
607
|
+
totalMs: number;
|
|
608
|
+
};
|
|
609
|
+
|
|
558
610
|
function createRuntimeMapCompletionDiagnostics(): RuntimeMapCompletionDiagnostics {
|
|
559
611
|
return {
|
|
560
612
|
dbSessionAcquireMs: 0,
|
|
561
613
|
dbSessionAcquireFailures: 0,
|
|
562
614
|
poolAcquireMs: 0,
|
|
563
615
|
poolAcquireFailures: 0,
|
|
616
|
+
tenantAdmissionMs: 0,
|
|
617
|
+
tenantAdmissionFailures: 0,
|
|
618
|
+
physicalConnectMs: 0,
|
|
619
|
+
physicalConnectFailures: 0,
|
|
564
620
|
transactionBeginMs: 0,
|
|
565
621
|
transactionBeginFailures: 0,
|
|
566
622
|
mapRowsUpdateMs: 0,
|
|
@@ -569,6 +625,8 @@ function createRuntimeMapCompletionDiagnostics(): RuntimeMapCompletionDiagnostic
|
|
|
569
625
|
missingRowsInsertFailures: 0,
|
|
570
626
|
failedRowsUpdateMs: 0,
|
|
571
627
|
failedRowsUpdateFailures: 0,
|
|
628
|
+
membershipCaptureMs: 0,
|
|
629
|
+
membershipCaptureFailures: 0,
|
|
572
630
|
transactionCommitMs: 0,
|
|
573
631
|
transactionCommitFailures: 0,
|
|
574
632
|
queryClientMs: 0,
|
|
@@ -689,6 +747,26 @@ type RuntimeApiActionRequest =
|
|
|
689
747
|
| {
|
|
690
748
|
action: 'runtime_receipts_release_attempt';
|
|
691
749
|
input: Parameters<typeof releaseRuntimeWorkReceipts>[1];
|
|
750
|
+
}
|
|
751
|
+
| {
|
|
752
|
+
action: 'runtime_array_input_get_manifest';
|
|
753
|
+
input: Parameters<typeof getRuntimeArrayInputManifest>[1];
|
|
754
|
+
}
|
|
755
|
+
| {
|
|
756
|
+
action: 'runtime_array_input_begin';
|
|
757
|
+
input: Parameters<typeof beginRuntimeArrayInput>[1];
|
|
758
|
+
}
|
|
759
|
+
| {
|
|
760
|
+
action: 'runtime_array_input_put_page';
|
|
761
|
+
input: Parameters<typeof putRuntimeArrayInputPage>[1];
|
|
762
|
+
}
|
|
763
|
+
| {
|
|
764
|
+
action: 'runtime_array_input_seal';
|
|
765
|
+
input: Parameters<typeof sealRuntimeArrayInput>[1];
|
|
766
|
+
}
|
|
767
|
+
| {
|
|
768
|
+
action: 'runtime_array_input_read_page';
|
|
769
|
+
input: Parameters<typeof readRuntimeArrayInputPage>[1];
|
|
692
770
|
};
|
|
693
771
|
|
|
694
772
|
/**
|
|
@@ -869,23 +947,42 @@ async function readRuntimeApiResponseJson(input: {
|
|
|
869
947
|
async function postRuntimeApi<TResponse>(
|
|
870
948
|
context: RuntimeApiContext,
|
|
871
949
|
body: RuntimeApiActionRequest,
|
|
950
|
+
preparedBodyJson?: string,
|
|
872
951
|
): Promise<TResponse> {
|
|
873
952
|
const url = resolveRuntimeApiUrl(context);
|
|
874
953
|
const vercelHeaders = vercelProtectionBypassHeader(
|
|
875
954
|
context.vercelProtectionBypassToken,
|
|
876
955
|
);
|
|
956
|
+
// Older receipt gateways validate their top-level scope before dispatching
|
|
957
|
+
// an action. Include the same authenticated input scope so an unsupported
|
|
958
|
+
// named array action reaches their explicit unsupported-action response.
|
|
959
|
+
const requestBody =
|
|
960
|
+
body.action === 'runtime_array_input_get_manifest' ||
|
|
961
|
+
body.action === 'runtime_array_input_begin' ||
|
|
962
|
+
body.action === 'runtime_array_input_put_page' ||
|
|
963
|
+
body.action === 'runtime_array_input_seal' ||
|
|
964
|
+
body.action === 'runtime_array_input_read_page'
|
|
965
|
+
? { ...body, playName: body.input.playName, runId: body.input.runId }
|
|
966
|
+
: body;
|
|
877
967
|
const maxAttempts = runtimeApiMaxAttempts(body.action);
|
|
878
|
-
const
|
|
879
|
-
|
|
968
|
+
const budget = startRuntimeApiRetryBudget(body.action);
|
|
969
|
+
let unreachableError: Error | null = null;
|
|
970
|
+
for (let attempt = 1; ; attempt += 1) {
|
|
880
971
|
let response: Response | null = null;
|
|
881
972
|
let parsed: Record<string, unknown> | null = null;
|
|
882
973
|
const abortController = new AbortController();
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
974
|
+
const previousAttemptUnreachable =
|
|
975
|
+
budget.unreachableAttemptStartedAt !== null;
|
|
976
|
+
const attemptStartedAt = startRuntimeApiAttempt(budget);
|
|
977
|
+
const operationRemainingMs =
|
|
978
|
+
runtimeApiRetryDeadlineAt(
|
|
979
|
+
budget,
|
|
980
|
+
previousAttemptUnreachable ? 'no_response' : 'response',
|
|
981
|
+
) - attemptStartedAt;
|
|
887
982
|
if (operationRemainingMs <= 0) {
|
|
888
|
-
throw
|
|
983
|
+
throw previousAttemptUnreachable && unreachableError
|
|
984
|
+
? unreachableError
|
|
985
|
+
: runtimeApiTotalDeadlineError(body.action);
|
|
889
986
|
}
|
|
890
987
|
const timeout = setTimeout(
|
|
891
988
|
() => abortController.abort(),
|
|
@@ -895,7 +992,9 @@ async function postRuntimeApi<TResponse>(
|
|
|
895
992
|
response = await fetch(url, {
|
|
896
993
|
method: 'POST',
|
|
897
994
|
headers: resolveRuntimeApiHeaders(context, vercelHeaders),
|
|
898
|
-
body:
|
|
995
|
+
body:
|
|
996
|
+
preparedBodyJson ??
|
|
997
|
+
JSON.stringify(prepareRetainedPlayPipelinesForTransport(requestBody)),
|
|
899
998
|
signal: context.abortSignal
|
|
900
999
|
? AbortSignal.any([context.abortSignal, abortController.signal])
|
|
901
1000
|
: abortController.signal,
|
|
@@ -929,28 +1028,39 @@ async function postRuntimeApi<TResponse>(
|
|
|
929
1028
|
if (context.abortSignal?.aborted) {
|
|
930
1029
|
throw context.abortSignal.reason ?? error;
|
|
931
1030
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1031
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1032
|
+
if (response) {
|
|
1033
|
+
// A status line arrived and then its body failed or stalled. The
|
|
1034
|
+
// gateway answered, so this spends the ordinary attempt ladder; only a
|
|
1035
|
+
// request that got no response at all retries through a network cut.
|
|
1036
|
+
const bodyError = new Error(
|
|
1037
|
+
`Runtime API request to ${url} failed while reading the response body (status ${response.status}): ${message}`,
|
|
1038
|
+
);
|
|
1039
|
+
budget.respondedAttempts += 1;
|
|
1040
|
+
if (budget.respondedAttempts >= maxAttempts) throw bodyError;
|
|
938
1041
|
await sleepRuntimeApiRetry({
|
|
939
1042
|
action: body.action,
|
|
940
1043
|
attempt,
|
|
941
|
-
|
|
1044
|
+
deadlineAt: runtimeApiRetryDeadlineAt(budget, 'response'),
|
|
1045
|
+
deadlineError: bodyError,
|
|
942
1046
|
abortSignal: context.abortSignal ?? undefined,
|
|
943
1047
|
});
|
|
944
1048
|
continue;
|
|
945
1049
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
: 'before receiving a response'
|
|
952
|
-
}: ${message}`,
|
|
1050
|
+
// No response arrived: DNS, connect, reset, or the request timeout.
|
|
1051
|
+
// Keep trying through a sandbox network cut.
|
|
1052
|
+
budget.unreachableAttemptStartedAt = attemptStartedAt;
|
|
1053
|
+
unreachableError = new Error(
|
|
1054
|
+
`Runtime API request to ${url} failed before receiving a response: ${message}`,
|
|
953
1055
|
);
|
|
1056
|
+
await sleepRuntimeApiRetry({
|
|
1057
|
+
action: body.action,
|
|
1058
|
+
attempt,
|
|
1059
|
+
deadlineAt: runtimeApiRetryDeadlineAt(budget, 'no_response'),
|
|
1060
|
+
deadlineError: unreachableError,
|
|
1061
|
+
abortSignal: context.abortSignal ?? undefined,
|
|
1062
|
+
});
|
|
1063
|
+
continue;
|
|
954
1064
|
} finally {
|
|
955
1065
|
clearTimeout(timeout);
|
|
956
1066
|
}
|
|
@@ -963,11 +1073,13 @@ async function postRuntimeApi<TResponse>(
|
|
|
963
1073
|
// response body. Treat that as a transport failure, not a typed success:
|
|
964
1074
|
// every runtime action returns a JSON object and runtime writes are
|
|
965
1075
|
// already retry-safe across the equivalent fetch-failed boundary.
|
|
966
|
-
|
|
1076
|
+
budget.respondedAttempts += 1;
|
|
1077
|
+
if (budget.respondedAttempts < maxAttempts) {
|
|
967
1078
|
await sleepRuntimeApiRetry({
|
|
968
1079
|
action: body.action,
|
|
969
1080
|
attempt,
|
|
970
|
-
|
|
1081
|
+
deadlineAt: runtimeApiRetryDeadlineAt(budget, 'response'),
|
|
1082
|
+
deadlineError: runtimeApiTotalDeadlineError(body.action),
|
|
971
1083
|
abortSignal: context.abortSignal ?? undefined,
|
|
972
1084
|
});
|
|
973
1085
|
continue;
|
|
@@ -1034,12 +1146,14 @@ async function postRuntimeApi<TResponse>(
|
|
|
1034
1146
|
isTransientRuntimeApiErrorMessage(
|
|
1035
1147
|
`${typeof parsed?.error === 'string' ? parsed.error : ''}\n${details ?? ''}`,
|
|
1036
1148
|
));
|
|
1037
|
-
|
|
1149
|
+
budget.respondedAttempts += 1;
|
|
1150
|
+
if (shouldRetryRuntimeResponse && budget.respondedAttempts < maxAttempts) {
|
|
1038
1151
|
await sleepRuntimeApiRetry({
|
|
1039
1152
|
action: body.action,
|
|
1040
1153
|
attempt,
|
|
1041
1154
|
retryAfterMs,
|
|
1042
|
-
|
|
1155
|
+
deadlineAt: runtimeApiRetryDeadlineAt(budget, 'response'),
|
|
1156
|
+
deadlineError: runtimeApiTotalDeadlineError(body.action),
|
|
1043
1157
|
abortSignal: context.abortSignal ?? undefined,
|
|
1044
1158
|
});
|
|
1045
1159
|
continue;
|
|
@@ -1072,14 +1186,28 @@ async function postRuntimeApi<TResponse>(
|
|
|
1072
1186
|
new Error(`${errorMessage} (${diagnosticContext}).`),
|
|
1073
1187
|
);
|
|
1074
1188
|
}
|
|
1189
|
+
if (
|
|
1190
|
+
(body.action === 'runtime_array_input_get_manifest' ||
|
|
1191
|
+
body.action === 'runtime_array_input_begin') &&
|
|
1192
|
+
(((response.status === 403 || response.status === 409) &&
|
|
1193
|
+
errorCode === 'RUNTIME_ARRAY_INPUT_UNAVAILABLE' &&
|
|
1194
|
+
(parsed?.action === undefined || parsed.action === body.action)) ||
|
|
1195
|
+
(response.status === 400 &&
|
|
1196
|
+
errorMessage === 'Unsupported receipt gateway action.' &&
|
|
1197
|
+
parsed?.action === body.action) ||
|
|
1198
|
+
((response.status === 400 || response.status === 403) &&
|
|
1199
|
+
(errorMessage === 'Unsupported runtime action capability scope.' ||
|
|
1200
|
+
errorMessage === 'Unsupported runtime data-plane action.' ||
|
|
1201
|
+
errorMessage === `Unsupported action: ${body.action}`)))
|
|
1202
|
+
) {
|
|
1203
|
+
throw new RuntimeArrayInputStoreUnavailableError();
|
|
1204
|
+
}
|
|
1075
1205
|
throw new Error(
|
|
1076
1206
|
details
|
|
1077
1207
|
? `${errorMessage} (${diagnosticContext}): ${details}`
|
|
1078
1208
|
: `${errorMessage} (${diagnosticContext}).`,
|
|
1079
1209
|
);
|
|
1080
1210
|
}
|
|
1081
|
-
|
|
1082
|
-
throw new Error('Runtime API request failed after retries.');
|
|
1083
1211
|
}
|
|
1084
1212
|
|
|
1085
1213
|
function runtimeApiRetryDelayMs(
|
|
@@ -1087,8 +1215,11 @@ function runtimeApiRetryDelayMs(
|
|
|
1087
1215
|
attempt: number,
|
|
1088
1216
|
retryAfterMs?: number,
|
|
1089
1217
|
): number {
|
|
1218
|
+
// An operation runs past the ladder only after attempts that got no
|
|
1219
|
+
// response; it then waits the ladder's last step.
|
|
1220
|
+
const delays = runtimeApiRetryDelaysForAction(action);
|
|
1090
1221
|
const configuredDelay =
|
|
1091
|
-
|
|
1222
|
+
delays[Math.min(attempt, delays.length) - 1] ??
|
|
1092
1223
|
RUNTIME_API_DEFAULT_RETRY_AFTER_MS;
|
|
1093
1224
|
return retryAfterMs === undefined
|
|
1094
1225
|
? configuredDelay
|
|
@@ -1107,15 +1238,55 @@ function runtimeApiRequestTimeoutMs(
|
|
|
1107
1238
|
: RUNTIME_API_REQUEST_TIMEOUT_MS;
|
|
1108
1239
|
}
|
|
1109
1240
|
|
|
1110
|
-
|
|
1241
|
+
/**
|
|
1242
|
+
* Retry bookkeeping for one runtime API operation. Attempts that got a
|
|
1243
|
+
* response spend the action's attempt ladder and, for map-row completion, its
|
|
1244
|
+
* total deadline. An attempt that got no response at all retries until
|
|
1245
|
+
* RUNNER_GATEWAY_CONNECTION_RETRY_BUDGET_MS after the operation began, and the
|
|
1246
|
+
* time spent unreachable is not charged to the response budget.
|
|
1247
|
+
*/
|
|
1248
|
+
type RuntimeApiRetryBudget = {
|
|
1249
|
+
action: RuntimeApiActionRequest['action'];
|
|
1250
|
+
startedAt: number;
|
|
1251
|
+
respondedAttempts: number;
|
|
1252
|
+
unreachableMs: number;
|
|
1253
|
+
/** Start of the latest attempt, while that attempt got no response. */
|
|
1254
|
+
unreachableAttemptStartedAt: number | null;
|
|
1255
|
+
};
|
|
1256
|
+
|
|
1257
|
+
function startRuntimeApiRetryBudget(
|
|
1111
1258
|
action: RuntimeApiActionRequest['action'],
|
|
1112
|
-
|
|
1259
|
+
): RuntimeApiRetryBudget {
|
|
1260
|
+
return {
|
|
1261
|
+
action,
|
|
1262
|
+
startedAt: Date.now(),
|
|
1263
|
+
respondedAttempts: 0,
|
|
1264
|
+
unreachableMs: 0,
|
|
1265
|
+
unreachableAttemptStartedAt: null,
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/** Charges the previous attempt's time to the outage if it got no response. */
|
|
1270
|
+
function startRuntimeApiAttempt(budget: RuntimeApiRetryBudget): number {
|
|
1271
|
+
const now = Date.now();
|
|
1272
|
+
if (budget.unreachableAttemptStartedAt !== null) {
|
|
1273
|
+
budget.unreachableMs += now - budget.unreachableAttemptStartedAt;
|
|
1274
|
+
budget.unreachableAttemptStartedAt = null;
|
|
1275
|
+
}
|
|
1276
|
+
return now;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
function runtimeApiRetryDeadlineAt(
|
|
1280
|
+
budget: RuntimeApiRetryBudget,
|
|
1281
|
+
failure: 'response' | 'no_response',
|
|
1113
1282
|
): number {
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1283
|
+
if (failure === 'no_response') {
|
|
1284
|
+
return budget.startedAt + RUNNER_GATEWAY_CONNECTION_RETRY_BUDGET_MS;
|
|
1285
|
+
}
|
|
1286
|
+
return budget.action === 'runtime_sheet_complete_map_rows'
|
|
1287
|
+
? budget.startedAt +
|
|
1288
|
+
budget.unreachableMs +
|
|
1289
|
+
RUNTIME_SHEET_COMPLETION_TOTAL_TIMEOUT_MS
|
|
1119
1290
|
: Number.POSITIVE_INFINITY;
|
|
1120
1291
|
}
|
|
1121
1292
|
|
|
@@ -1132,7 +1303,8 @@ async function sleepRuntimeApiRetry(input: {
|
|
|
1132
1303
|
action: RuntimeApiActionRequest['action'];
|
|
1133
1304
|
attempt: number;
|
|
1134
1305
|
retryAfterMs?: number;
|
|
1135
|
-
|
|
1306
|
+
deadlineAt: number;
|
|
1307
|
+
deadlineError: Error;
|
|
1136
1308
|
abortSignal?: AbortSignal;
|
|
1137
1309
|
}): Promise<void> {
|
|
1138
1310
|
const requestedDelayMs = runtimeApiRetryDelayMs(
|
|
@@ -1140,12 +1312,9 @@ async function sleepRuntimeApiRetry(input: {
|
|
|
1140
1312
|
input.attempt,
|
|
1141
1313
|
input.retryAfterMs,
|
|
1142
1314
|
);
|
|
1143
|
-
const operationRemainingMs =
|
|
1144
|
-
input.action,
|
|
1145
|
-
input.operationStartedAt,
|
|
1146
|
-
);
|
|
1315
|
+
const operationRemainingMs = input.deadlineAt - Date.now();
|
|
1147
1316
|
if (operationRemainingMs <= 0) {
|
|
1148
|
-
throw
|
|
1317
|
+
throw input.deadlineError;
|
|
1149
1318
|
}
|
|
1150
1319
|
const delayMs = Math.min(requestedDelayMs, operationRemainingMs);
|
|
1151
1320
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -1164,7 +1333,7 @@ async function sleepRuntimeApiRetry(input: {
|
|
|
1164
1333
|
input.abortSignal?.addEventListener('abort', onAbort, { once: true });
|
|
1165
1334
|
});
|
|
1166
1335
|
if (requestedDelayMs >= operationRemainingMs) {
|
|
1167
|
-
throw
|
|
1336
|
+
throw input.deadlineError;
|
|
1168
1337
|
}
|
|
1169
1338
|
}
|
|
1170
1339
|
|
|
@@ -2119,11 +2288,14 @@ async function connectRuntimePostgresPool(
|
|
|
2119
2288
|
lane: RuntimePostgresLane,
|
|
2120
2289
|
postgresUrl: string,
|
|
2121
2290
|
signal?: AbortSignal | null,
|
|
2291
|
+
completionDiagnostics?: RuntimeMapCompletionDiagnostics,
|
|
2122
2292
|
): Promise<RuntimePoolClient> {
|
|
2123
2293
|
const admissionStartedAt = Date.now();
|
|
2124
|
-
const releaseAdmission = await
|
|
2125
|
-
|
|
2126
|
-
|
|
2294
|
+
const releaseAdmission = await measureRuntimeSheetPhase(
|
|
2295
|
+
completionDiagnostics,
|
|
2296
|
+
'tenantAdmission',
|
|
2297
|
+
() => admission.acquire(lane, { signal: signal ?? undefined }),
|
|
2298
|
+
);
|
|
2127
2299
|
const admissionWaitMs = Math.max(0, Date.now() - admissionStartedAt);
|
|
2128
2300
|
if (admissionWaitMs > 0) {
|
|
2129
2301
|
const telemetry = admission.snapshot()[lane];
|
|
@@ -2142,6 +2314,8 @@ async function connectRuntimePostgresPool(
|
|
|
2142
2314
|
}
|
|
2143
2315
|
let timedOut = false;
|
|
2144
2316
|
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
2317
|
+
const physicalConnectStartedAt = Date.now();
|
|
2318
|
+
let physicalConnectFailed = false;
|
|
2145
2319
|
const connectPromise = pool.connect();
|
|
2146
2320
|
connectPromise
|
|
2147
2321
|
.then((client) => {
|
|
@@ -2185,11 +2359,19 @@ async function connectRuntimePostgresPool(
|
|
|
2185
2359
|
},
|
|
2186
2360
|
};
|
|
2187
2361
|
} catch (error) {
|
|
2362
|
+
physicalConnectFailed = true;
|
|
2188
2363
|
if (!timedOut) {
|
|
2189
2364
|
releaseAdmission();
|
|
2190
2365
|
}
|
|
2191
2366
|
throw error;
|
|
2192
2367
|
} finally {
|
|
2368
|
+
if (completionDiagnostics) {
|
|
2369
|
+
completionDiagnostics.physicalConnectMs +=
|
|
2370
|
+
Date.now() - physicalConnectStartedAt;
|
|
2371
|
+
completionDiagnostics.physicalConnectFailures += Number(
|
|
2372
|
+
physicalConnectFailed,
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2193
2375
|
if (timeout) {
|
|
2194
2376
|
clearTimeout(timeout);
|
|
2195
2377
|
}
|
|
@@ -2386,6 +2568,27 @@ async function awaitRuntimePostgresOperation<T>(input: {
|
|
|
2386
2568
|
});
|
|
2387
2569
|
}
|
|
2388
2570
|
|
|
2571
|
+
async function rollbackRuntimePostgresTransaction(
|
|
2572
|
+
client: RuntimePoolClient,
|
|
2573
|
+
): Promise<void> {
|
|
2574
|
+
await awaitRuntimePostgresOperation({
|
|
2575
|
+
client,
|
|
2576
|
+
deadlineAt: Date.now() + RUNTIME_POSTGRES_ROLLBACK_CLEANUP_TIMEOUT_MS,
|
|
2577
|
+
operation: client.query('ROLLBACK'),
|
|
2578
|
+
});
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2581
|
+
class RuntimeSheetTransactionRollbackError extends AggregateError {
|
|
2582
|
+
constructor(operationError: unknown, rollbackError: unknown) {
|
|
2583
|
+
super(
|
|
2584
|
+
[operationError, rollbackError],
|
|
2585
|
+
'Runtime Sheet transaction rollback failed; transaction state is unknown',
|
|
2586
|
+
{ cause: operationError },
|
|
2587
|
+
);
|
|
2588
|
+
this.name = 'RuntimeSheetTransactionRollbackError';
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2389
2592
|
async function withRuntimePostgres<T>(
|
|
2390
2593
|
session: RuntimePostgresSession,
|
|
2391
2594
|
fn: (
|
|
@@ -2404,6 +2607,7 @@ async function withRuntimePostgres<T>(
|
|
|
2404
2607
|
let requestLocalPool: RuntimePool | null = null;
|
|
2405
2608
|
let poolLease: RuntimePostgresPoolLease | null = null;
|
|
2406
2609
|
let roleTransactionStarted = false;
|
|
2610
|
+
let discardClient = false;
|
|
2407
2611
|
let operationDeadlineAt: number | null = null;
|
|
2408
2612
|
const cachePool =
|
|
2409
2613
|
(options.cachePool ?? true) && canReuseRuntimePostgresPoolsAcrossRequests();
|
|
@@ -2430,6 +2634,7 @@ async function withRuntimePostgres<T>(
|
|
|
2430
2634
|
lane,
|
|
2431
2635
|
session.postgresUrl,
|
|
2432
2636
|
options.signal,
|
|
2637
|
+
options.completionDiagnostics,
|
|
2433
2638
|
),
|
|
2434
2639
|
);
|
|
2435
2640
|
operationDeadlineAt =
|
|
@@ -2461,17 +2666,22 @@ async function withRuntimePostgres<T>(
|
|
|
2461
2666
|
}
|
|
2462
2667
|
break;
|
|
2463
2668
|
} catch (error) {
|
|
2669
|
+
let failure = error;
|
|
2464
2670
|
if (client) {
|
|
2465
2671
|
if (roleTransactionStarted) {
|
|
2466
|
-
|
|
2467
|
-
client
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2672
|
+
try {
|
|
2673
|
+
await rollbackRuntimePostgresTransaction(client);
|
|
2674
|
+
} catch (rollbackError) {
|
|
2675
|
+
discardClient = true;
|
|
2676
|
+
failure = new RuntimeSheetTransactionRollbackError(
|
|
2677
|
+
error,
|
|
2678
|
+
rollbackError,
|
|
2679
|
+
);
|
|
2680
|
+
} finally {
|
|
2681
|
+
roleTransactionStarted = false;
|
|
2682
|
+
}
|
|
2473
2683
|
}
|
|
2474
|
-
client.release();
|
|
2684
|
+
client.release(discardClient);
|
|
2475
2685
|
client = null;
|
|
2476
2686
|
}
|
|
2477
2687
|
// Queue overflow and caller cancellation happen before pool.connect().
|
|
@@ -2491,9 +2701,10 @@ async function withRuntimePostgres<T>(
|
|
|
2491
2701
|
poolLease = null;
|
|
2492
2702
|
if (
|
|
2493
2703
|
attempt >= maxConnectAttempts ||
|
|
2494
|
-
|
|
2704
|
+
failure instanceof RuntimeSheetTransactionRollbackError ||
|
|
2705
|
+
!isTransientRuntimePostgresConnectionError(failure)
|
|
2495
2706
|
) {
|
|
2496
|
-
throw
|
|
2707
|
+
throw failure;
|
|
2497
2708
|
}
|
|
2498
2709
|
await sleep(
|
|
2499
2710
|
RUNTIME_POSTGRES_CONNECT_RETRY_DELAYS_MS[attempt - 1] ??
|
|
@@ -2530,18 +2741,22 @@ async function withRuntimePostgres<T>(
|
|
|
2530
2741
|
}
|
|
2531
2742
|
return result;
|
|
2532
2743
|
} catch (error) {
|
|
2744
|
+
if (error instanceof RuntimeSheetTransactionRollbackError) {
|
|
2745
|
+
discardClient = true;
|
|
2746
|
+
}
|
|
2533
2747
|
if (roleTransactionStarted) {
|
|
2534
|
-
|
|
2535
|
-
client
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2748
|
+
try {
|
|
2749
|
+
await rollbackRuntimePostgresTransaction(client);
|
|
2750
|
+
} catch (rollbackError) {
|
|
2751
|
+
discardClient = true;
|
|
2752
|
+
throw new RuntimeSheetTransactionRollbackError(error, rollbackError);
|
|
2753
|
+
} finally {
|
|
2754
|
+
roleTransactionStarted = false;
|
|
2755
|
+
}
|
|
2541
2756
|
}
|
|
2542
2757
|
throw error;
|
|
2543
2758
|
} finally {
|
|
2544
|
-
client.release();
|
|
2759
|
+
client.release(discardClient);
|
|
2545
2760
|
await poolLease?.release();
|
|
2546
2761
|
if (requestLocalPool) {
|
|
2547
2762
|
await Promise.resolve(requestLocalPool.end()).catch(() => {});
|
|
@@ -2673,6 +2888,7 @@ async function withRuntimeSheetProvisioningRetry<T>(
|
|
|
2673
2888
|
retried: false,
|
|
2674
2889
|
error: error instanceof Error ? error.message : String(error),
|
|
2675
2890
|
});
|
|
2891
|
+
if (error instanceof RuntimeSheetTransactionRollbackError) throw error;
|
|
2676
2892
|
if (isMissingRelationError(error)) {
|
|
2677
2893
|
const ensureStartedAt = Date.now();
|
|
2678
2894
|
await ensureRuntimeSheet(context, {
|
|
@@ -2741,77 +2957,109 @@ async function withRuntimeSheetQueryClient<T>(
|
|
|
2741
2957
|
statementTimeoutMs?: number;
|
|
2742
2958
|
timings?: RuntimeSheetTiming[];
|
|
2743
2959
|
completionDiagnostics?: RuntimeMapCompletionDiagnostics;
|
|
2960
|
+
concurrentColumnPreparation?: boolean;
|
|
2744
2961
|
},
|
|
2745
|
-
operation: (
|
|
2962
|
+
operation: (
|
|
2963
|
+
client: RuntimeQueryClient,
|
|
2964
|
+
columnLockMode: 'shared' | 'exclusive',
|
|
2965
|
+
) => Promise<T>,
|
|
2746
2966
|
): Promise<T> {
|
|
2747
2967
|
const resultStartedAt = Date.now();
|
|
2968
|
+
let columnLockMode: 'shared' | 'exclusive' = input.concurrentColumnPreparation
|
|
2969
|
+
? 'shared'
|
|
2970
|
+
: 'exclusive';
|
|
2748
2971
|
const resultPromise = withRuntimeSheetProvisioningRetry(
|
|
2749
2972
|
context,
|
|
2750
2973
|
{ ...input, session },
|
|
2751
2974
|
async () => {
|
|
2752
2975
|
if (
|
|
2753
2976
|
!input.transactional &&
|
|
2977
|
+
!input.concurrentColumnPreparation &&
|
|
2754
2978
|
input.statementTimeoutMs === undefined &&
|
|
2755
2979
|
isRuntimeOneShotQueryFactoryRegistered()
|
|
2756
2980
|
) {
|
|
2757
2981
|
const operationStartedAt = Date.now();
|
|
2758
|
-
return await withRuntimeOneShotPostgres(session,
|
|
2759
|
-
()
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
}
|
|
2765
|
-
);
|
|
2982
|
+
return await withRuntimeOneShotPostgres(session, (client) =>
|
|
2983
|
+
operation(client, 'exclusive'),
|
|
2984
|
+
).finally(() => {
|
|
2985
|
+
input.timings?.push({
|
|
2986
|
+
phase: 'one_shot_operation',
|
|
2987
|
+
ms: Date.now() - operationStartedAt,
|
|
2988
|
+
});
|
|
2989
|
+
});
|
|
2766
2990
|
}
|
|
2767
2991
|
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
'transactionBegin',
|
|
2778
|
-
() => client.query('BEGIN'),
|
|
2779
|
-
);
|
|
2780
|
-
}
|
|
2781
|
-
try {
|
|
2782
|
-
if (input.statementTimeoutMs !== undefined) {
|
|
2783
|
-
await client.query(
|
|
2784
|
-
`SET LOCAL statement_timeout TO ${Math.max(1, Math.floor(input.statementTimeoutMs))}`,
|
|
2785
|
-
);
|
|
2786
|
-
}
|
|
2787
|
-
const result = await operation(client);
|
|
2992
|
+
const runTransaction = () =>
|
|
2993
|
+
withRuntimePostgres(
|
|
2994
|
+
session,
|
|
2995
|
+
async (client, transaction) => {
|
|
2996
|
+
const managesTransaction =
|
|
2997
|
+
(input.concurrentColumnPreparation ||
|
|
2998
|
+
input.transactional ||
|
|
2999
|
+
input.statementTimeoutMs !== undefined) &&
|
|
3000
|
+
!transaction.active;
|
|
2788
3001
|
if (managesTransaction) {
|
|
2789
3002
|
await measureRuntimeSheetPhase(
|
|
2790
3003
|
input.completionDiagnostics,
|
|
2791
|
-
'
|
|
2792
|
-
() => client.query('
|
|
3004
|
+
'transactionBegin',
|
|
3005
|
+
() => client.query('BEGIN'),
|
|
2793
3006
|
);
|
|
2794
3007
|
}
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
3008
|
+
try {
|
|
3009
|
+
if (input.statementTimeoutMs !== undefined) {
|
|
3010
|
+
await client.query(
|
|
3011
|
+
`SET LOCAL statement_timeout TO ${Math.max(1, Math.floor(input.statementTimeoutMs))}`,
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
const result = await operation(client, columnLockMode);
|
|
3015
|
+
if (managesTransaction) {
|
|
3016
|
+
await measureRuntimeSheetPhase(
|
|
3017
|
+
input.completionDiagnostics,
|
|
3018
|
+
'transactionCommit',
|
|
3019
|
+
() => client.query('COMMIT'),
|
|
3020
|
+
);
|
|
3021
|
+
}
|
|
3022
|
+
return result;
|
|
3023
|
+
} catch (error) {
|
|
3024
|
+
if (managesTransaction) {
|
|
3025
|
+
try {
|
|
3026
|
+
await rollbackRuntimePostgresTransaction(client);
|
|
3027
|
+
} catch (rollbackError) {
|
|
3028
|
+
throw new RuntimeSheetTransactionRollbackError(
|
|
3029
|
+
error,
|
|
3030
|
+
rollbackError,
|
|
3031
|
+
);
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
throw error;
|
|
2799
3035
|
}
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
3036
|
+
},
|
|
3037
|
+
{
|
|
3038
|
+
cachePool: !context.disablePostgresPoolCache,
|
|
3039
|
+
maxConnectAttempts:
|
|
3040
|
+
input.statementTimeoutMs === undefined ? undefined : 1,
|
|
3041
|
+
operationTimeoutMs:
|
|
3042
|
+
input.statementTimeoutMs === undefined
|
|
3043
|
+
? undefined
|
|
3044
|
+
: RUNTIME_SHEET_COMPLETION_SERVER_OPERATION_TIMEOUT_MS,
|
|
3045
|
+
signal: context.abortSignal,
|
|
3046
|
+
completionDiagnostics: input.completionDiagnostics,
|
|
3047
|
+
},
|
|
3048
|
+
);
|
|
3049
|
+
try {
|
|
3050
|
+
return await runTransaction();
|
|
3051
|
+
} catch (error) {
|
|
3052
|
+
if (
|
|
3053
|
+
columnLockMode !== 'shared' ||
|
|
3054
|
+
!(error instanceof RuntimeSheetColumnSchemaChangeRequired)
|
|
3055
|
+
)
|
|
3056
|
+
throw error;
|
|
3057
|
+
// The entire role/row transaction has confirmed rollback. Re-read
|
|
3058
|
+
// accepted rows, intent and schema under the existing exclusive fence;
|
|
3059
|
+
// never upgrade a shared lock or retain a pre-rollback SQL snapshot.
|
|
3060
|
+
columnLockMode = 'exclusive';
|
|
3061
|
+
return await runTransaction();
|
|
3062
|
+
}
|
|
2815
3063
|
},
|
|
2816
3064
|
);
|
|
2817
3065
|
if (input.completionDiagnostics) {
|
|
@@ -3628,19 +3876,32 @@ async function readRuntimeRows(
|
|
|
3628
3876
|
);
|
|
3629
3877
|
}
|
|
3630
3878
|
const { rows } = await client.query(
|
|
3879
|
+
// Select the preferred status once, using early-exit witnesses. A
|
|
3880
|
+
// window aggregate forces every fresh run row through WindowAgg even
|
|
3881
|
+
// for the first small page. Keep the same statement snapshot, status
|
|
3882
|
+
// preference, freshness predicate and public offset semantics.
|
|
3631
3883
|
`WITH scoped AS (
|
|
3632
|
-
SELECT
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3884
|
+
SELECT CASE
|
|
3885
|
+
WHEN EXISTS (
|
|
3886
|
+
SELECT 1 FROM ${sheetTable(session)}
|
|
3887
|
+
WHERE _run_id = $1::text
|
|
3888
|
+
AND ${runtimeSheetResultFreshnessSql('_cell_meta', '$1::text')}
|
|
3889
|
+
AND _status = 'enriched'
|
|
3890
|
+
) THEN 'enriched'
|
|
3891
|
+
WHEN EXISTS (
|
|
3892
|
+
SELECT 1 FROM ${sheetTable(session)}
|
|
3893
|
+
WHERE _run_id = $1::text
|
|
3894
|
+
AND ${runtimeSheetResultFreshnessSql('_cell_meta', '$1::text')}
|
|
3895
|
+
AND _status = 'failed'
|
|
3896
|
+
) THEN 'failed'
|
|
3897
|
+
END AS preferred_status
|
|
3638
3898
|
)
|
|
3639
3899
|
SELECT *
|
|
3640
|
-
FROM
|
|
3641
|
-
WHERE
|
|
3642
|
-
|
|
3643
|
-
|
|
3900
|
+
FROM ${sheetTable(session)}
|
|
3901
|
+
WHERE _run_id = $1::text
|
|
3902
|
+
AND ${runtimeSheetResultFreshnessSql('_cell_meta', '$1::text')}
|
|
3903
|
+
AND ((SELECT preferred_status FROM scoped) IS NULL
|
|
3904
|
+
OR _status = (SELECT preferred_status FROM scoped))
|
|
3644
3905
|
ORDER BY _input_index ASC NULLS LAST, _created_at ASC, _key ASC
|
|
3645
3906
|
LIMIT $2 OFFSET $3`,
|
|
3646
3907
|
[input.runId, input.limit, input.offset],
|
|
@@ -4104,15 +4365,29 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
4104
4365
|
);
|
|
4105
4366
|
const { rows } = await client.query(
|
|
4106
4367
|
`
|
|
4107
|
-
WITH input_rows AS (
|
|
4108
|
-
SELECT DISTINCT ON (
|
|
4109
|
-
|
|
4110
|
-
FROM unnest($1::text[]
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4368
|
+
WITH input_rows AS MATERIALIZED (
|
|
4369
|
+
SELECT DISTINCT ON (input_values._key)
|
|
4370
|
+
input_values._key, input_values._input_index, input_values.ord
|
|
4371
|
+
FROM unnest($1::text[], $3::bigint[])
|
|
4372
|
+
WITH ORDINALITY AS input_values(_key, _input_index, ord)
|
|
4373
|
+
WHERE input_values.ord <= LEAST(
|
|
4374
|
+
COALESCE(cardinality($1::text[]), 0),
|
|
4375
|
+
COALESCE(cardinality($2::jsonb[]), 0),
|
|
4376
|
+
COALESCE(cardinality($3::bigint[]), 0)
|
|
4377
|
+
)
|
|
4378
|
+
ORDER BY input_values._key, input_values.ord
|
|
4379
|
+
),
|
|
4380
|
+
input_payloads AS MATERIALIZED (
|
|
4381
|
+
SELECT payload_values.payload, payload_values.ord
|
|
4382
|
+
FROM unnest($2::jsonb[]) WITH ORDINALITY AS payload_values(payload, ord)
|
|
4383
|
+
),
|
|
4384
|
+
physical_input_rows AS NOT MATERIALIZED (
|
|
4385
|
+
-- Repeated admission/fence scans retain only narrow identities.
|
|
4386
|
+
-- Keep one unsorted payload spool and join its original ordinal only
|
|
4387
|
+
-- in physical write paths; do not materialize this wide join again.
|
|
4388
|
+
SELECT input_rows._key, input_rows._input_index, input_payloads.payload
|
|
4389
|
+
FROM input_rows
|
|
4390
|
+
JOIN input_payloads ON input_payloads.ord = input_rows.ord
|
|
4116
4391
|
),
|
|
4117
4392
|
preexisting_rows AS (
|
|
4118
4393
|
-- Force is new intent across runs, not across scheduler attempts of
|
|
@@ -4134,7 +4409,7 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
4134
4409
|
_input_index = input_rows._input_index,
|
|
4135
4410
|
_updated_at = now(),
|
|
4136
4411
|
_version = ${nextRuntimeSheetVersionExpression(session)}${input.physicalRefreshSetSql}
|
|
4137
|
-
FROM input_rows
|
|
4412
|
+
FROM physical_input_rows AS input_rows
|
|
4138
4413
|
WHERE target._key = input_rows._key
|
|
4139
4414
|
AND target._status = 'enriched'
|
|
4140
4415
|
AND COALESCE(target._write_version, 0) < $12::bigint
|
|
@@ -4155,7 +4430,7 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
4155
4430
|
_attempt_seq = $10::integer,
|
|
4156
4431
|
_updated_at = now(),
|
|
4157
4432
|
_version = ${nextRuntimeSheetVersionExpression(session)}${input.physicalRefreshSetSql}
|
|
4158
|
-
FROM input_rows
|
|
4433
|
+
FROM physical_input_rows AS input_rows
|
|
4159
4434
|
WHERE target._key = input_rows._key
|
|
4160
4435
|
AND target._status = 'failed'
|
|
4161
4436
|
AND COALESCE(target._write_version, 0) < $12::bigint
|
|
@@ -4166,7 +4441,7 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
4166
4441
|
SET _input_index = input_rows._input_index,
|
|
4167
4442
|
_updated_at = now(),
|
|
4168
4443
|
_version = ${nextRuntimeSheetVersionExpression(session)}${input.physicalRefreshSetSql}
|
|
4169
|
-
FROM input_rows
|
|
4444
|
+
FROM physical_input_rows AS input_rows
|
|
4170
4445
|
WHERE target._key = input_rows._key
|
|
4171
4446
|
AND target._write_version = $12::bigint
|
|
4172
4447
|
AND target._status <> 'stale'
|
|
@@ -4200,7 +4475,7 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
4200
4475
|
inserted_rows AS (
|
|
4201
4476
|
INSERT INTO ${sheetTable(session)} (_key, _status, _run_id, _input_index, _attempt_id, _attempt_owner_run_id, _attempt_expires_at, _attempt_seq, _write_version, _writer_run_id, _created_at${input.physicalInsertColumnsSql})
|
|
4202
4477
|
SELECT input_rows._key, 'pending', $4::text, input_rows._input_index, $7::text, $8::text, $9::timestamptz, $10::integer, $12::bigint, $4::text, now()${input.physicalInsertValuesSql}
|
|
4203
|
-
FROM input_rows
|
|
4478
|
+
FROM physical_input_rows AS input_rows
|
|
4204
4479
|
LEFT JOIN ${sheetTable(session)} AS existing_input ON existing_input._key = input_rows._key
|
|
4205
4480
|
${input.physicalInsertJoinSql}
|
|
4206
4481
|
ON CONFLICT (_key) DO UPDATE SET
|
|
@@ -4251,7 +4526,7 @@ async function prepareRuntimeSheetDatasetRows(
|
|
|
4251
4526
|
_error = NULL,
|
|
4252
4527
|
_updated_at = now(),
|
|
4253
4528
|
_version = ${nextRuntimeSheetVersionExpression(session)}${input.physicalRefreshSetSql}
|
|
4254
|
-
FROM input_rows
|
|
4529
|
+
FROM physical_input_rows AS input_rows
|
|
4255
4530
|
WHERE target._key = input_rows._key
|
|
4256
4531
|
AND target._status IN ('pending', 'running')
|
|
4257
4532
|
AND COALESCE(target._write_version, 0) < $12::bigint
|
|
@@ -4965,7 +5240,9 @@ async function getRuntimeWorkReceiptSession(
|
|
|
4965
5240
|
playName: string;
|
|
4966
5241
|
key: string;
|
|
4967
5242
|
},
|
|
5243
|
+
access: 'read' | 'mutate' = 'mutate',
|
|
4968
5244
|
): Promise<RuntimePostgresSession> {
|
|
5245
|
+
if (access === 'mutate') assertWorkReceiptKeyMutable(input.key);
|
|
4969
5246
|
const playName = context.playName?.trim() || input.playName;
|
|
4970
5247
|
if (!playName) {
|
|
4971
5248
|
throw new Error('Runtime work receipts require a playName.');
|
|
@@ -5033,15 +5310,23 @@ async function getRuntimeWorkReceiptSessionForKeys(
|
|
|
5033
5310
|
playName: string;
|
|
5034
5311
|
keys: string[];
|
|
5035
5312
|
},
|
|
5313
|
+
access: 'read' | 'mutate' = 'mutate',
|
|
5036
5314
|
): Promise<RuntimePostgresSession> {
|
|
5315
|
+
if (access === 'mutate') {
|
|
5316
|
+
for (const key of input.keys) assertWorkReceiptKeyMutable(key);
|
|
5317
|
+
}
|
|
5037
5318
|
const firstKey = input.keys.find((key) => key.trim());
|
|
5038
5319
|
if (!firstKey) {
|
|
5039
5320
|
throw new Error('Runtime work receipt batch requires at least one key.');
|
|
5040
5321
|
}
|
|
5041
|
-
const session = await getRuntimeWorkReceiptSession(
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5322
|
+
const session = await getRuntimeWorkReceiptSession(
|
|
5323
|
+
context,
|
|
5324
|
+
{
|
|
5325
|
+
playName: input.playName,
|
|
5326
|
+
key: firstKey,
|
|
5327
|
+
},
|
|
5328
|
+
access,
|
|
5329
|
+
);
|
|
5045
5330
|
for (const key of input.keys) {
|
|
5046
5331
|
validateRuntimeWorkReceiptKeyScope(session, { key });
|
|
5047
5332
|
}
|
|
@@ -5073,6 +5358,121 @@ async function readRuntimeWorkReceipt(
|
|
|
5073
5358
|
return rows[0] ? mapRuntimeWorkReceiptRow(rows[0]) : null;
|
|
5074
5359
|
}
|
|
5075
5360
|
|
|
5361
|
+
async function withRuntimeArrayInputReceiptPort<T>(
|
|
5362
|
+
context: RuntimeApiContext,
|
|
5363
|
+
input: RuntimeArrayInputScope,
|
|
5364
|
+
operation: (port: RuntimeArrayInputReceiptPort) => Promise<T>,
|
|
5365
|
+
): Promise<T> {
|
|
5366
|
+
const orgId = context.orgId?.trim();
|
|
5367
|
+
if (!orgId || !input.playName?.trim() || !input.runId?.trim()) {
|
|
5368
|
+
throw new Error('Runtime array input requires exact org/play/run scope.');
|
|
5369
|
+
}
|
|
5370
|
+
if (
|
|
5371
|
+
(context.playName && context.playName !== input.playName) ||
|
|
5372
|
+
(context.runId && context.runId !== input.runId)
|
|
5373
|
+
) {
|
|
5374
|
+
throw new Error('Runtime array input is outside the execution scope.');
|
|
5375
|
+
}
|
|
5376
|
+
const session = await getRuntimeWorkReceiptSessionForRun(context, {
|
|
5377
|
+
playName: input.playName,
|
|
5378
|
+
});
|
|
5379
|
+
if (session.target.orgId !== orgId || session.playName !== input.playName) {
|
|
5380
|
+
throw new Error('Runtime array input session is outside execution scope.');
|
|
5381
|
+
}
|
|
5382
|
+
return await operation({
|
|
5383
|
+
orgId,
|
|
5384
|
+
playName: input.playName,
|
|
5385
|
+
runId: input.runId,
|
|
5386
|
+
table: workReceiptTable(session),
|
|
5387
|
+
outerTransaction:
|
|
5388
|
+
Boolean(session.executionRole) &&
|
|
5389
|
+
!isRuntimeOneShotQueryFactoryRegistered(),
|
|
5390
|
+
withClient: (callback) =>
|
|
5391
|
+
withRuntimeWorkReceiptClient(context, session, callback),
|
|
5392
|
+
});
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5395
|
+
export async function getRuntimeArrayInputManifest(
|
|
5396
|
+
context: RuntimeApiContext,
|
|
5397
|
+
input: RuntimeArrayInputScope,
|
|
5398
|
+
): Promise<RuntimeArrayInputManifest> {
|
|
5399
|
+
if (context.dbSessionStrategy === 'gateway_only') {
|
|
5400
|
+
return await postRuntimeApi<RuntimeArrayInputManifest>(context, {
|
|
5401
|
+
action: 'runtime_array_input_get_manifest',
|
|
5402
|
+
input,
|
|
5403
|
+
});
|
|
5404
|
+
}
|
|
5405
|
+
return await withRuntimeArrayInputReceiptPort(context, input, (port) =>
|
|
5406
|
+
getRuntimeArrayInputManifestReceipt(port, input),
|
|
5407
|
+
);
|
|
5408
|
+
}
|
|
5409
|
+
|
|
5410
|
+
export async function beginRuntimeArrayInput(
|
|
5411
|
+
context: RuntimeApiContext,
|
|
5412
|
+
input: RuntimeArrayInputBegin,
|
|
5413
|
+
): Promise<void> {
|
|
5414
|
+
if (context.dbSessionStrategy === 'gateway_only') {
|
|
5415
|
+
await postRuntimeApi(context, {
|
|
5416
|
+
action: 'runtime_array_input_begin',
|
|
5417
|
+
input,
|
|
5418
|
+
});
|
|
5419
|
+
return;
|
|
5420
|
+
}
|
|
5421
|
+
await withRuntimeArrayInputReceiptPort(context, input, (port) =>
|
|
5422
|
+
beginRuntimeArrayInputReceipt(port, input),
|
|
5423
|
+
);
|
|
5424
|
+
}
|
|
5425
|
+
|
|
5426
|
+
export async function putRuntimeArrayInputPage(
|
|
5427
|
+
context: RuntimeApiContext,
|
|
5428
|
+
input: RuntimeArrayInputPutPage,
|
|
5429
|
+
): Promise<void> {
|
|
5430
|
+
if (context.dbSessionStrategy === 'gateway_only') {
|
|
5431
|
+
await postRuntimeApi(context, {
|
|
5432
|
+
action: 'runtime_array_input_put_page',
|
|
5433
|
+
input,
|
|
5434
|
+
});
|
|
5435
|
+
return;
|
|
5436
|
+
}
|
|
5437
|
+
await withRuntimeArrayInputReceiptPort(context, input, (port) =>
|
|
5438
|
+
putRuntimeArrayInputPageReceipt(port, input),
|
|
5439
|
+
);
|
|
5440
|
+
}
|
|
5441
|
+
|
|
5442
|
+
export async function sealRuntimeArrayInput(
|
|
5443
|
+
context: RuntimeApiContext,
|
|
5444
|
+
input: RuntimeArrayInputSeal,
|
|
5445
|
+
): Promise<void> {
|
|
5446
|
+
if (context.dbSessionStrategy === 'gateway_only') {
|
|
5447
|
+
await postRuntimeApi(context, {
|
|
5448
|
+
action: 'runtime_array_input_seal',
|
|
5449
|
+
input,
|
|
5450
|
+
});
|
|
5451
|
+
return;
|
|
5452
|
+
}
|
|
5453
|
+
await withRuntimeArrayInputReceiptPort(context, input, (port) =>
|
|
5454
|
+
sealRuntimeArrayInputReceipt(port, input),
|
|
5455
|
+
);
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
export async function readRuntimeArrayInputPage(
|
|
5459
|
+
context: RuntimeApiContext,
|
|
5460
|
+
input: RuntimeArrayInputScope & { ordinal: number },
|
|
5461
|
+
): Promise<{ offset: number; rows: RuntimeArrayInputOccurrence[] }> {
|
|
5462
|
+
if (context.dbSessionStrategy === 'gateway_only') {
|
|
5463
|
+
return await postRuntimeApi<{
|
|
5464
|
+
offset: number;
|
|
5465
|
+
rows: RuntimeArrayInputOccurrence[];
|
|
5466
|
+
}>(context, {
|
|
5467
|
+
action: 'runtime_array_input_read_page',
|
|
5468
|
+
input,
|
|
5469
|
+
});
|
|
5470
|
+
}
|
|
5471
|
+
return await withRuntimeArrayInputReceiptPort(context, input, (port) =>
|
|
5472
|
+
readRuntimeArrayInputPageReceipt(port, input),
|
|
5473
|
+
);
|
|
5474
|
+
}
|
|
5475
|
+
|
|
5076
5476
|
export async function getRuntimeWorkReceipt(
|
|
5077
5477
|
context: RuntimeApiContext,
|
|
5078
5478
|
input: {
|
|
@@ -5080,10 +5480,14 @@ export async function getRuntimeWorkReceipt(
|
|
|
5080
5480
|
key: string;
|
|
5081
5481
|
},
|
|
5082
5482
|
): Promise<WorkReceipt | null> {
|
|
5083
|
-
const session = await getRuntimeWorkReceiptSession(
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5483
|
+
const session = await getRuntimeWorkReceiptSession(
|
|
5484
|
+
context,
|
|
5485
|
+
{
|
|
5486
|
+
playName: input.playName,
|
|
5487
|
+
key: input.key,
|
|
5488
|
+
},
|
|
5489
|
+
'read',
|
|
5490
|
+
);
|
|
5087
5491
|
return await withRuntimeWorkReceiptClient(context, session, async (client) =>
|
|
5088
5492
|
readRuntimeWorkReceipt(client, session, input.key),
|
|
5089
5493
|
);
|
|
@@ -5100,10 +5504,14 @@ export async function getRuntimeWorkReceipts(
|
|
|
5100
5504
|
...new Set(input.keys.map((key) => key.trim()).filter(Boolean)),
|
|
5101
5505
|
];
|
|
5102
5506
|
if (keys.length === 0) return [];
|
|
5103
|
-
const session = await getRuntimeWorkReceiptSessionForKeys(
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5507
|
+
const session = await getRuntimeWorkReceiptSessionForKeys(
|
|
5508
|
+
context,
|
|
5509
|
+
{
|
|
5510
|
+
playName: input.playName,
|
|
5511
|
+
keys,
|
|
5512
|
+
},
|
|
5513
|
+
'read',
|
|
5514
|
+
);
|
|
5107
5515
|
return await withRuntimeWorkReceiptClient(
|
|
5108
5516
|
context,
|
|
5109
5517
|
session,
|
|
@@ -6206,6 +6614,8 @@ export async function completeRuntimeWorkReceipts(
|
|
|
6206
6614
|
returnOutput?: boolean;
|
|
6207
6615
|
},
|
|
6208
6616
|
): Promise<Array<WorkReceipt | null>> {
|
|
6617
|
+
for (const receipt of input.receipts)
|
|
6618
|
+
assertWorkReceiptKeyMutable(receipt.key);
|
|
6209
6619
|
const receipts = input.receipts.filter((receipt) => receipt.key.trim());
|
|
6210
6620
|
if (receipts.length === 0) return [];
|
|
6211
6621
|
if (
|
|
@@ -6989,33 +7399,501 @@ export async function skipRuntimeWorkReceipt(
|
|
|
6989
7399
|
);
|
|
6990
7400
|
}
|
|
6991
7401
|
|
|
6992
|
-
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
7402
|
+
type RuntimeSheetDatasetStartInput = Parameters<
|
|
7403
|
+
typeof startRuntimeSheetDataset
|
|
7404
|
+
>[1];
|
|
7405
|
+
|
|
7406
|
+
/** Leave 50 KB of headroom below the 4.5 MB Vercel Function body ceiling. */
|
|
7407
|
+
const RUNTIME_SHEET_START_REQUEST_MAX_BYTES = 4_450_000;
|
|
7408
|
+
|
|
7409
|
+
function runtimeSheetStartIntentId(
|
|
7410
|
+
input: RuntimeSheetDatasetStartInput,
|
|
7411
|
+
): string {
|
|
7412
|
+
const inputOffset = Math.max(0, Math.floor(input.inputOffset ?? 0));
|
|
7413
|
+
const digest = createHash('sha256')
|
|
7414
|
+
.update(input.playName)
|
|
7415
|
+
.update('\0')
|
|
7416
|
+
.update(input.runId)
|
|
7417
|
+
.update('\0')
|
|
7418
|
+
.update(input.tableNamespace)
|
|
7419
|
+
.update('\0')
|
|
7420
|
+
.update(String(inputOffset))
|
|
7421
|
+
.update('\0');
|
|
7422
|
+
for (const row of input.rows) {
|
|
7423
|
+
const serializedRow = JSON.stringify(row);
|
|
7424
|
+
digest.update(serializedRow ?? 'undefined').update('\0');
|
|
7425
|
+
}
|
|
7426
|
+
const value = digest.digest('hex').slice(0, 32);
|
|
7427
|
+
return `sheet-attempt:${value}`;
|
|
7428
|
+
}
|
|
7429
|
+
|
|
7430
|
+
function runtimeSheetStartRequestBytes(input: {
|
|
7431
|
+
request: RuntimeApiActionRequest;
|
|
7432
|
+
limitBytes: number;
|
|
7433
|
+
}): { bytes: number; exceeded: boolean } {
|
|
7434
|
+
const body = prepareRetainedPlayPipelinesForTransport(input.request);
|
|
7435
|
+
const measurement = jsonByteLengthUpTo(body, input.limitBytes);
|
|
7436
|
+
return { bytes: measurement.bytes, exceeded: measurement.exceeded };
|
|
7437
|
+
}
|
|
7438
|
+
|
|
7439
|
+
async function startRuntimeSheetDatasetViaGateway(
|
|
7001
7440
|
context: RuntimeApiContext,
|
|
7002
|
-
input:
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
7011
|
-
|
|
7012
|
-
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7441
|
+
input: RuntimeSheetDatasetStartInput,
|
|
7442
|
+
): Promise<PrepareRuntimeSheetResult> {
|
|
7443
|
+
const request = (
|
|
7444
|
+
pageInput: RuntimeSheetDatasetStartInput,
|
|
7445
|
+
): RuntimeApiActionRequest => ({
|
|
7446
|
+
action:
|
|
7447
|
+
pageInput.stableWriteIntent === true
|
|
7448
|
+
? 'runtime_sheet_start_with_intent'
|
|
7449
|
+
: 'runtime_sheet_start',
|
|
7450
|
+
// Sheet admission consumes rows, not the full authored Play input. Exclude
|
|
7451
|
+
// that duplicate payload from both sizing and every actual page request.
|
|
7452
|
+
input: { ...pageInput, playInput: undefined, pendingRerunAdmission: true },
|
|
7453
|
+
});
|
|
7454
|
+
const completeRequest = request(input);
|
|
7455
|
+
if (
|
|
7456
|
+
!runtimeSheetStartRequestBytes({
|
|
7457
|
+
request: completeRequest,
|
|
7458
|
+
limitBytes: RUNTIME_SHEET_START_REQUEST_MAX_BYTES,
|
|
7459
|
+
}).exceeded
|
|
7460
|
+
) {
|
|
7461
|
+
return await postRuntimeApi<PrepareRuntimeSheetResult>(
|
|
7462
|
+
context,
|
|
7463
|
+
completeRequest,
|
|
7464
|
+
);
|
|
7465
|
+
}
|
|
7466
|
+
|
|
7467
|
+
const requestInput: RuntimeSheetDatasetStartInput = {
|
|
7468
|
+
...input,
|
|
7469
|
+
attemptId: input.attemptId?.trim() || runtimeSheetStartIntentId(input),
|
|
7470
|
+
pendingRerunAdmission: true,
|
|
7471
|
+
};
|
|
7472
|
+
// Keep every page under the app-function ingress ceiling while preserving
|
|
7473
|
+
// the stable write intent, rerun admission, and attempt fence.
|
|
7474
|
+
const inputOffset = Math.max(0, Math.floor(input.inputOffset ?? 0));
|
|
7475
|
+
const continuationInput: RuntimeSheetDatasetStartInput = {
|
|
7476
|
+
...requestInput,
|
|
7477
|
+
inputOffset,
|
|
7478
|
+
rows: [],
|
|
7479
|
+
};
|
|
7480
|
+
const emptyRequestBytes = runtimeSheetStartRequestBytes({
|
|
7481
|
+
request: request(continuationInput),
|
|
7482
|
+
limitBytes: RUNTIME_SHEET_START_REQUEST_MAX_BYTES,
|
|
7483
|
+
});
|
|
7484
|
+
if (emptyRequestBytes.exceeded) {
|
|
7485
|
+
throw new Error(
|
|
7486
|
+
`Runtime sheet start metadata exceeds the ${RUNTIME_SHEET_START_REQUEST_MAX_BYTES}-byte app request budget before row data is included.`,
|
|
7487
|
+
);
|
|
7488
|
+
}
|
|
7489
|
+
// Leave space for fields returned by the first page and sent on continuation.
|
|
7490
|
+
const rowPageBudgetBytes =
|
|
7491
|
+
RUNTIME_SHEET_START_REQUEST_MAX_BYTES - emptyRequestBytes.bytes - 4 * 1024;
|
|
7492
|
+
if (rowPageBudgetBytes < 1) {
|
|
7493
|
+
throw new Error(
|
|
7494
|
+
'Runtime sheet start metadata leaves no room for a row in the app request budget.',
|
|
7495
|
+
);
|
|
7496
|
+
}
|
|
7497
|
+
|
|
7498
|
+
const pageResults: PrepareRuntimeSheetResult[] = [];
|
|
7499
|
+
let pageInput = continuationInput;
|
|
7500
|
+
const startedAt = Date.now();
|
|
7501
|
+
for await (const page of iteratePlayDatasetInputPages(input.rows, {
|
|
7502
|
+
maxPageBytes: rowPageBudgetBytes,
|
|
7503
|
+
estimateRowBytes: (row) => {
|
|
7504
|
+
const measured = jsonByteLengthUpTo(
|
|
7505
|
+
row,
|
|
7506
|
+
RUNTIME_SHEET_START_REQUEST_MAX_BYTES,
|
|
7507
|
+
);
|
|
7508
|
+
return measured.bytes + 1;
|
|
7509
|
+
},
|
|
7510
|
+
})) {
|
|
7511
|
+
const nextInput: RuntimeSheetDatasetStartInput = {
|
|
7512
|
+
...pageInput,
|
|
7513
|
+
rows: page.rows,
|
|
7514
|
+
inputOffset: inputOffset + page.offset,
|
|
7515
|
+
};
|
|
7516
|
+
const pageRequest = request(nextInput);
|
|
7517
|
+
const pageBytes = runtimeSheetStartRequestBytes({
|
|
7518
|
+
request: pageRequest,
|
|
7519
|
+
limitBytes: RUNTIME_SHEET_START_REQUEST_MAX_BYTES,
|
|
7520
|
+
});
|
|
7521
|
+
if (pageBytes.exceeded) {
|
|
7522
|
+
throw new Error(
|
|
7523
|
+
`A runtime sheet start page exceeds the ${RUNTIME_SHEET_START_REQUEST_MAX_BYTES}-byte app request budget.`,
|
|
7524
|
+
);
|
|
7525
|
+
}
|
|
7526
|
+
const result = await postRuntimeApi<PrepareRuntimeSheetResult>(
|
|
7527
|
+
context,
|
|
7528
|
+
pageRequest,
|
|
7529
|
+
);
|
|
7530
|
+
pageResults.push(result);
|
|
7531
|
+
pageInput = {
|
|
7532
|
+
...nextInput,
|
|
7533
|
+
attemptId: result.attemptId ?? nextInput.attemptId,
|
|
7534
|
+
attemptOwnerRunId:
|
|
7535
|
+
result.attemptOwnerRunId ?? nextInput.attemptOwnerRunId,
|
|
7536
|
+
attemptExpiresAt: result.attemptExpiresAt ?? nextInput.attemptExpiresAt,
|
|
7537
|
+
attemptSeq: result.attemptSeq ?? nextInput.attemptSeq,
|
|
7538
|
+
writeVersion: result.writeVersion ?? nextInput.writeVersion,
|
|
7539
|
+
};
|
|
7540
|
+
}
|
|
7541
|
+
|
|
7542
|
+
if (pageResults.length === 0) {
|
|
7543
|
+
return await postRuntimeApi<PrepareRuntimeSheetResult>(
|
|
7544
|
+
context,
|
|
7545
|
+
request(continuationInput),
|
|
7546
|
+
);
|
|
7547
|
+
}
|
|
7548
|
+
if (pageResults.length === 1) return pageResults[0]!;
|
|
7549
|
+
|
|
7550
|
+
const latest = pageResults.at(-1)!;
|
|
7551
|
+
const pendingRows = pageResults.flatMap((result) => result.pendingRows);
|
|
7552
|
+
const completedRows = pageResults.flatMap((result) => result.completedRows);
|
|
7553
|
+
const blockedRows = pageResults.flatMap((result) => result.blockedRows);
|
|
7554
|
+
const inserted = pageResults.reduce(
|
|
7555
|
+
(total, result) => total + result.inserted,
|
|
7556
|
+
0,
|
|
7557
|
+
);
|
|
7558
|
+
const skipped = pageResults.reduce(
|
|
7559
|
+
(total, result) => total + result.skipped,
|
|
7560
|
+
0,
|
|
7561
|
+
);
|
|
7562
|
+
const lastDefined = <T>(
|
|
7563
|
+
select: (result: PrepareRuntimeSheetResult) => T | null | undefined,
|
|
7564
|
+
) => {
|
|
7565
|
+
for (let index = pageResults.length - 1; index >= 0; index -= 1) {
|
|
7566
|
+
const value = select(pageResults[index]!);
|
|
7567
|
+
if (value != null) return value;
|
|
7568
|
+
}
|
|
7569
|
+
return undefined;
|
|
7570
|
+
};
|
|
7571
|
+
return {
|
|
7572
|
+
...(input.includePreviousCells
|
|
7573
|
+
? {
|
|
7574
|
+
previousCells: pageResults.flatMap(
|
|
7575
|
+
(result) => result.previousCells ?? [],
|
|
7576
|
+
),
|
|
7577
|
+
}
|
|
7578
|
+
: {}),
|
|
7579
|
+
inserted,
|
|
7580
|
+
skipped,
|
|
7581
|
+
pendingRows,
|
|
7582
|
+
completedRows,
|
|
7583
|
+
blockedRows,
|
|
7584
|
+
tableNamespace: latest.tableNamespace,
|
|
7585
|
+
attemptId:
|
|
7586
|
+
lastDefined((result) => result.attemptId) ??
|
|
7587
|
+
requestInput.attemptId ??
|
|
7588
|
+
undefined,
|
|
7589
|
+
attemptOwnerRunId:
|
|
7590
|
+
lastDefined((result) => result.attemptOwnerRunId) ??
|
|
7591
|
+
requestInput.attemptOwnerRunId ??
|
|
7592
|
+
undefined,
|
|
7593
|
+
attemptExpiresAt:
|
|
7594
|
+
lastDefined((result) => result.attemptExpiresAt) ??
|
|
7595
|
+
requestInput.attemptExpiresAt ??
|
|
7596
|
+
undefined,
|
|
7597
|
+
attemptSeq:
|
|
7598
|
+
lastDefined((result) => result.attemptSeq) ??
|
|
7599
|
+
requestInput.attemptSeq ??
|
|
7600
|
+
undefined,
|
|
7601
|
+
writeVersion:
|
|
7602
|
+
lastDefined((result) => result.writeVersion) ??
|
|
7603
|
+
requestInput.writeVersion ??
|
|
7604
|
+
undefined,
|
|
7605
|
+
timings: [
|
|
7606
|
+
{
|
|
7607
|
+
phase: 'gateway_page_batch',
|
|
7608
|
+
ms: Date.now() - startedAt,
|
|
7609
|
+
rows: input.rows.length,
|
|
7610
|
+
chunks: pageResults.length,
|
|
7611
|
+
inserted,
|
|
7612
|
+
skipped,
|
|
7613
|
+
pending: pendingRows.length,
|
|
7614
|
+
completed: completedRows.length,
|
|
7615
|
+
},
|
|
7616
|
+
],
|
|
7617
|
+
};
|
|
7618
|
+
}
|
|
7619
|
+
|
|
7620
|
+
/**
|
|
7621
|
+
* Prepare one dataset page and return its row dispositions and write version.
|
|
7622
|
+
* With stableWriteIntent, the first admission for a run/table is durable in
|
|
7623
|
+
* the same transaction as its rows; subsequent pages and wakes reuse it even
|
|
7624
|
+
* after losing all runner-local state. A new run admits a newer intent.
|
|
7625
|
+
* Gateway transport uses a named operation; an older receiver cannot silently
|
|
7626
|
+
* ignore this opt-in or trigger unsafe legacy admission.
|
|
7627
|
+
*/
|
|
7628
|
+
function runtimeSheetInputRowIdentity(
|
|
7629
|
+
row: Record<string, unknown>,
|
|
7630
|
+
tableNamespace: string,
|
|
7631
|
+
): { key: string; cleanedRow: Record<string, unknown> } {
|
|
7632
|
+
// Use the same identity before transport paging and inside the receiver.
|
|
7633
|
+
const cleanedRow = toSerializableCsvAliasedRow(row);
|
|
7634
|
+
return {
|
|
7635
|
+
key:
|
|
7636
|
+
resolveMapRowOutcomeKey(row) ??
|
|
7637
|
+
derivePlayRowIdentity(cleanedRow, tableNamespace),
|
|
7638
|
+
cleanedRow,
|
|
7639
|
+
};
|
|
7640
|
+
}
|
|
7641
|
+
|
|
7642
|
+
type RuntimeSheetStartInput = Parameters<typeof startRuntimeSheetDataset>[1];
|
|
7643
|
+
type RuntimeSheetStartMetadata = Omit<
|
|
7644
|
+
RuntimeSheetStartInput,
|
|
7645
|
+
'rows' | 'playInput'
|
|
7646
|
+
>;
|
|
7647
|
+
type RuntimeSheetStartAction =
|
|
7648
|
+
| 'runtime_sheet_start'
|
|
7649
|
+
| 'runtime_sheet_start_with_intent';
|
|
7650
|
+
type RuntimeSheetStartTransportPage = {
|
|
7651
|
+
rowsJson: string[];
|
|
7652
|
+
inputOffset?: number;
|
|
7653
|
+
};
|
|
7654
|
+
|
|
7655
|
+
function serializeRuntimeSheetStartRequest(
|
|
7656
|
+
action: RuntimeSheetStartAction,
|
|
7657
|
+
metadata: RuntimeSheetStartMetadata,
|
|
7658
|
+
rowsJson: readonly string[],
|
|
7659
|
+
): string {
|
|
7660
|
+
const prepared = prepareRetainedPlayPipelinesForTransport({
|
|
7661
|
+
action,
|
|
7662
|
+
input: metadata,
|
|
7663
|
+
});
|
|
7664
|
+
const inputJson = JSON.stringify(prepared.input);
|
|
7665
|
+
return `{"action":${JSON.stringify(prepared.action)},"input":${inputJson.slice(0, -1)}${inputJson.length > 2 ? ',' : ''}"rows":[${rowsJson.join(',')}]}}`;
|
|
7666
|
+
}
|
|
7667
|
+
|
|
7668
|
+
/**
|
|
7669
|
+
* Local admission pages, not a new wire response. Consumers must retain each
|
|
7670
|
+
* page's history/row-memory checks rather than merge a source-sized response.
|
|
7671
|
+
* The runner supplies its existing persistence retry around each fixed page.
|
|
7672
|
+
*/
|
|
7673
|
+
export async function* startRuntimeSheetDatasetPages(
|
|
7674
|
+
context: RuntimeApiContext,
|
|
7675
|
+
input: RuntimeSheetStartInput,
|
|
7676
|
+
options?: {
|
|
7677
|
+
runPage?: (
|
|
7678
|
+
operation: () => Promise<PrepareRuntimeSheetResult>,
|
|
7679
|
+
) => Promise<PrepareRuntimeSheetResult>;
|
|
7680
|
+
},
|
|
7681
|
+
): AsyncGenerator<PrepareRuntimeSheetResult> {
|
|
7682
|
+
const runPage =
|
|
7683
|
+
options?.runPage ??
|
|
7684
|
+
((operation: () => Promise<PrepareRuntimeSheetResult>) => operation());
|
|
7685
|
+
if (context.dbSessionStrategy !== 'gateway_only') {
|
|
7686
|
+
yield await runPage(() => startRuntimeSheetDataset(context, input));
|
|
7687
|
+
return;
|
|
7688
|
+
}
|
|
7689
|
+
const action: RuntimeSheetStartAction =
|
|
7690
|
+
input.stableWriteIntent === true
|
|
7691
|
+
? 'runtime_sheet_start_with_intent'
|
|
7692
|
+
: 'runtime_sheet_start';
|
|
7693
|
+
// playInput is not read by sheet admission. Repeating the entire authored
|
|
7694
|
+
// input beside every bounded row page would defeat the transport boundary.
|
|
7695
|
+
const frozenMetadata = JSON.parse(
|
|
7696
|
+
JSON.stringify({
|
|
7697
|
+
...input,
|
|
7698
|
+
rows: undefined,
|
|
7699
|
+
playInput: undefined,
|
|
7700
|
+
pendingRerunAdmission: true,
|
|
7701
|
+
}),
|
|
7702
|
+
) as RuntimeSheetStartMetadata;
|
|
7703
|
+
const tableNamespace = frozenMetadata.tableNamespace;
|
|
7704
|
+
const includePreviousCells = frozenMetadata.includePreviousCells === true;
|
|
7705
|
+
const previousCellsBudgetBytes = frozenMetadata.previousCellsBudgetBytes;
|
|
7706
|
+
const attemptOwnerRunId =
|
|
7707
|
+
normalizeRuntimeSheetAttemptOwnerRunId(frozenMetadata);
|
|
7708
|
+
const attemptSeq = normalizeRuntimeRunAttempt(frozenMetadata.attemptSeq);
|
|
7709
|
+
// Array JSON encodes holes/undefined as null; preserve that wire behavior on
|
|
7710
|
+
// the unchanged single-page path as well as ordinary JSON value snapshots.
|
|
7711
|
+
const rowsJson = Array.from(
|
|
7712
|
+
input.rows,
|
|
7713
|
+
(row) => JSON.stringify(row) ?? 'null',
|
|
7714
|
+
);
|
|
7715
|
+
const rowBytes = rowsJson.reduce(
|
|
7716
|
+
(total, row) => total + Buffer.byteLength(row, 'utf8'),
|
|
7717
|
+
Math.max(0, rowsJson.length - 1),
|
|
7718
|
+
);
|
|
7719
|
+
const fullBytes =
|
|
7720
|
+
Buffer.byteLength(
|
|
7721
|
+
serializeRuntimeSheetStartRequest(action, frozenMetadata, []),
|
|
7722
|
+
'utf8',
|
|
7723
|
+
) + rowBytes;
|
|
7724
|
+
const pages: RuntimeSheetStartTransportPage[] = [];
|
|
7725
|
+
let duplicateRows = 0;
|
|
7726
|
+
if (fullBytes <= RUNNER_TERMINAL_PUSH_MAX_BODY_BYTES) {
|
|
7727
|
+
// Keep every previously transportable admission as one transaction, with
|
|
7728
|
+
// its existing receiver-side deduplication and optional response fields.
|
|
7729
|
+
pages.push({ rowsJson });
|
|
7730
|
+
} else {
|
|
7731
|
+
frozenMetadata.attemptId = normalizeRuntimeSheetAttemptId(
|
|
7732
|
+
frozenMetadata.attemptId,
|
|
7733
|
+
);
|
|
7734
|
+
const inputOffset = Math.max(
|
|
7735
|
+
0,
|
|
7736
|
+
Math.floor(frozenMetadata.inputOffset ?? 0),
|
|
7737
|
+
);
|
|
7738
|
+
if (!Number.isSafeInteger(inputOffset + rowsJson.length)) {
|
|
7739
|
+
throw new Error('Runtime sheet start requires safe global row indexes.');
|
|
7740
|
+
}
|
|
7741
|
+
// Reserve the longest valid version/index before ANY page is admitted.
|
|
7742
|
+
// A late oversized row or contract must not leave earlier pages pending.
|
|
7743
|
+
const framingBytes = Buffer.byteLength(
|
|
7744
|
+
serializeRuntimeSheetStartRequest(
|
|
7745
|
+
action,
|
|
7746
|
+
{
|
|
7747
|
+
...frozenMetadata,
|
|
7748
|
+
writeVersion: Number.MAX_SAFE_INTEGER,
|
|
7749
|
+
inputOffset: Number.MAX_SAFE_INTEGER,
|
|
7750
|
+
},
|
|
7751
|
+
[],
|
|
7752
|
+
),
|
|
7753
|
+
'utf8',
|
|
7754
|
+
);
|
|
7755
|
+
const seen = new Set<string>();
|
|
7756
|
+
let page: RuntimeSheetStartTransportPage = { rowsJson: [] };
|
|
7757
|
+
let pageBytes = framingBytes;
|
|
7758
|
+
for (let index = 0; index < rowsJson.length; index += 1) {
|
|
7759
|
+
const row: unknown = JSON.parse(rowsJson[index]!);
|
|
7760
|
+
rowsJson[index] = '';
|
|
7761
|
+
if (!row || typeof row !== 'object' || Array.isArray(row)) {
|
|
7762
|
+
throw new Error(
|
|
7763
|
+
`Runtime sheet start row ${index} must encode an object.`,
|
|
7764
|
+
);
|
|
7765
|
+
}
|
|
7766
|
+
const sourceRow = row as Record<string, unknown>;
|
|
7767
|
+
const { key } = runtimeSheetInputRowIdentity(sourceRow, tableNamespace);
|
|
7768
|
+
if (seen.has(key)) {
|
|
7769
|
+
duplicateRows += 1;
|
|
7770
|
+
continue;
|
|
7771
|
+
}
|
|
7772
|
+
seen.add(key);
|
|
7773
|
+
const serialized = JSON.stringify({
|
|
7774
|
+
...sourceRow,
|
|
7775
|
+
...mapRowOutcomeRuntimeFields({
|
|
7776
|
+
key,
|
|
7777
|
+
inputIndex:
|
|
7778
|
+
normalizeRuntimeMapInputIndex(
|
|
7779
|
+
sourceRow[MAP_ROW_OUTCOME_RUNTIME_FIELDS.inputIndex],
|
|
7780
|
+
) ?? inputOffset + index,
|
|
7781
|
+
}),
|
|
7782
|
+
});
|
|
7783
|
+
const bytes = Buffer.byteLength(serialized, 'utf8');
|
|
7784
|
+
if (framingBytes + bytes > RUNNER_TERMINAL_PUSH_MAX_BODY_BYTES) {
|
|
7785
|
+
throw new Error(
|
|
7786
|
+
`Runtime sheet start row ${index} exceeds the ${RUNNER_TERMINAL_PUSH_MAX_BODY_BYTES} byte request-body limit.`,
|
|
7787
|
+
);
|
|
7788
|
+
}
|
|
7789
|
+
const separatorBytes = page.rowsJson.length > 0 ? 1 : 0;
|
|
7790
|
+
if (
|
|
7791
|
+
page.rowsJson.length > 0 &&
|
|
7792
|
+
pageBytes + separatorBytes + bytes >
|
|
7793
|
+
RUNTIME_SHEET_START_TARGET_BODY_BYTES
|
|
7794
|
+
) {
|
|
7795
|
+
pages.push(page);
|
|
7796
|
+
page = { rowsJson: [] };
|
|
7797
|
+
pageBytes = framingBytes;
|
|
7798
|
+
}
|
|
7799
|
+
if (page.rowsJson.length === 0) page.inputOffset = inputOffset + index;
|
|
7800
|
+
pageBytes += bytes + (page.rowsJson.length > 0 ? 1 : 0);
|
|
7801
|
+
page.rowsJson.push(serialized);
|
|
7802
|
+
}
|
|
7803
|
+
if (page.rowsJson.length > 0) pages.push(page);
|
|
7804
|
+
if (pages.length === 0) {
|
|
7805
|
+
throw new Error(
|
|
7806
|
+
'Runtime sheet start metadata exceeds the request-body limit.',
|
|
7807
|
+
);
|
|
7808
|
+
}
|
|
7809
|
+
}
|
|
7810
|
+
|
|
7811
|
+
let writeVersion = frozenMetadata.writeVersion;
|
|
7812
|
+
let membershipCapture: boolean | undefined;
|
|
7813
|
+
for (const [index, page] of pages.entries()) {
|
|
7814
|
+
const pageMetadata = {
|
|
7815
|
+
...frozenMetadata,
|
|
7816
|
+
...(page.inputOffset !== undefined
|
|
7817
|
+
? { inputOffset: page.inputOffset }
|
|
7818
|
+
: {}),
|
|
7819
|
+
...(writeVersion != null ? { writeVersion } : {}),
|
|
7820
|
+
};
|
|
7821
|
+
const preparedBodyJson = serializeRuntimeSheetStartRequest(
|
|
7822
|
+
action,
|
|
7823
|
+
pageMetadata,
|
|
7824
|
+
page.rowsJson,
|
|
7825
|
+
);
|
|
7826
|
+
if (
|
|
7827
|
+
Buffer.byteLength(preparedBodyJson, 'utf8') >
|
|
7828
|
+
RUNNER_TERMINAL_PUSH_MAX_BODY_BYTES
|
|
7829
|
+
) {
|
|
7830
|
+
throw new Error(
|
|
7831
|
+
'Runtime sheet start page exceeded its preflight body budget.',
|
|
7832
|
+
);
|
|
7833
|
+
}
|
|
7834
|
+
const request = JSON.parse(preparedBodyJson) as Extract<
|
|
7835
|
+
RuntimeApiActionRequest,
|
|
7836
|
+
{ action: RuntimeSheetStartAction }
|
|
7837
|
+
>;
|
|
7838
|
+
const result = await runPage(() =>
|
|
7839
|
+
postRuntimeApi<PrepareRuntimeSheetResult>(
|
|
7840
|
+
context,
|
|
7841
|
+
request,
|
|
7842
|
+
preparedBodyJson,
|
|
7843
|
+
),
|
|
7844
|
+
);
|
|
7845
|
+
page.rowsJson.length = 0;
|
|
7846
|
+
if (includePreviousCells) {
|
|
7847
|
+
assertDatasetAdmissionHistory(result, previousCellsBudgetBytes);
|
|
7848
|
+
}
|
|
7849
|
+
if (pages.length > 1) {
|
|
7850
|
+
const admittedVersion = result.writeVersion;
|
|
7851
|
+
if (
|
|
7852
|
+
typeof admittedVersion !== 'number' ||
|
|
7853
|
+
!Number.isSafeInteger(admittedVersion) ||
|
|
7854
|
+
admittedVersion <= 0 ||
|
|
7855
|
+
(writeVersion != null && result.writeVersion !== writeVersion) ||
|
|
7856
|
+
normalizeTableNamespace(result.tableNamespace) !==
|
|
7857
|
+
normalizeTableNamespace(tableNamespace) ||
|
|
7858
|
+
(result.attemptId !== undefined &&
|
|
7859
|
+
result.attemptId !== frozenMetadata.attemptId) ||
|
|
7860
|
+
(result.attemptOwnerRunId !== undefined &&
|
|
7861
|
+
result.attemptOwnerRunId !== attemptOwnerRunId) ||
|
|
7862
|
+
(result.attemptSeq !== undefined && result.attemptSeq !== attemptSeq) ||
|
|
7863
|
+
(index > 0 && result.membershipCapture !== membershipCapture)
|
|
7864
|
+
) {
|
|
7865
|
+
throw new Error(
|
|
7866
|
+
'Runtime sheet admission pages disagree on their write intent or identity.',
|
|
7867
|
+
);
|
|
7868
|
+
}
|
|
7869
|
+
writeVersion = result.writeVersion;
|
|
7870
|
+
}
|
|
7871
|
+
membershipCapture = result.membershipCapture;
|
|
7872
|
+
yield index === 0 && duplicateRows > 0
|
|
7873
|
+
? { ...result, skipped: result.skipped + duplicateRows }
|
|
7874
|
+
: result;
|
|
7875
|
+
}
|
|
7876
|
+
}
|
|
7877
|
+
|
|
7878
|
+
export async function startRuntimeSheetDataset(
|
|
7879
|
+
context: RuntimeApiContext,
|
|
7880
|
+
input: {
|
|
7881
|
+
playName: string;
|
|
7882
|
+
tableNamespace: string;
|
|
7883
|
+
playInput?: Record<string, unknown> | null;
|
|
7884
|
+
sheetContract: PlaySheetContract;
|
|
7885
|
+
rows: Record<string, unknown>[];
|
|
7886
|
+
runId: string;
|
|
7887
|
+
attemptId?: string | null;
|
|
7888
|
+
attemptOwnerRunId?: string | null;
|
|
7889
|
+
attemptExpiresAt?: string | null;
|
|
7890
|
+
attemptSeq?: number | null;
|
|
7891
|
+
attemptLeaseTtlMs?: number | null;
|
|
7892
|
+
writeVersion?: number | null;
|
|
7893
|
+
/** Retain this run/table's first admission across pages, retries and wakes. Absence keeps legacy allocation. */
|
|
7894
|
+
stableWriteIntent?: boolean;
|
|
7895
|
+
/** Return completed durable cells separately from merged source rows. */
|
|
7896
|
+
includePreviousCells?: boolean;
|
|
7019
7897
|
previousCellsBudgetBytes?: number;
|
|
7020
7898
|
inputOffset?: number;
|
|
7021
7899
|
force?: boolean;
|
|
@@ -7033,15 +7911,7 @@ export async function startRuntimeSheetDataset(
|
|
|
7033
7911
|
};
|
|
7034
7912
|
if (context.dbSessionStrategy === 'gateway_only') {
|
|
7035
7913
|
return checkedResult(
|
|
7036
|
-
await
|
|
7037
|
-
// A named operation prevents an old gateway from silently ignoring the
|
|
7038
|
-
// intent opt-in and re-admitting this run under a new version.
|
|
7039
|
-
action:
|
|
7040
|
-
input.stableWriteIntent === true
|
|
7041
|
-
? 'runtime_sheet_start_with_intent'
|
|
7042
|
-
: 'runtime_sheet_start',
|
|
7043
|
-
input: { ...input, pendingRerunAdmission: true },
|
|
7044
|
-
}),
|
|
7914
|
+
await startRuntimeSheetDatasetViaGateway(context, input),
|
|
7045
7915
|
);
|
|
7046
7916
|
}
|
|
7047
7917
|
const totalStartedAt = Date.now();
|
|
@@ -7103,10 +7973,10 @@ export async function startRuntimeSheetDataset(
|
|
|
7103
7973
|
// Materializes projected CSV aliases as visible cells and drops internal
|
|
7104
7974
|
// __deepline* keys — a plain spread would silently lose the
|
|
7105
7975
|
// non-enumerable alias fields on the JSON payload boundary.
|
|
7106
|
-
const cleanedRow =
|
|
7107
|
-
|
|
7108
|
-
|
|
7109
|
-
|
|
7976
|
+
const { cleanedRow, key } = runtimeSheetInputRowIdentity(
|
|
7977
|
+
row,
|
|
7978
|
+
input.tableNamespace,
|
|
7979
|
+
);
|
|
7110
7980
|
const source =
|
|
7111
7981
|
row.__deeplineSourceRow && typeof row.__deeplineSourceRow === 'object'
|
|
7112
7982
|
? (sanitizePostgresJsonValue(row.__deeplineSourceRow) as Record<
|
|
@@ -7210,9 +8080,10 @@ export async function startRuntimeSheetDataset(
|
|
|
7210
8080
|
tableNamespace: input.tableNamespace,
|
|
7211
8081
|
sheetContract: input.sheetContract,
|
|
7212
8082
|
transactional: needsTransaction,
|
|
8083
|
+
concurrentColumnPreparation: true,
|
|
7213
8084
|
timings,
|
|
7214
8085
|
},
|
|
7215
|
-
async (client) => {
|
|
8086
|
+
async (client, columnLockMode) => {
|
|
7216
8087
|
const attemptExpiryStartedAt = Date.now();
|
|
7217
8088
|
const attemptExpiresAt = await mintRuntimeSheetAttemptExpiresAt(
|
|
7218
8089
|
client,
|
|
@@ -7224,6 +8095,7 @@ export async function startRuntimeSheetDataset(
|
|
|
7224
8095
|
input,
|
|
7225
8096
|
);
|
|
7226
8097
|
const columnTypes = await RuntimeSheetColumns.prepareWrite(client, {
|
|
8098
|
+
lockMode: columnLockMode,
|
|
7227
8099
|
schema: session.postgres.schema,
|
|
7228
8100
|
table: session.postgres.sheetTable,
|
|
7229
8101
|
columns: physicalSheetColumnProjections(input.sheetContract).map(
|
|
@@ -7781,6 +8653,7 @@ function physicalRuntimeSheetUpdateSql(
|
|
|
7781
8653
|
}
|
|
7782
8654
|
|
|
7783
8655
|
type CompleteRuntimeMapRowChunksInput = {
|
|
8656
|
+
columnLockMode: 'shared' | 'exclusive';
|
|
7784
8657
|
chunks: RuntimePreparedCompletedRow[][];
|
|
7785
8658
|
physicalColumnProjections: PhysicalSheetColumnProjection[];
|
|
7786
8659
|
runId: string;
|
|
@@ -7951,45 +8824,57 @@ function writableEnrichedRuntimeSheetAttemptSql(
|
|
|
7951
8824
|
async function completeRuntimeMapRowChunks(
|
|
7952
8825
|
client: RuntimeQueryClient,
|
|
7953
8826
|
session: RuntimePostgresSession,
|
|
7954
|
-
input: CompleteRuntimeMapRowChunksInput
|
|
7955
|
-
|
|
8827
|
+
input: CompleteRuntimeMapRowChunksInput & {
|
|
8828
|
+
exactKeyChunks: RuntimePreparedCompletedRow[][][];
|
|
8829
|
+
},
|
|
8830
|
+
): Promise<RuntimeMapRowsMutationResult & { allExactRowsUpdated: boolean }> {
|
|
7956
8831
|
let updated = 0;
|
|
8832
|
+
// Exact submitted keys are deduplicated before chunking. A physical update
|
|
8833
|
+
// locks each existing key until this transaction commits; only that proof
|
|
8834
|
+
// permits skipping missing-row discovery. Repair counts are not key coverage.
|
|
8835
|
+
let allExactRowsUpdated = true;
|
|
7957
8836
|
const fencedRows: RuntimeMapRowsStaleRow[] = [];
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
const
|
|
7962
|
-
const
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
8837
|
+
// Preserve the original row-count units: every exact write precedes all
|
|
8838
|
+
// input-index repairs within that unit, regardless of payload subdivision.
|
|
8839
|
+
for (const exactKeyChunks of input.exactKeyChunks) {
|
|
8840
|
+
const repairChunk: RuntimePreparedCompletedRow[] = [];
|
|
8841
|
+
for (const chunk of exactKeyChunks) {
|
|
8842
|
+
const chunkKeys = chunk.map((row) => row.key);
|
|
8843
|
+
const chunkInputIndexes = chunk.map((row) => row.input_index);
|
|
8844
|
+
const chunkDataPatches = chunk.map((row) => row.data_patch_json);
|
|
8845
|
+
const chunkCellMetaPatches = chunk.map((row) => row.cell_meta_patch_json);
|
|
8846
|
+
const targetChangedPatchedCellSql = changedPatchedCellSql(
|
|
8847
|
+
'target',
|
|
8848
|
+
'updates.data_patch',
|
|
8849
|
+
input.physicalColumnProjections,
|
|
8850
|
+
);
|
|
8851
|
+
const writableEnrichedAttemptSql = writableEnrichedRuntimeSheetAttemptSql(
|
|
8852
|
+
'target',
|
|
8853
|
+
'$10::text',
|
|
8854
|
+
'$11::timestamptz',
|
|
8855
|
+
'$12',
|
|
8856
|
+
);
|
|
8857
|
+
const writableNonTerminalAttemptSql = `(
|
|
7975
8858
|
target._status <> 'enriched'
|
|
7976
8859
|
AND (
|
|
7977
8860
|
${activeRuntimeSheetAttemptFenceSql('target', '$9::text', '$10::text', '$11::timestamptz', '$12::integer')}
|
|
7978
8861
|
OR coalesce(target._attempt_owner_run_id, target._run_id) IS DISTINCT FROM $10::text
|
|
7979
8862
|
)
|
|
7980
8863
|
)`;
|
|
7981
|
-
|
|
7982
|
-
SELECT
|
|
7983
|
-
|
|
7984
|
-
|
|
7985
|
-
|
|
7986
|
-
|
|
7987
|
-
|
|
7988
|
-
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
|
|
8864
|
+
const matchingUpdatesSql = `WITH mutation_context AS (SELECT $6::text AS play_name, $7::text AS table_namespace, $11::timestamptz AS attempt_expires_at, $12::integer AS attempt_seq), updates AS (
|
|
8865
|
+
SELECT input_values._key,
|
|
8866
|
+
input_values.input_index,
|
|
8867
|
+
input_values.ord AS submitted_ordinal,
|
|
8868
|
+
input_values.data_patch,
|
|
8869
|
+
input_values.cell_meta_patch
|
|
8870
|
+
FROM unnest($1::text[], $2::bigint[], $3::jsonb[], $4::jsonb[])
|
|
8871
|
+
WITH ORDINALITY AS input_values(_key, input_index, data_patch, cell_meta_patch, ord)
|
|
8872
|
+
WHERE input_values.ord <= LEAST(
|
|
8873
|
+
COALESCE(cardinality($1::text[]), 0),
|
|
8874
|
+
COALESCE(cardinality($2::bigint[]), 0),
|
|
8875
|
+
COALESCE(cardinality($3::jsonb[]), 0),
|
|
8876
|
+
COALESCE(cardinality($4::jsonb[]), 0)
|
|
8877
|
+
)
|
|
7993
8878
|
),
|
|
7994
8879
|
matched_updates AS (
|
|
7995
8880
|
SELECT target._key AS matched_key,
|
|
@@ -7997,18 +8882,21 @@ async function completeRuntimeMapRowChunks(
|
|
|
7997
8882
|
target._status AS prev_status,
|
|
7998
8883
|
target._cell_meta AS prev_cell_meta,
|
|
7999
8884
|
updates.input_index,
|
|
8885
|
+
updates.submitted_ordinal,
|
|
8000
8886
|
updates.data_patch,
|
|
8001
8887
|
updates.cell_meta_patch
|
|
8002
8888
|
FROM updates
|
|
8003
8889
|
JOIN ${sheetTable(session)} AS target
|
|
8004
8890
|
ON target._key = updates._key
|
|
8005
8891
|
)`;
|
|
8006
|
-
|
|
8892
|
+
// Membership must not promote a run-wide partial index over exact keys
|
|
8893
|
+
// when statistics underestimate a fresh run. Keep negated fencing intact.
|
|
8894
|
+
const writableRowsSql = `target._key = updates.matched_key
|
|
8007
8895
|
AND ($13::bigint IS NULL OR target._write_version = $13::bigint)
|
|
8008
8896
|
AND ($13::bigint IS NOT NULL OR target._status <> 'enriched')
|
|
8009
8897
|
AND NOT (${newerTerminalRuntimeSheetRowSql('target', '$11::timestamptz', '$12::integer', '$9::text', '$10::text')})
|
|
8010
8898
|
AND (
|
|
8011
|
-
($9::text IS NULL AND target._run_id = $5::text)
|
|
8899
|
+
($9::text IS NULL AND coalesce(target._run_id = $5::text, false))
|
|
8012
8900
|
OR ($9::text IS NOT NULL AND ${writableNonTerminalAttemptSql})
|
|
8013
8901
|
OR ($9::text IS NOT NULL AND ${activeRuntimeSheetAttemptFenceSql('target', '$9::text', '$10::text', '$11::timestamptz', '$12::integer')})
|
|
8014
8902
|
OR ($9::text IS NOT NULL AND ${writableEnrichedAttemptSql})
|
|
@@ -8045,46 +8933,76 @@ async function completeRuntimeMapRowChunks(
|
|
|
8045
8933
|
WHERE coalesce(target._cell_meta -> field_values.field ->> 'status', '') <> 'completed'
|
|
8046
8934
|
)
|
|
8047
8935
|
)`;
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8936
|
+
const mutationParams = [
|
|
8937
|
+
chunkKeys,
|
|
8938
|
+
chunkInputIndexes,
|
|
8939
|
+
chunkDataPatches,
|
|
8940
|
+
chunkCellMetaPatches,
|
|
8941
|
+
input.runId,
|
|
8942
|
+
input.normalizedPlayName,
|
|
8943
|
+
input.normalizedTableNamespace,
|
|
8944
|
+
[...new Set(input.outputFields)],
|
|
8945
|
+
input.attemptId,
|
|
8946
|
+
input.attemptOwnerRunId,
|
|
8947
|
+
input.attemptExpiresAt,
|
|
8948
|
+
input.attemptSeq,
|
|
8949
|
+
input.writeVersion,
|
|
8950
|
+
];
|
|
8951
|
+
const columnTypes = await RuntimeSheetColumns.prepareWrite(client, {
|
|
8952
|
+
lockMode: input.columnLockMode,
|
|
8953
|
+
schema: session.postgres.schema,
|
|
8954
|
+
table: session.postgres.sheetTable,
|
|
8955
|
+
columns: input.physicalColumnProjections,
|
|
8956
|
+
rows: async () => {
|
|
8957
|
+
const accepted = await client.query<{
|
|
8958
|
+
submitted_ordinal: number | string;
|
|
8959
|
+
}>(
|
|
8960
|
+
`${matchingUpdatesSql}
|
|
8961
|
+
SELECT updates.submitted_ordinal
|
|
8073
8962
|
FROM matched_updates AS updates
|
|
8074
8963
|
JOIN ${sheetTable(session)} AS target ON target._key = updates.matched_key
|
|
8075
8964
|
WHERE ${writableRowsSql}
|
|
8076
8965
|
FOR UPDATE OF target`,
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
8083
|
-
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8966
|
+
mutationParams,
|
|
8967
|
+
);
|
|
8968
|
+
// Ordinality identifies the original parameter even if UTF8 text
|
|
8969
|
+
// transport replaces a lone surrogate or makes two keys collide.
|
|
8970
|
+
// Decode only the already-sanitized JSON parameter; never re-run
|
|
8971
|
+
// authored toJSON or deduplicate accepted physical-key collisions.
|
|
8972
|
+
const seen = new Set<number>();
|
|
8973
|
+
return accepted.rows.map((row) => {
|
|
8974
|
+
const raw = row.submitted_ordinal;
|
|
8975
|
+
const ordinal =
|
|
8976
|
+
typeof raw === 'number'
|
|
8977
|
+
? raw
|
|
8978
|
+
: typeof raw === 'string'
|
|
8979
|
+
? Number(raw)
|
|
8980
|
+
: NaN;
|
|
8981
|
+
if (
|
|
8982
|
+
!Number.isSafeInteger(ordinal) ||
|
|
8983
|
+
ordinal < 1 ||
|
|
8984
|
+
ordinal > chunk.length ||
|
|
8985
|
+
(typeof raw === 'string' && String(ordinal) !== raw) ||
|
|
8986
|
+
seen.has(ordinal)
|
|
8987
|
+
) {
|
|
8988
|
+
throw new Error(
|
|
8989
|
+
'Runtime Sheet accepted-ordinal evidence is invalid.',
|
|
8990
|
+
);
|
|
8991
|
+
}
|
|
8992
|
+
seen.add(ordinal);
|
|
8993
|
+
return JSON.parse(chunk[ordinal - 1]!.data_patch_json) as Record<
|
|
8994
|
+
string,
|
|
8995
|
+
unknown
|
|
8996
|
+
>;
|
|
8997
|
+
});
|
|
8998
|
+
},
|
|
8999
|
+
});
|
|
9000
|
+
const { rows } = await client.query<{
|
|
9001
|
+
updated: number;
|
|
9002
|
+
matched_keys: string[];
|
|
9003
|
+
fenced_rows: unknown;
|
|
9004
|
+
}>(
|
|
9005
|
+
`${matchingUpdatesSql},
|
|
8088
9006
|
applied_rows AS (
|
|
8089
9007
|
UPDATE ${sheetTable(session)} AS target
|
|
8090
9008
|
SET _status = 'enriched',
|
|
@@ -8224,23 +9142,31 @@ async function completeRuntimeMapRowChunks(
|
|
|
8224
9142
|
coalesce((SELECT jsonb_agg(jsonb_build_object('key', _key, 'reason', reason, 'rowVersionStamp', row_write_version)) FROM fenced_rows), '[]'::jsonb) AS fenced_rows,
|
|
8225
9143
|
(SELECT count(*)::int FROM summary_delta) AS summary_delta_count,
|
|
8226
9144
|
(SELECT count(*)::int FROM column_delta) AS column_delta_count`,
|
|
8227
|
-
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
9145
|
+
mutationParams,
|
|
9146
|
+
);
|
|
9147
|
+
const matchedKeys = new Set(rows[0]?.matched_keys ?? []);
|
|
9148
|
+
const chunkFencedRows = parseRuntimeMapRowsFencedRows(
|
|
9149
|
+
rows[0]?.fenced_rows,
|
|
9150
|
+
);
|
|
9151
|
+
// An exact-version replay that already produced the same terminal value is
|
|
9152
|
+
// accepted idempotently even when SQL has no physical mutation to apply.
|
|
9153
|
+
// Stale versions remain excluded through fenced_rows.
|
|
9154
|
+
const appliedCount = Number(rows[0]?.updated ?? 0);
|
|
9155
|
+
if (matchedKeys.size !== chunk.length || appliedCount !== chunk.length) {
|
|
9156
|
+
allExactRowsUpdated = false;
|
|
9157
|
+
}
|
|
9158
|
+
updated += appliedCount;
|
|
9159
|
+
fencedRows.push(...chunkFencedRows);
|
|
9160
|
+
if (matchedKeys.size === chunk.length) {
|
|
9161
|
+
continue;
|
|
9162
|
+
}
|
|
8240
9163
|
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
9164
|
+
repairChunk.push(
|
|
9165
|
+
...chunk.filter(
|
|
9166
|
+
(row) => !matchedKeys.has(row.key) && row.input_index !== null,
|
|
9167
|
+
),
|
|
9168
|
+
);
|
|
9169
|
+
}
|
|
8244
9170
|
if (repairChunk.length === 0) {
|
|
8245
9171
|
continue;
|
|
8246
9172
|
}
|
|
@@ -8252,7 +9178,7 @@ async function completeRuntimeMapRowChunks(
|
|
|
8252
9178
|
updated += repaired.updated;
|
|
8253
9179
|
fencedRows.push(...repaired.fencedRows);
|
|
8254
9180
|
}
|
|
8255
|
-
return { updated, fencedRows };
|
|
9181
|
+
return { updated, fencedRows, allExactRowsUpdated };
|
|
8256
9182
|
}
|
|
8257
9183
|
|
|
8258
9184
|
async function completeRuntimeMapRowChunksWithInputIndexRepair(
|
|
@@ -8360,6 +9286,7 @@ async function completeRuntimeMapRowChunksWithInputIndexRepair(
|
|
|
8360
9286
|
input.writeVersion,
|
|
8361
9287
|
];
|
|
8362
9288
|
const columnTypes = await RuntimeSheetColumns.prepareWrite(client, {
|
|
9289
|
+
lockMode: input.columnLockMode,
|
|
8363
9290
|
schema: session.postgres.schema,
|
|
8364
9291
|
table: session.postgres.sheetTable,
|
|
8365
9292
|
columns: input.physicalColumnProjections,
|
|
@@ -8546,6 +9473,7 @@ async function insertMissingCompletedMapRowChunks(
|
|
|
8546
9473
|
const chunkDataPatches = chunk.map((row) => row.data_patch_json);
|
|
8547
9474
|
const chunkCellMetaPatches = chunk.map((row) => row.cell_meta_patch_json);
|
|
8548
9475
|
const columnTypes = await RuntimeSheetColumns.prepareWrite(client, {
|
|
9476
|
+
lockMode: input.columnLockMode,
|
|
8549
9477
|
schema: session.postgres.schema,
|
|
8550
9478
|
table: session.postgres.sheetTable,
|
|
8551
9479
|
columns: input.physicalColumnProjections,
|
|
@@ -8553,13 +9481,19 @@ async function insertMissingCompletedMapRowChunks(
|
|
|
8553
9481
|
const accepted = await client.query<{
|
|
8554
9482
|
data_patch: Record<string, unknown>;
|
|
8555
9483
|
}>(
|
|
8556
|
-
`
|
|
8557
|
-
|
|
9484
|
+
`WITH exact_key_missing AS MATERIALIZED (
|
|
9485
|
+
SELECT incoming.* FROM jsonb_to_recordset($1::jsonb)
|
|
9486
|
+
AS incoming(key text, input_index bigint, data_patch jsonb)
|
|
9487
|
+
WHERE NOT EXISTS (
|
|
9488
|
+
SELECT 1 FROM ${sheetTable(session)} AS target
|
|
9489
|
+
WHERE target._key = incoming.key
|
|
9490
|
+
)
|
|
9491
|
+
)
|
|
9492
|
+
SELECT incoming.data_patch FROM exact_key_missing AS incoming
|
|
8558
9493
|
WHERE NOT EXISTS (
|
|
8559
9494
|
SELECT 1 FROM ${sheetTable(session)} AS target
|
|
8560
|
-
WHERE target.
|
|
8561
|
-
|
|
8562
|
-
AND target._input_index = incoming.input_index)
|
|
9495
|
+
WHERE incoming.input_index IS NOT NULL AND target._run_id = $2::text
|
|
9496
|
+
AND target._input_index = incoming.input_index
|
|
8563
9497
|
)`,
|
|
8564
9498
|
[
|
|
8565
9499
|
stringifyPostgresJson(
|
|
@@ -8602,20 +9536,28 @@ async function insertMissingCompletedMapRowChunks(
|
|
|
8602
9536
|
JOIN unnest($4::jsonb[]) WITH ORDINALITY AS cell_meta_values(cell_meta_patch, ord)
|
|
8603
9537
|
ON cell_meta_values.ord = key_values.ord
|
|
8604
9538
|
),
|
|
8605
|
-
--
|
|
8606
|
-
--
|
|
8607
|
-
|
|
9539
|
+
-- Keep input-index repair behind exact-key misses: otherwise the OR
|
|
9540
|
+
-- can read a whole run's index entries for every existing key.
|
|
9541
|
+
exact_key_missing AS MATERIALIZED (
|
|
8608
9542
|
SELECT input_rows.*
|
|
8609
9543
|
FROM input_rows
|
|
8610
9544
|
WHERE NOT EXISTS (
|
|
8611
9545
|
SELECT 1
|
|
8612
9546
|
FROM ${sheetTable(session)} AS target
|
|
8613
9547
|
WHERE target._key = input_rows._key
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
9548
|
+
)
|
|
9549
|
+
),
|
|
9550
|
+
-- Decode only accepted rows: a rejected replay may contain values
|
|
9551
|
+
-- incompatible with the stored native types.
|
|
9552
|
+
missing_rows AS MATERIALIZED (
|
|
9553
|
+
SELECT exact_key_missing.*
|
|
9554
|
+
FROM exact_key_missing
|
|
9555
|
+
WHERE NOT EXISTS (
|
|
9556
|
+
SELECT 1
|
|
9557
|
+
FROM ${sheetTable(session)} AS target
|
|
9558
|
+
WHERE exact_key_missing.input_index IS NOT NULL
|
|
9559
|
+
AND target._run_id = $5::text
|
|
9560
|
+
AND target._input_index = exact_key_missing.input_index
|
|
8619
9561
|
)
|
|
8620
9562
|
),
|
|
8621
9563
|
inserted_rows AS (
|
|
@@ -8741,6 +9683,7 @@ async function failRuntimeMapRowChunks(
|
|
|
8741
9683
|
client: RuntimeQueryClient,
|
|
8742
9684
|
session: RuntimePostgresSession,
|
|
8743
9685
|
input: {
|
|
9686
|
+
columnLockMode: 'shared' | 'exclusive';
|
|
8744
9687
|
chunks: RuntimePreparedFailedRow[][];
|
|
8745
9688
|
physicalColumnProjections: PhysicalSheetColumnProjection[];
|
|
8746
9689
|
forceTerminal?: boolean;
|
|
@@ -8767,24 +9710,10 @@ async function failRuntimeMapRowChunks(
|
|
|
8767
9710
|
'updates.data_patch',
|
|
8768
9711
|
input.physicalColumnProjections,
|
|
8769
9712
|
);
|
|
8770
|
-
//
|
|
8771
|
-
//
|
|
8772
|
-
const
|
|
8773
|
-
|
|
8774
|
-
for (const [field, meta] of Object.entries(row.cell_meta_patch)) {
|
|
8775
|
-
if (
|
|
8776
|
-
meta &&
|
|
8777
|
-
typeof meta === 'object' &&
|
|
8778
|
-
(meta as { status?: unknown }).status === 'failed'
|
|
8779
|
-
) {
|
|
8780
|
-
failedCellCounts.set(field, (failedCellCounts.get(field) ?? 0) + 1);
|
|
8781
|
-
}
|
|
8782
|
-
}
|
|
8783
|
-
}
|
|
8784
|
-
const failedCellFields = [...failedCellCounts.keys()];
|
|
8785
|
-
const failedCellTotals = failedCellFields.map(
|
|
8786
|
-
(field) => failedCellCounts.get(field) ?? 0,
|
|
8787
|
-
);
|
|
9713
|
+
// Keep legacy parameter positions stable; deltas now come from applied
|
|
9714
|
+
// physical winners and their previous cells, never submitted aliases.
|
|
9715
|
+
const failedCellFields: string[] = [];
|
|
9716
|
+
const failedCellTotals: number[] = [];
|
|
8788
9717
|
const matchingUpdatesSql = `WITH mutation_context AS (SELECT $7::text AS play_name, $8::text AS table_namespace, $9::text[] AS failed_fields, $10::int[] AS failed_totals, $12::text AS attempt_owner_run_id, $13::timestamptz AS attempt_expires_at, $14::integer AS attempt_seq), updates AS (
|
|
8789
9718
|
SELECT key_values._key,
|
|
8790
9719
|
input_index_values.input_index,
|
|
@@ -8856,6 +9785,7 @@ async function failRuntimeMapRowChunks(
|
|
|
8856
9785
|
input.writeVersion,
|
|
8857
9786
|
];
|
|
8858
9787
|
const columnTypes = await RuntimeSheetColumns.prepareWrite(client, {
|
|
9788
|
+
lockMode: input.columnLockMode,
|
|
8859
9789
|
schema: session.postgres.schema,
|
|
8860
9790
|
table: session.postgres.sheetTable,
|
|
8861
9791
|
columns: input.physicalColumnProjections,
|
|
@@ -8896,7 +9826,10 @@ async function failRuntimeMapRowChunks(
|
|
|
8896
9826
|
FROM matched_updates AS updates, ${sheetTable(session)} AS prev
|
|
8897
9827
|
WHERE ${writableRowsSql}
|
|
8898
9828
|
AND prev._key = target._key
|
|
8899
|
-
RETURNING target._key, prev._status AS prev_status
|
|
9829
|
+
RETURNING target._key, prev._status AS prev_status,
|
|
9830
|
+
prev._cell_meta AS prev_cell_meta,
|
|
9831
|
+
target._cell_meta AS next_cell_meta,
|
|
9832
|
+
updates.cell_meta_patch
|
|
8900
9833
|
),
|
|
8901
9834
|
applied_count AS (
|
|
8902
9835
|
SELECT count(*)::bigint AS c,
|
|
@@ -8972,20 +9905,45 @@ async function failRuntimeMapRowChunks(
|
|
|
8972
9905
|
_updated_at = now()
|
|
8973
9906
|
RETURNING 1
|
|
8974
9907
|
),
|
|
9908
|
+
cell_status_delta AS (
|
|
9909
|
+
SELECT field_values.field,
|
|
9910
|
+
sum((coalesce(applied_rows.next_cell_meta -> field_values.field ->> 'status', '') = 'failed')::int
|
|
9911
|
+
- (coalesce(applied_rows.prev_cell_meta -> field_values.field ->> 'status', '') = 'failed')::int)::int AS failed,
|
|
9912
|
+
-- Completed output counts are admitted by the completed-row
|
|
9913
|
+
-- writer. Partial failed rows may carry uncounted completed
|
|
9914
|
+
-- cells; only remove a cell counted on an enriched row.
|
|
9915
|
+
-count(*) FILTER (
|
|
9916
|
+
WHERE applied_rows.prev_status = 'enriched'
|
|
9917
|
+
AND coalesce(applied_rows.prev_cell_meta -> field_values.field ->> 'status', '') = 'completed'
|
|
9918
|
+
AND coalesce(applied_rows.next_cell_meta -> field_values.field ->> 'status', '') <> 'completed'
|
|
9919
|
+
)::int AS completed
|
|
9920
|
+
FROM applied_rows
|
|
9921
|
+
CROSS JOIN LATERAL jsonb_object_keys(
|
|
9922
|
+
CASE WHEN jsonb_typeof(applied_rows.cell_meta_patch) = 'object'
|
|
9923
|
+
THEN applied_rows.cell_meta_patch ELSE '{}'::jsonb END
|
|
9924
|
+
) AS field_values(field)
|
|
9925
|
+
GROUP BY field_values.field
|
|
9926
|
+
),
|
|
8975
9927
|
column_delta AS (
|
|
8976
9928
|
INSERT INTO ${columnSummaryTable(session)} AS target (
|
|
8977
9929
|
play_name,
|
|
8978
9930
|
table_namespace,
|
|
8979
9931
|
field,
|
|
8980
|
-
failed
|
|
9932
|
+
failed,
|
|
9933
|
+
completed
|
|
8981
9934
|
)
|
|
8982
|
-
SELECT $7::text, $8::text,
|
|
8983
|
-
FROM
|
|
8984
|
-
|
|
8985
|
-
ON count_values.ord = field_values.ord
|
|
8986
|
-
WHERE EXISTS (SELECT 1 FROM applied_rows)
|
|
9935
|
+
SELECT $7::text, $8::text, field, GREATEST(failed, 0), GREATEST(completed, 0)
|
|
9936
|
+
FROM cell_status_delta
|
|
9937
|
+
WHERE failed <> 0 OR completed <> 0
|
|
8987
9938
|
ON CONFLICT (play_name, table_namespace, field) DO UPDATE SET
|
|
8988
|
-
failed = GREATEST(target.failed +
|
|
9939
|
+
failed = GREATEST(target.failed + (
|
|
9940
|
+
SELECT delta.failed FROM cell_status_delta AS delta
|
|
9941
|
+
WHERE delta.field = EXCLUDED.field
|
|
9942
|
+
), 0),
|
|
9943
|
+
completed = GREATEST(target.completed + (
|
|
9944
|
+
SELECT delta.completed FROM cell_status_delta AS delta
|
|
9945
|
+
WHERE delta.field = EXCLUDED.field
|
|
9946
|
+
), 0),
|
|
8989
9947
|
_updated_at = now()
|
|
8990
9948
|
RETURNING 1
|
|
8991
9949
|
)
|
|
@@ -9002,6 +9960,40 @@ async function failRuntimeMapRowChunks(
|
|
|
9002
9960
|
return { updated, fencedRows };
|
|
9003
9961
|
}
|
|
9004
9962
|
|
|
9963
|
+
/** Post only rows already canonicalized by this module's owning operation. */
|
|
9964
|
+
async function postPreparedRuntimeMapRows(
|
|
9965
|
+
context: RuntimeApiContext & { playName: string },
|
|
9966
|
+
input: Parameters<typeof completeRuntimeMapRows>[1],
|
|
9967
|
+
submittedKeys: string[],
|
|
9968
|
+
): Promise<RuntimeMapRowsWriteResult> {
|
|
9969
|
+
const result = await postRuntimeApi<
|
|
9970
|
+
Partial<RuntimeMapRowsWriteResult> &
|
|
9971
|
+
Pick<RuntimeMapRowsWriteResult, 'updated' | 'fencedKeys'>
|
|
9972
|
+
>(context, {
|
|
9973
|
+
action: 'runtime_sheet_complete_map_rows',
|
|
9974
|
+
input,
|
|
9975
|
+
});
|
|
9976
|
+
const staleKeys =
|
|
9977
|
+
result.staleKeys ?? result.staleDroppedKeys ?? result.fencedKeys;
|
|
9978
|
+
return {
|
|
9979
|
+
...classifyRuntimeMapRowsWrite({
|
|
9980
|
+
submittedKeys,
|
|
9981
|
+
updated: result.updated,
|
|
9982
|
+
fencedKeys: staleKeys,
|
|
9983
|
+
// A gateway that predates stale reasons omits staleRows; keep it
|
|
9984
|
+
// absent so the caller classifies those keys as unreported.
|
|
9985
|
+
fencedRows: Array.isArray(result.staleRows)
|
|
9986
|
+
? parseRuntimeMapRowsFencedRows(result.staleRows)
|
|
9987
|
+
: undefined,
|
|
9988
|
+
}),
|
|
9989
|
+
// Preserve the gateway's compatibility classification during rollout.
|
|
9990
|
+
fencedKeys: result.fencedKeys,
|
|
9991
|
+
...(typeof result.runtimeTestPageTailHoldMs === 'number'
|
|
9992
|
+
? { runtimeTestPageTailHoldMs: result.runtimeTestPageTailHoldMs }
|
|
9993
|
+
: {}),
|
|
9994
|
+
};
|
|
9995
|
+
}
|
|
9996
|
+
|
|
9005
9997
|
/**
|
|
9006
9998
|
* Mark map rows terminal in the per-run scoped Postgres sheet table by key.
|
|
9007
9999
|
* Writes enriched output values into materialized physical columns, flips
|
|
@@ -9052,35 +10044,13 @@ export async function completeRuntimeMapRows(
|
|
|
9052
10044
|
runId: input.runId,
|
|
9053
10045
|
outputFields: input.outputFields ?? [],
|
|
9054
10046
|
});
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
const submittedKeys = keyedRows
|
|
9063
|
-
.map((row) => row.key)
|
|
9064
|
-
.filter((key): key is string => Boolean(key));
|
|
9065
|
-
const staleKeys =
|
|
9066
|
-
result.staleKeys ?? result.staleDroppedKeys ?? result.fencedKeys;
|
|
9067
|
-
return {
|
|
9068
|
-
...classifyRuntimeMapRowsWrite({
|
|
9069
|
-
submittedKeys,
|
|
9070
|
-
updated: result.updated,
|
|
9071
|
-
fencedKeys: staleKeys,
|
|
9072
|
-
// A gateway that predates stale reasons omits staleRows; keep it
|
|
9073
|
-
// absent so the caller classifies those keys as unreported.
|
|
9074
|
-
fencedRows: Array.isArray(result.staleRows)
|
|
9075
|
-
? parseRuntimeMapRowsFencedRows(result.staleRows)
|
|
9076
|
-
: undefined,
|
|
9077
|
-
}),
|
|
9078
|
-
// Preserve the gateway's compatibility classification during rollout.
|
|
9079
|
-
fencedKeys: result.fencedKeys,
|
|
9080
|
-
...(typeof result.runtimeTestPageTailHoldMs === 'number'
|
|
9081
|
-
? { runtimeTestPageTailHoldMs: result.runtimeTestPageTailHoldMs }
|
|
9082
|
-
: {}),
|
|
9083
|
-
};
|
|
10047
|
+
return postPreparedRuntimeMapRows(
|
|
10048
|
+
context,
|
|
10049
|
+
{ ...input, rows },
|
|
10050
|
+
keyedRows
|
|
10051
|
+
.map((row) => row.key)
|
|
10052
|
+
.filter((key): key is string => Boolean(key)),
|
|
10053
|
+
);
|
|
9084
10054
|
}
|
|
9085
10055
|
const sheetContract = augmentSheetContractWithDatasetFields({
|
|
9086
10056
|
contract: input.sheetContract,
|
|
@@ -9097,22 +10067,35 @@ export async function completeRuntimeMapRows(
|
|
|
9097
10067
|
let updatedRowCount = 0;
|
|
9098
10068
|
let fencedRowCount = 0;
|
|
9099
10069
|
const logCompletion = () => {
|
|
9100
|
-
|
|
9101
|
-
'
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
10070
|
+
const diagnostics: RuntimeSheetMapCompletionDiagnostics = {
|
|
10071
|
+
event: 'finished',
|
|
10072
|
+
outcome: completionOutcome,
|
|
10073
|
+
inputRows: input.rows.length,
|
|
10074
|
+
uniqueRows: uniqueRowCount,
|
|
10075
|
+
completedRows: completedRowCount,
|
|
10076
|
+
failedRows: failedRowCount,
|
|
10077
|
+
chunks: chunkCount,
|
|
10078
|
+
updatedRows: updatedRowCount,
|
|
10079
|
+
fencedRows: fencedRowCount,
|
|
10080
|
+
...completionDiagnostics,
|
|
10081
|
+
totalMs: Date.now() - completionStartedAt,
|
|
10082
|
+
};
|
|
10083
|
+
try {
|
|
10084
|
+
if (context.onSheetMapCompletion) {
|
|
10085
|
+
// Delivery is observational: neither a synchronous throw nor a rejected
|
|
10086
|
+
// asynchronous sink may replace the write's result or delay its return.
|
|
10087
|
+
void Promise.resolve(context.onSheetMapCompletion(diagnostics)).catch(
|
|
10088
|
+
() => {},
|
|
10089
|
+
);
|
|
10090
|
+
} else {
|
|
10091
|
+
console.info(
|
|
10092
|
+
'[runtime-sheet-map-completion]',
|
|
10093
|
+
JSON.stringify(diagnostics),
|
|
10094
|
+
);
|
|
10095
|
+
}
|
|
10096
|
+
} catch {
|
|
10097
|
+
// Product execution does not depend on operational telemetry delivery.
|
|
10098
|
+
}
|
|
9116
10099
|
};
|
|
9117
10100
|
let session: RuntimePostgresSession;
|
|
9118
10101
|
try {
|
|
@@ -9175,11 +10158,20 @@ export async function completeRuntimeMapRows(
|
|
|
9175
10158
|
runId: input.runId,
|
|
9176
10159
|
outputFields: input.outputFields ?? [],
|
|
9177
10160
|
});
|
|
10161
|
+
const sqlBatchBudget = {
|
|
10162
|
+
maxRows: DIRECT_POSTGRES_BATCH_SIZE,
|
|
10163
|
+
maxBytes: RUNTIME_MAP_SQL_BATCH_INPUT_BYTES,
|
|
10164
|
+
};
|
|
9178
10165
|
const chunks = chunkValues(completedRows, DIRECT_POSTGRES_BATCH_SIZE);
|
|
10166
|
+
const exactKeyChunks = chunks.map((chunk) =>
|
|
10167
|
+
chunkPreparedRuntimeSheetRows(chunk, sqlBatchBudget),
|
|
10168
|
+
);
|
|
9179
10169
|
const failedChunks = chunkValues(failedRows, DIRECT_POSTGRES_BATCH_SIZE);
|
|
9180
10170
|
completedRowCount = completedRows.length;
|
|
9181
10171
|
failedRowCount = failedRows.length;
|
|
9182
|
-
chunkCount =
|
|
10172
|
+
chunkCount =
|
|
10173
|
+
exactKeyChunks.reduce((total, group) => total + group.length, 0) +
|
|
10174
|
+
failedChunks.length;
|
|
9183
10175
|
const needsTransaction = true;
|
|
9184
10176
|
const outputFields = [...new Set(input.outputFields ?? [])];
|
|
9185
10177
|
const attemptId = input.attemptId?.trim() || null;
|
|
@@ -9205,15 +10197,18 @@ export async function completeRuntimeMapRows(
|
|
|
9205
10197
|
tableNamespace: input.tableNamespace,
|
|
9206
10198
|
sheetContract,
|
|
9207
10199
|
transactional: needsTransaction,
|
|
10200
|
+
concurrentColumnPreparation: true,
|
|
9208
10201
|
statementTimeoutMs: RUNTIME_SHEET_COMPLETION_STATEMENT_TIMEOUT_MS,
|
|
9209
10202
|
completionDiagnostics,
|
|
9210
10203
|
},
|
|
9211
|
-
async (client) => {
|
|
10204
|
+
async (client, columnLockMode) => {
|
|
9212
10205
|
const completed =
|
|
9213
10206
|
completedRows.length > 0
|
|
9214
10207
|
? await (async () => {
|
|
9215
10208
|
const chunkInput = {
|
|
10209
|
+
columnLockMode,
|
|
9216
10210
|
chunks,
|
|
10211
|
+
exactKeyChunks,
|
|
9217
10212
|
physicalColumnProjections: projections,
|
|
9218
10213
|
runId: input.runId,
|
|
9219
10214
|
attemptId,
|
|
@@ -9232,16 +10227,19 @@ export async function completeRuntimeMapRows(
|
|
|
9232
10227
|
'mapRowsUpdate',
|
|
9233
10228
|
() => completeRuntimeMapRowChunks(client, session, chunkInput),
|
|
9234
10229
|
);
|
|
9235
|
-
const inserted =
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
10230
|
+
const inserted =
|
|
10231
|
+
updated.allExactRowsUpdated === true
|
|
10232
|
+
? { inserted: 0 }
|
|
10233
|
+
: await measureRuntimeSheetPhase(
|
|
10234
|
+
completionDiagnostics,
|
|
10235
|
+
'missingRowsInsert',
|
|
10236
|
+
() =>
|
|
10237
|
+
insertMissingCompletedMapRowChunks(
|
|
10238
|
+
client,
|
|
10239
|
+
session,
|
|
10240
|
+
chunkInput,
|
|
10241
|
+
),
|
|
10242
|
+
);
|
|
9245
10243
|
return {
|
|
9246
10244
|
updated: updated.updated + inserted.inserted,
|
|
9247
10245
|
fencedRows: updated.fencedRows,
|
|
@@ -9255,6 +10253,7 @@ export async function completeRuntimeMapRows(
|
|
|
9255
10253
|
'failedRowsUpdate',
|
|
9256
10254
|
() =>
|
|
9257
10255
|
failRuntimeMapRowChunks(client, session, {
|
|
10256
|
+
columnLockMode,
|
|
9258
10257
|
chunks: failedChunks,
|
|
9259
10258
|
physicalColumnProjections: projections,
|
|
9260
10259
|
forceTerminal: input.forceFailedRows === true,
|
|
@@ -9278,14 +10277,22 @@ export async function completeRuntimeMapRows(
|
|
|
9278
10277
|
updated: completed.updated + failed.updated,
|
|
9279
10278
|
fencedRows: [...completed.fencedRows, ...failed.fencedRows],
|
|
9280
10279
|
});
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
|
|
10280
|
+
// This phase includes the availability probe and, when available, the
|
|
10281
|
+
// terminal membership capture. It is nested inside queryClientMs.
|
|
10282
|
+
await measureRuntimeSheetPhase(
|
|
10283
|
+
completionDiagnostics,
|
|
10284
|
+
'membershipCapture',
|
|
10285
|
+
async () => {
|
|
10286
|
+
if (await runtimeSheetMembershipCaptureAvailable(client, session)) {
|
|
10287
|
+
await captureRuntimeSheetTerminalMembership(client, session, {
|
|
10288
|
+
runId: input.runId,
|
|
10289
|
+
tableNamespace: input.tableNamespace,
|
|
10290
|
+
sheetContract,
|
|
10291
|
+
keys: classified.committedKeys,
|
|
10292
|
+
});
|
|
10293
|
+
}
|
|
10294
|
+
},
|
|
10295
|
+
);
|
|
9289
10296
|
return classified;
|
|
9290
10297
|
},
|
|
9291
10298
|
)
|
|
@@ -9318,7 +10325,15 @@ export function createCompleteRuntimeMapRowsOperation(
|
|
|
9318
10325
|
outputFields: input.outputFields ?? [],
|
|
9319
10326
|
}),
|
|
9320
10327
|
};
|
|
9321
|
-
|
|
10328
|
+
const submittedKeys = frozenInput.rows
|
|
10329
|
+
.map((row) => row.key)
|
|
10330
|
+
.filter((key): key is string => Boolean(key));
|
|
10331
|
+
// The operation owns this JSON-safe snapshot. Re-preparing it on each
|
|
10332
|
+
// capacity retry repeats full data/meta stringify+parse without changing
|
|
10333
|
+
// its canonical value. Ordinary public calls still prepare above.
|
|
10334
|
+
return frozenInput.rows.length === 0
|
|
10335
|
+
? () => completeRuntimeMapRows(context, frozenInput)
|
|
10336
|
+
: () => postPreparedRuntimeMapRows(context, frozenInput, submittedKeys);
|
|
9322
10337
|
}
|
|
9323
10338
|
|
|
9324
10339
|
export type RuntimeSheetDatasetReadInput = {
|