@stigmer/server 3.24.0 → 3.26.0
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/authorization/authorizer.d.ts +10 -4
- package/dist/authorization/authorizer.d.ts.map +1 -1
- package/dist/authorization/authorizer.js +11 -4
- package/dist/authorization/authorizer.js.map +1 -1
- package/dist/authorization/model/index.d.ts +4 -4
- package/dist/authorization/model/index.d.ts.map +1 -1
- package/dist/authorization/model/index.js +2 -0
- package/dist/authorization/model/index.js.map +1 -1
- package/dist/authorization/model/oauth_app.d.ts.map +1 -1
- package/dist/authorization/model/oauth_app.js +3 -2
- package/dist/authorization/model/oauth_app.js.map +1 -1
- package/dist/authorization/model/platform_client.d.ts +2 -0
- package/dist/authorization/model/platform_client.d.ts.map +1 -0
- package/dist/authorization/model/platform_client.js +31 -0
- package/dist/authorization/model/platform_client.js.map +1 -0
- package/dist/boot/compose.d.ts.map +1 -1
- package/dist/boot/compose.js +99 -13
- package/dist/boot/compose.js.map +1 -1
- package/dist/boot/list-indexes.d.ts +3 -0
- package/dist/boot/list-indexes.d.ts.map +1 -0
- package/dist/boot/list-indexes.js +24 -0
- package/dist/boot/list-indexes.js.map +1 -0
- package/dist/boot/route-shadowing.d.ts +25 -0
- package/dist/boot/route-shadowing.d.ts.map +1 -0
- package/dist/boot/route-shadowing.js +17 -0
- package/dist/boot/route-shadowing.js.map +1 -0
- package/dist/domain/agentexecution/controller.d.ts.map +1 -1
- package/dist/domain/agentexecution/controller.js +6 -8
- package/dist/domain/agentexecution/controller.js.map +1 -1
- package/dist/domain/agentexecution/list-index.d.ts +2 -0
- package/dist/domain/agentexecution/list-index.d.ts.map +1 -0
- package/dist/domain/agentexecution/list-index.js +23 -0
- package/dist/domain/agentexecution/list-index.js.map +1 -0
- package/dist/domain/agentexecution/steps.d.ts +17 -33
- package/dist/domain/agentexecution/steps.d.ts.map +1 -1
- package/dist/domain/agentexecution/steps.js +94 -95
- package/dist/domain/agentexecution/steps.js.map +1 -1
- package/dist/domain/agentexecution/usage.d.ts.map +1 -1
- package/dist/domain/agentexecution/usage.js +37 -14
- package/dist/domain/agentexecution/usage.js.map +1 -1
- package/dist/domain/artifact/controller.d.ts.map +1 -1
- package/dist/domain/artifact/controller.js +27 -15
- package/dist/domain/artifact/controller.js.map +1 -1
- package/dist/domain/artifact/list-index.d.ts +2 -0
- package/dist/domain/artifact/list-index.d.ts.map +1 -0
- package/dist/domain/artifact/list-index.js +21 -0
- package/dist/domain/artifact/list-index.js.map +1 -0
- package/dist/domain/iampolicy/membership.d.ts +6 -1
- package/dist/domain/iampolicy/membership.d.ts.map +1 -1
- package/dist/domain/iampolicy/membership.js +8 -2
- package/dist/domain/iampolicy/membership.js.map +1 -1
- package/dist/domain/identityaccount/constants.d.ts +13 -0
- package/dist/domain/identityaccount/constants.d.ts.map +1 -1
- package/dist/domain/identityaccount/constants.js +17 -0
- package/dist/domain/identityaccount/constants.js.map +1 -1
- package/dist/domain/identityaccount/controller.d.ts +3 -1
- package/dist/domain/identityaccount/controller.d.ts.map +1 -1
- package/dist/domain/identityaccount/controller.js +15 -6
- package/dist/domain/identityaccount/controller.js.map +1 -1
- package/dist/domain/identityaccount/operator.d.ts.map +1 -1
- package/dist/domain/identityaccount/operator.js +1 -0
- package/dist/domain/identityaccount/operator.js.map +1 -1
- package/dist/domain/identityaccount/provisioning.d.ts +18 -0
- package/dist/domain/identityaccount/provisioning.d.ts.map +1 -1
- package/dist/domain/identityaccount/provisioning.js +1 -0
- package/dist/domain/identityaccount/provisioning.js.map +1 -1
- package/dist/domain/identityaccount/resource-store.js +9 -5
- package/dist/domain/identityaccount/resource-store.js.map +1 -1
- package/dist/domain/identityaccount/steps.d.ts +11 -2
- package/dist/domain/identityaccount/steps.d.ts.map +1 -1
- package/dist/domain/identityaccount/steps.js +42 -4
- package/dist/domain/identityaccount/steps.js.map +1 -1
- package/dist/domain/identityaccount/store-contract.d.ts.map +1 -1
- package/dist/domain/identityaccount/store-contract.js +33 -0
- package/dist/domain/identityaccount/store-contract.js.map +1 -1
- package/dist/domain/identityaccount/store.d.ts +7 -5
- package/dist/domain/identityaccount/store.d.ts.map +1 -1
- package/dist/domain/identityaccount/store.js.map +1 -1
- package/dist/domain/platform/controller.d.ts +10 -0
- package/dist/domain/platform/controller.d.ts.map +1 -1
- package/dist/domain/platform/controller.js +2 -1
- package/dist/domain/platform/controller.js.map +1 -1
- package/dist/domain/platformclient/constants.d.ts +60 -0
- package/dist/domain/platformclient/constants.d.ts.map +1 -0
- package/dist/domain/platformclient/constants.js +78 -0
- package/dist/domain/platformclient/constants.js.map +1 -0
- package/dist/domain/platformclient/controller.d.ts +59 -0
- package/dist/domain/platformclient/controller.d.ts.map +1 -0
- package/dist/domain/platformclient/controller.js +191 -0
- package/dist/domain/platformclient/controller.js.map +1 -0
- package/dist/domain/platformclient/credentials.d.ts +13 -0
- package/dist/domain/platformclient/credentials.d.ts.map +1 -0
- package/dist/domain/platformclient/credentials.js +56 -0
- package/dist/domain/platformclient/credentials.js.map +1 -0
- package/dist/domain/platformclient/mint.d.ts +23 -0
- package/dist/domain/platformclient/mint.d.ts.map +1 -0
- package/dist/domain/platformclient/mint.js +254 -0
- package/dist/domain/platformclient/mint.js.map +1 -0
- package/dist/domain/platformclient/origin-guard.d.ts +9 -0
- package/dist/domain/platformclient/origin-guard.d.ts.map +1 -0
- package/dist/domain/platformclient/origin-guard.js +81 -0
- package/dist/domain/platformclient/origin-guard.js.map +1 -0
- package/dist/domain/platformclient/resource-store.d.ts +4 -0
- package/dist/domain/platformclient/resource-store.d.ts.map +1 -0
- package/dist/domain/platformclient/resource-store.js +117 -0
- package/dist/domain/platformclient/resource-store.js.map +1 -0
- package/dist/domain/platformclient/steps.d.ts +51 -0
- package/dist/domain/platformclient/steps.d.ts.map +1 -0
- package/dist/domain/platformclient/steps.js +339 -0
- package/dist/domain/platformclient/steps.js.map +1 -0
- package/dist/domain/platformclient/store-contract.d.ts +8 -0
- package/dist/domain/platformclient/store-contract.d.ts.map +1 -0
- package/dist/domain/platformclient/store-contract.js +208 -0
- package/dist/domain/platformclient/store-contract.js.map +1 -0
- package/dist/domain/platformclient/store.d.ts +58 -0
- package/dist/domain/platformclient/store.d.ts.map +1 -0
- package/dist/domain/platformclient/store.js +13 -0
- package/dist/domain/platformclient/store.js.map +1 -0
- package/dist/domain/platformclient/system-managed.d.ts +18 -0
- package/dist/domain/platformclient/system-managed.d.ts.map +1 -0
- package/dist/domain/platformclient/system-managed.js +80 -0
- package/dist/domain/platformclient/system-managed.js.map +1 -0
- package/dist/domain/platformclient/token-controller.d.ts +11 -0
- package/dist/domain/platformclient/token-controller.d.ts.map +1 -0
- package/dist/domain/platformclient/token-controller.js +32 -0
- package/dist/domain/platformclient/token-controller.js.map +1 -0
- package/dist/domain/platformclient/verifier.d.ts +10 -0
- package/dist/domain/platformclient/verifier.d.ts.map +1 -0
- package/dist/domain/platformclient/verifier.js +76 -0
- package/dist/domain/platformclient/verifier.js.map +1 -0
- package/dist/domain/session/list-index.d.ts +4 -0
- package/dist/domain/session/list-index.d.ts.map +1 -0
- package/dist/domain/session/list-index.js +26 -0
- package/dist/domain/session/list-index.js.map +1 -0
- package/dist/domain/session/steps.d.ts +7 -8
- package/dist/domain/session/steps.d.ts.map +1 -1
- package/dist/domain/session/steps.js +76 -78
- package/dist/domain/session/steps.js.map +1 -1
- package/dist/domain/workflowexecution/controller.d.ts.map +1 -1
- package/dist/domain/workflowexecution/controller.js +53 -55
- package/dist/domain/workflowexecution/controller.js.map +1 -1
- package/dist/domain/workflowexecution/execution-filter.d.ts +4 -0
- package/dist/domain/workflowexecution/execution-filter.d.ts.map +1 -1
- package/dist/domain/workflowexecution/execution-filter.js +11 -5
- package/dist/domain/workflowexecution/execution-filter.js.map +1 -1
- package/dist/domain/workflowexecution/get-execution-summary.d.ts.map +1 -1
- package/dist/domain/workflowexecution/get-execution-summary.js +23 -17
- package/dist/domain/workflowexecution/get-execution-summary.js.map +1 -1
- package/dist/domain/workflowexecution/list-index.d.ts +2 -0
- package/dist/domain/workflowexecution/list-index.d.ts.map +1 -0
- package/dist/domain/workflowexecution/list-index.js +21 -0
- package/dist/domain/workflowexecution/list-index.js.map +1 -0
- package/dist/domain/workflowexecution/list-pending-approvals.d.ts.map +1 -1
- package/dist/domain/workflowexecution/list-pending-approvals.js +69 -32
- package/dist/domain/workflowexecution/list-pending-approvals.js.map +1 -1
- package/dist/domain/workflowexecution/queries.d.ts +30 -4
- package/dist/domain/workflowexecution/queries.d.ts.map +1 -1
- package/dist/domain/workflowexecution/queries.js +76 -22
- package/dist/domain/workflowexecution/queries.js.map +1 -1
- package/dist/encryption/key-manager.d.ts +20 -3
- package/dist/encryption/key-manager.d.ts.map +1 -1
- package/dist/encryption/key-manager.js +64 -42
- package/dist/encryption/key-manager.js.map +1 -1
- package/dist/extensions/caller-guards.d.ts +8 -3
- package/dist/extensions/caller-guards.d.ts.map +1 -1
- package/dist/extensions/drivers.d.ts +37 -0
- package/dist/extensions/drivers.d.ts.map +1 -1
- package/dist/extensions/guest-token-minting.d.ts +26 -0
- package/dist/extensions/guest-token-minting.d.ts.map +1 -0
- package/dist/extensions/guest-token-minting.js +2 -0
- package/dist/extensions/guest-token-minting.js.map +1 -0
- package/dist/extensions/identity.d.ts +6 -4
- package/dist/extensions/identity.d.ts.map +1 -1
- package/dist/extensions/identity.js.map +1 -1
- package/dist/extensions/list-read-scope.d.ts +9 -12
- package/dist/extensions/list-read-scope.d.ts.map +1 -1
- package/dist/extensions/list-read-scope.js.map +1 -1
- package/dist/extensions/registry.d.ts +30 -1
- package/dist/extensions/registry.d.ts.map +1 -1
- package/dist/extensions/registry.js +34 -1
- package/dist/extensions/registry.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/interceptors/auth.d.ts +14 -0
- package/dist/pipeline/interceptors/auth.d.ts.map +1 -1
- package/dist/pipeline/interceptors/auth.js +24 -0
- package/dist/pipeline/interceptors/auth.js.map +1 -1
- package/dist/pipeline/steps/build-update-state.d.ts +11 -1
- package/dist/pipeline/steps/build-update-state.d.ts.map +1 -1
- package/dist/pipeline/steps/build-update-state.js +5 -2
- package/dist/pipeline/steps/build-update-state.js.map +1 -1
- package/dist/pipeline/steps/list-page.d.ts +63 -0
- package/dist/pipeline/steps/list-page.d.ts.map +1 -0
- package/dist/pipeline/steps/list-page.js +187 -0
- package/dist/pipeline/steps/list-page.js.map +1 -0
- package/dist/platformtoken/envelope.d.ts +100 -0
- package/dist/platformtoken/envelope.d.ts.map +1 -0
- package/dist/platformtoken/envelope.js +184 -0
- package/dist/platformtoken/envelope.js.map +1 -0
- package/dist/platformtoken/key-ring.d.ts +85 -0
- package/dist/platformtoken/key-ring.d.ts.map +1 -0
- package/dist/platformtoken/key-ring.js +164 -0
- package/dist/platformtoken/key-ring.js.map +1 -0
- package/dist/query/activity/handler.d.ts +6 -7
- package/dist/query/activity/handler.d.ts.map +1 -1
- package/dist/query/activity/handler.js +42 -59
- package/dist/query/activity/handler.js.map +1 -1
- package/dist/store/interface.d.ts +29 -0
- package/dist/store/interface.d.ts.map +1 -1
- package/dist/store/interface.js.map +1 -1
- package/dist/store/list-index.d.ts +123 -0
- package/dist/store/list-index.d.ts.map +1 -0
- package/dist/store/list-index.js +277 -0
- package/dist/store/list-index.js.map +1 -0
- package/dist/store/logger.d.ts +3 -0
- package/dist/store/logger.d.ts.map +1 -1
- package/dist/store/logger.js +5 -1
- package/dist/store/logger.js.map +1 -1
- package/dist/store/postgres/migrations.d.ts +10 -6
- package/dist/store/postgres/migrations.d.ts.map +1 -1
- package/dist/store/postgres/migrations.js +64 -6
- package/dist/store/postgres/migrations.js.map +1 -1
- package/dist/store/postgres/store.d.ts +26 -5
- package/dist/store/postgres/store.d.ts.map +1 -1
- package/dist/store/postgres/store.js +377 -22
- package/dist/store/postgres/store.js.map +1 -1
- package/dist/store/sqlite/migrations.d.ts +11 -5
- package/dist/store/sqlite/migrations.d.ts.map +1 -1
- package/dist/store/sqlite/migrations.js +50 -4
- package/dist/store/sqlite/migrations.js.map +1 -1
- package/dist/store/sqlite/store.d.ts +18 -3
- package/dist/store/sqlite/store.d.ts.map +1 -1
- package/dist/store/sqlite/store.js +321 -15
- package/dist/store/sqlite/store.js.map +1 -1
- package/package.json +6 -6
- package/src/authorization/README.md +1 -1
- package/src/authorization/__tests__/authorizer.test.ts +6 -2
- package/src/authorization/__tests__/drivers.ts +6 -1
- package/src/authorization/__tests__/wire-permissions.test.ts +1 -0
- package/src/authorization/authorizer.ts +11 -4
- package/src/authorization/model/__tests__/registry.test.ts +8 -1
- package/src/authorization/model/index.ts +6 -4
- package/src/authorization/model/oauth_app.ts +3 -2
- package/src/authorization/model/platform_client.ts +43 -0
- package/src/boot/__tests__/auth-enabled-composition.test.ts +2 -1
- package/src/boot/__tests__/list-indexes.test.ts +65 -0
- package/src/boot/__tests__/route-shadowing.test.ts +67 -0
- package/src/boot/compose.ts +113 -18
- package/src/boot/list-indexes.ts +25 -0
- package/src/boot/route-shadowing.ts +47 -0
- package/src/domain/agentexecution/__tests__/agentexecution.test.ts +85 -3
- package/src/domain/agentexecution/controller.ts +8 -10
- package/src/domain/agentexecution/list-index.ts +24 -0
- package/src/domain/agentexecution/steps.ts +118 -115
- package/src/domain/agentexecution/usage.ts +38 -17
- package/src/domain/artifact/controller.ts +28 -18
- package/src/domain/artifact/list-index.ts +22 -0
- package/src/domain/iampolicy/__tests__/membership.test.ts +7 -0
- package/src/domain/iampolicy/membership.ts +8 -2
- package/src/domain/identityaccount/__tests__/platform-client-accounts.test.ts +124 -0
- package/src/domain/identityaccount/__tests__/resource-store.test.ts +1 -0
- package/src/domain/identityaccount/constants.ts +20 -0
- package/src/domain/identityaccount/controller.ts +16 -4
- package/src/domain/identityaccount/operator.ts +1 -0
- package/src/domain/identityaccount/provisioning.ts +17 -0
- package/src/domain/identityaccount/resource-store.ts +9 -5
- package/src/domain/identityaccount/steps.ts +58 -3
- package/src/domain/identityaccount/store-contract.ts +51 -0
- package/src/domain/identityaccount/store.ts +7 -5
- package/src/domain/platform/__tests__/platform.test.ts +7 -1
- package/src/domain/platform/controller.ts +12 -1
- package/src/domain/platformclient/__tests__/controller.test.ts +397 -0
- package/src/domain/platformclient/__tests__/credentials.test.ts +46 -0
- package/src/domain/platformclient/__tests__/mint.test.ts +415 -0
- package/src/domain/platformclient/__tests__/resource-store.test.ts +140 -0
- package/src/domain/platformclient/__tests__/system-managed.test.ts +101 -0
- package/src/domain/platformclient/__tests__/verifier-and-guard.test.ts +207 -0
- package/src/domain/platformclient/constants.ts +120 -0
- package/src/domain/platformclient/controller.ts +449 -0
- package/src/domain/platformclient/credentials.ts +68 -0
- package/src/domain/platformclient/mint.ts +412 -0
- package/src/domain/platformclient/origin-guard.ts +117 -0
- package/src/domain/platformclient/resource-store.ts +150 -0
- package/src/domain/platformclient/steps.ts +472 -0
- package/src/domain/platformclient/store-contract.ts +318 -0
- package/src/domain/platformclient/store.ts +63 -0
- package/src/domain/platformclient/system-managed.ts +115 -0
- package/src/domain/platformclient/token-controller.ts +49 -0
- package/src/domain/platformclient/verifier.ts +106 -0
- package/src/domain/session/list-index.ts +28 -0
- package/src/domain/session/steps.ts +108 -123
- package/src/domain/workflowexecution/__tests__/list-read-scope-summaries.test.ts +155 -99
- package/src/domain/workflowexecution/__tests__/workflowexecution.test.ts +193 -31
- package/src/domain/workflowexecution/controller.ts +54 -82
- package/src/domain/workflowexecution/execution-filter.ts +20 -7
- package/src/domain/workflowexecution/get-execution-summary.ts +31 -24
- package/src/domain/workflowexecution/list-index.ts +22 -0
- package/src/domain/workflowexecution/list-pending-approvals.ts +82 -25
- package/src/domain/workflowexecution/queries.ts +134 -23
- package/src/encryption/key-manager.ts +87 -44
- package/src/extensions/__tests__/extension-composition.test.ts +3 -2
- package/src/extensions/__tests__/platform-client-composed.test.ts +288 -0
- package/src/extensions/__tests__/registry.test.ts +4 -1
- package/src/extensions/__tests__/tier-truthfulness.test.ts +8 -0
- package/src/extensions/caller-guards.ts +8 -3
- package/src/extensions/drivers.ts +37 -0
- package/src/extensions/guest-token-minting.ts +32 -0
- package/src/extensions/identity.ts +6 -4
- package/src/extensions/list-read-scope.ts +9 -12
- package/src/extensions/registry.ts +77 -3
- package/src/index.ts +65 -1
- package/src/pipeline/interceptors/auth.ts +27 -0
- package/src/pipeline/steps/__tests__/list-page.test.ts +186 -0
- package/src/pipeline/steps/__tests__/references.test.ts +2 -0
- package/src/pipeline/steps/build-update-state.ts +5 -2
- package/src/pipeline/steps/list-page.ts +284 -0
- package/src/platformtoken/__tests__/envelope.test.ts +277 -0
- package/src/platformtoken/__tests__/key-ring.test.ts +205 -0
- package/src/platformtoken/envelope.ts +276 -0
- package/src/platformtoken/key-ring.ts +266 -0
- package/src/query/activity/handler.ts +57 -78
- package/src/runnerauth/__tests__/runner-subject-composed.test.ts +9 -3
- package/src/store/README.md +51 -32
- package/src/store/__tests__/list-index.measure.test.ts +293 -0
- package/src/store/__tests__/list-index.test.ts +328 -0
- package/src/store/__tests__/store-contract.ts +346 -3
- package/src/store/interface.ts +39 -0
- package/src/store/list-index.ts +465 -0
- package/src/store/logger.ts +8 -1
- package/src/store/postgres/__tests__/list-index-repair.test.ts +133 -0
- package/src/store/postgres/__tests__/migrations.test.ts +27 -19
- package/src/store/postgres/__tests__/store-contract.test.ts +50 -3
- package/src/store/postgres/migrations.ts +69 -7
- package/src/store/postgres/store.ts +485 -29
- package/src/store/sqlite/__tests__/migrations.test.ts +51 -7
- package/src/store/sqlite/__tests__/store-contract.test.ts +70 -16
- package/src/store/sqlite/__tests__/support.ts +12 -3
- package/src/store/sqlite/migrations.ts +57 -6
- package/src/store/sqlite/store.ts +462 -21
|
@@ -5,18 +5,15 @@
|
|
|
5
5
|
*
|
|
6
6
|
* This is the OSS twin of the cloud's ListRecentActivityHandler; the
|
|
7
7
|
* merge, ordering, projection, and filtering semantics are deliberately
|
|
8
|
-
* identical (stigmer#461).
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* census lane 22) both loads narrow to the caller's authorized ids ∩
|
|
18
|
-
* the request's org when non-blank — the Java handler's two
|
|
19
|
-
* listAuthorizedResourceIds calls and its findRecentByIdsAndOrg org arm.
|
|
8
|
+
* identical (stigmer#461). Both loads read the request's org through the
|
|
9
|
+
* list index (every org when blank) — the same narrowing the per-kind
|
|
10
|
+
* lists it summarizes apply, so recents is never stricter than they are —
|
|
11
|
+
* and, with a composed ListReadScope (census lane 22), offer those rows to
|
|
12
|
+
* its restrict verb: one read per kind, exact, where the Java handler
|
|
13
|
+
* enumerated the caller's authorized ids and then scanned. Recents orders
|
|
14
|
+
* by the last update, not by creation, so each kind's org is read whole
|
|
15
|
+
* and merged in memory (each kind's newest page_size rows are a superset
|
|
16
|
+
* of its contribution to the merged page).
|
|
20
17
|
*
|
|
21
18
|
* Proven by __tests__/handler.test.ts (Go's handler_test.go arms) and
|
|
22
19
|
* activity.conformance.test.ts on local.
|
|
@@ -35,7 +32,9 @@ import type {
|
|
|
35
32
|
RecentActivityEntry,
|
|
36
33
|
} from "@stigmer/protos/ai/stigmer/activity/v1/io_pb";
|
|
37
34
|
import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
35
|
+
import type { Session } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
38
36
|
import { WorkflowExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
37
|
+
import type { WorkflowExecution } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/api_pb";
|
|
39
38
|
import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/workflowexecution/v1/enum_pb";
|
|
40
39
|
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
41
40
|
import type { ApiResourceAudit } from "@stigmer/protos/ai/stigmer/commons/apiresource/status_pb";
|
|
@@ -44,6 +43,9 @@ import type { ApiResourceMetadata } from "@stigmer/protos/ai/stigmer/commons/api
|
|
|
44
43
|
import type { Logger } from "../../boot/logger.js";
|
|
45
44
|
import type { CallerIdentity } from "../../extensions/identity.js";
|
|
46
45
|
import type { ListReadScope } from "../../extensions/list-read-scope.js";
|
|
46
|
+
import { restrictListByReadScope } from "../../extensions/list-read-scope.js";
|
|
47
|
+
import { sessionListIndex } from "../../domain/session/list-index.js";
|
|
48
|
+
import { workflowExecutionListIndex } from "../../domain/workflowexecution/list-index.js";
|
|
47
49
|
import type { Store } from "../../store/interface.js";
|
|
48
50
|
|
|
49
51
|
/**
|
|
@@ -91,10 +93,9 @@ export class ActivityHandler {
|
|
|
91
93
|
) {}
|
|
92
94
|
|
|
93
95
|
/**
|
|
94
|
-
* Go ListRecentActivity: load both kinds
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* org (blank = permission-bounded across orgs, the repo convention).
|
|
96
|
+
* Go ListRecentActivity: load both kinds (the request's org, every org
|
|
97
|
+
* when blank; the scope's restrict verb when one is composed), project
|
|
98
|
+
* to sidebar entries, merge-sort newest-first, trim to the page.
|
|
98
99
|
*/
|
|
99
100
|
async listRecentActivity(
|
|
100
101
|
request: ListRecentActivityRequest,
|
|
@@ -102,21 +103,8 @@ export class ActivityHandler {
|
|
|
102
103
|
): Promise<ListRecentActivityResponse> {
|
|
103
104
|
const pageSize = normalizePageSize(request.pageSize);
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (this.listReadScope !== undefined) {
|
|
108
|
-
sessionScope = await this.listReadScope.authorizedResourceIds(
|
|
109
|
-
identity,
|
|
110
|
-
ApiResourceKind.session,
|
|
111
|
-
);
|
|
112
|
-
executionScope = await this.listReadScope.authorizedResourceIds(
|
|
113
|
-
identity,
|
|
114
|
-
ApiResourceKind.workflow_execution,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const sessions = await this.loadSessions(sessionScope, request.org);
|
|
119
|
-
const executions = await this.loadExecutions(executionScope, request.org);
|
|
106
|
+
const sessions = await this.loadSessions(identity, request.org);
|
|
107
|
+
const executions = await this.loadExecutions(identity, request.org);
|
|
120
108
|
|
|
121
109
|
// Sessions before executions, then a stable sort: entries with equal
|
|
122
110
|
// timestamps keep this insertion order — the same tie-break the
|
|
@@ -146,32 +134,36 @@ export class ActivityHandler {
|
|
|
146
134
|
}
|
|
147
135
|
|
|
148
136
|
/**
|
|
149
|
-
* Go loadSessions:
|
|
150
|
-
*
|
|
137
|
+
* Go loadSessions: the org's personal sessions the caller may see,
|
|
138
|
+
* projected to recents entries; runtime-originated sessions excluded.
|
|
151
139
|
*/
|
|
152
140
|
private async loadSessions(
|
|
153
|
-
|
|
141
|
+
identity: CallerIdentity,
|
|
154
142
|
org: string,
|
|
155
143
|
): Promise<RecentActivityEntry[]> {
|
|
156
|
-
const rows = await this.store.
|
|
157
|
-
const
|
|
144
|
+
const rows = await this.store.queryResources(sessionListIndex, { org });
|
|
145
|
+
const personal: Session[] = [];
|
|
158
146
|
for (const row of rows) {
|
|
159
|
-
let session;
|
|
147
|
+
let session: Session;
|
|
160
148
|
try {
|
|
161
|
-
session = fromBinary(SessionSchema, row);
|
|
149
|
+
session = fromBinary(SessionSchema, row.data);
|
|
162
150
|
} catch {
|
|
163
151
|
this.logger.warn("Skipping undecodable session row in recent activity");
|
|
164
152
|
continue;
|
|
165
153
|
}
|
|
166
|
-
if (hasRuntimeOriginLabel(session.metadata)) {
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
if (
|
|
170
|
-
authorizedIds !== undefined &&
|
|
171
|
-
!isVisibleUnderScope(authorizedIds, org, session.metadata)
|
|
172
|
-
) {
|
|
173
|
-
continue;
|
|
154
|
+
if (!hasRuntimeOriginLabel(session.metadata)) {
|
|
155
|
+
personal.push(session);
|
|
174
156
|
}
|
|
157
|
+
}
|
|
158
|
+
const visible = await restrictListByReadScope(
|
|
159
|
+
this.listReadScope,
|
|
160
|
+
identity,
|
|
161
|
+
ApiResourceKind.session,
|
|
162
|
+
personal,
|
|
163
|
+
"",
|
|
164
|
+
);
|
|
165
|
+
const entries: RecentActivityEntry[] = [];
|
|
166
|
+
for (const session of visible) {
|
|
175
167
|
entries.push(
|
|
176
168
|
create(RecentActivityEntrySchema, {
|
|
177
169
|
id: session.metadata?.id ?? "",
|
|
@@ -184,31 +176,33 @@ export class ActivityHandler {
|
|
|
184
176
|
return entries;
|
|
185
177
|
}
|
|
186
178
|
|
|
187
|
-
/** Go loadExecutions:
|
|
179
|
+
/** Go loadExecutions: the org's workflow executions the caller may see, projected. */
|
|
188
180
|
private async loadExecutions(
|
|
189
|
-
|
|
181
|
+
identity: CallerIdentity,
|
|
190
182
|
org: string,
|
|
191
183
|
): Promise<RecentActivityEntry[]> {
|
|
192
|
-
const rows = await this.store.
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
const
|
|
184
|
+
const rows = await this.store.queryResources(workflowExecutionListIndex, {
|
|
185
|
+
org,
|
|
186
|
+
});
|
|
187
|
+
const decoded: WorkflowExecution[] = [];
|
|
196
188
|
for (const row of rows) {
|
|
197
|
-
let execution;
|
|
198
189
|
try {
|
|
199
|
-
|
|
190
|
+
decoded.push(fromBinary(WorkflowExecutionSchema, row.data));
|
|
200
191
|
} catch {
|
|
201
192
|
this.logger.warn(
|
|
202
193
|
"Skipping undecodable workflow execution row in recent activity",
|
|
203
194
|
);
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
if (
|
|
207
|
-
authorizedIds !== undefined &&
|
|
208
|
-
!isVisibleUnderScope(authorizedIds, org, execution.metadata)
|
|
209
|
-
) {
|
|
210
|
-
continue;
|
|
211
195
|
}
|
|
196
|
+
}
|
|
197
|
+
const visible = await restrictListByReadScope(
|
|
198
|
+
this.listReadScope,
|
|
199
|
+
identity,
|
|
200
|
+
ApiResourceKind.workflow_execution,
|
|
201
|
+
decoded,
|
|
202
|
+
"",
|
|
203
|
+
);
|
|
204
|
+
const entries: RecentActivityEntry[] = [];
|
|
205
|
+
for (const execution of visible) {
|
|
212
206
|
const name = execution.metadata?.name ?? "";
|
|
213
207
|
entries.push(
|
|
214
208
|
create(RecentActivityEntrySchema, {
|
|
@@ -217,7 +211,8 @@ export class ActivityHandler {
|
|
|
217
211
|
subject: name === "" ? UNTITLED_EXECUTION_SUBJECT : name,
|
|
218
212
|
updatedAt: extractUpdatedAt(execution.status?.audit),
|
|
219
213
|
status: resolvePhase(
|
|
220
|
-
execution.status?.phase ??
|
|
214
|
+
execution.status?.phase ??
|
|
215
|
+
ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED,
|
|
221
216
|
),
|
|
222
217
|
}),
|
|
223
218
|
);
|
|
@@ -226,22 +221,6 @@ export class ActivityHandler {
|
|
|
226
221
|
}
|
|
227
222
|
}
|
|
228
223
|
|
|
229
|
-
/**
|
|
230
|
-
* The scoped-visibility test both loads share when a scope is composed:
|
|
231
|
-
* the row's id must be authorized and, when the request names an org, the
|
|
232
|
-
* row must belong to it (the Java findRecentByIdsAndOrg arm).
|
|
233
|
-
*/
|
|
234
|
-
function isVisibleUnderScope(
|
|
235
|
-
authorizedIds: ReadonlySet<string>,
|
|
236
|
-
org: string,
|
|
237
|
-
metadata: ApiResourceMetadata | undefined,
|
|
238
|
-
): boolean {
|
|
239
|
-
if (!authorizedIds.has(metadata?.id ?? "")) {
|
|
240
|
-
return false;
|
|
241
|
-
}
|
|
242
|
-
return org === "" || (metadata?.org ?? "") === org;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
224
|
/** Go normalizePageSize: ≤0 → default 30; >100 → cap 100. */
|
|
246
225
|
export function normalizePageSize(requested: number): number {
|
|
247
226
|
if (requested <= 0) {
|
|
@@ -245,9 +245,13 @@ describe("the built-in posture (OIDC, no unit Authorizer): the runner acts as th
|
|
|
245
245
|
);
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
it("the chain is apikey → runner → oidc: ours claims the server's own token before the OIDC verifier can fault on it", () => {
|
|
248
|
+
it("the chain is apikey → platform-client → runner → oidc: ours claims the server's own token before the OIDC verifier can fault on it", () => {
|
|
249
|
+
// The platform-client verifier claims only `iss: "stigmer"` user tokens
|
|
250
|
+
// and a runner token carries no `iss`, so the two lanes are disjoint and
|
|
251
|
+
// both sit ahead of the OIDC verifier for the same reason.
|
|
249
252
|
expect(server.identityVerifiers.map((verifier) => verifier.name)).toEqual([
|
|
250
253
|
"apikey",
|
|
254
|
+
"platform-client",
|
|
251
255
|
RUNNER_VERIFIER_NAME,
|
|
252
256
|
"oidc",
|
|
253
257
|
]);
|
|
@@ -489,9 +493,11 @@ describe("a unit's own Authorizer: no runner verifier is composed", () => {
|
|
|
489
493
|
rmSync(dir, { recursive: true, force: true });
|
|
490
494
|
});
|
|
491
495
|
|
|
492
|
-
it("the chain is apikey then the unit's verifiers — the composition owns its credential story", () => {
|
|
496
|
+
it("the chain is apikey, platform-client, then the unit's verifiers — the composition owns its credential story", () => {
|
|
493
497
|
const names = server.identityVerifiers.map((verifier) => verifier.name);
|
|
494
|
-
|
|
498
|
+
// The PlatformClient lane is core in every posture that verifies
|
|
499
|
+
// callers; the runner-subject lane is the built-in Authorizer's alone.
|
|
500
|
+
expect(names).toEqual(["apikey", "platform-client", fakeVerifier.name]);
|
|
495
501
|
expect(names).not.toContain(RUNNER_VERIFIER_NAME);
|
|
496
502
|
});
|
|
497
503
|
});
|
package/src/store/README.md
CHANGED
|
@@ -1,40 +1,59 @@
|
|
|
1
1
|
# store/ — the persistence layer
|
|
2
2
|
|
|
3
3
|
Ports `backend/libs/go/store` (D2 §3, DD-003): `interface.ts` is the
|
|
4
|
-
driver-agnostic contract (surface-for-surface with Go's `store.Store`, plus
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
driver-agnostic contract (surface-for-surface with Go's `store.Store`, plus the
|
|
5
|
+
consolidated members Go kept behind the `DB()` escape hatch — bootstrap state,
|
|
6
|
+
signal dedupe, MCP OAuth). Two drivers implement it:
|
|
7
7
|
|
|
8
|
-
- `sqlite/` — the laptop-tier default (`node:sqlite`, zero-config, one
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
- `sqlite/` — the laptop-tier default (`node:sqlite`, zero-config, one file)
|
|
9
|
+
with its versioned migration chain (v1–v6 adopted from Go DDL-faithful, then
|
|
10
|
+
this port's own steps, each named at its constant in `sqlite/migrations.ts`).
|
|
11
11
|
- `postgres/` — the self-host/team tier (DD-010: `pg` pool, row-level
|
|
12
|
-
`FOR UPDATE` atomicity, its own independent migration chain
|
|
13
|
-
|
|
12
|
+
`FOR UPDATE` atomicity, its own independent migration chain with real indexes,
|
|
13
|
+
`tsvector`/`tsquery` search per DD-009).
|
|
14
14
|
|
|
15
|
-
Selection is boot config (`boot/config.ts`): `DATABASE_URL` set → Postgres
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
Selection is boot config (`boot/config.ts`): `DATABASE_URL` set → Postgres (it
|
|
16
|
+
wins when both are set — `DB_PATH` always has a default value); else sqlite on
|
|
17
|
+
`DB_PATH`. Domain code depends on `interface.ts` only — never on a driver
|
|
18
|
+
directory. Driver-neutral helpers shared by both drivers live here
|
|
19
|
+
(`proto-fields.ts` reflection + scan semantics, `list-index.ts`, `logger.ts`).
|
|
20
|
+
|
|
21
|
+
## The list index
|
|
22
|
+
|
|
23
|
+
A list lane reads one organization's or one parent's rows in creation order
|
|
24
|
+
through `Store.queryResources`, never by decoding the whole kind.
|
|
25
|
+
`list-index.ts` is the one derivation: a kind's declaration (beside its lanes,
|
|
26
|
+
`domain/<kind>/list-index.ts`) names its keys as data, the composition root
|
|
27
|
+
opens the store with the one list of declarations (`boot/list-indexes.ts`), and
|
|
28
|
+
every driver keeps a declared row's facts (organization, creation instant, keys)
|
|
29
|
+
in the same statement as the row.
|
|
30
|
+
|
|
31
|
+
Reads are exact whoever wrote the rows. A row whose facts are not proven current
|
|
32
|
+
— written by a binary that does not know the index, as the old pod does while a
|
|
33
|
+
roll overlaps it with the new one, or under another revision of the declaration
|
|
34
|
+
— is evaluated from its bytes and repaired by a compare-and-set on those bytes.
|
|
35
|
+
The migrations that add the index are schema only; the reconciliation each
|
|
36
|
+
driver runs at open derives every unproven row, so the unproven set is empty in
|
|
37
|
+
steady state and reads stay one index range. Every read shape names or forces
|
|
38
|
+
its index, because a freshly written table has no planner statistics.
|
|
39
|
+
|
|
40
|
+
## Contract and continuity
|
|
20
41
|
|
|
21
42
|
The behavioral contract both drivers must satisfy identically is
|
|
22
|
-
`__tests__/store-contract.ts`, invoked by each driver's
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
chain has no adoption story by construction — no Postgres database
|
|
40
|
-
predates its driver.
|
|
43
|
+
`__tests__/store-contract.ts`, invoked by each driver's `store-contract.test.ts`
|
|
44
|
+
(sqlite always; Postgres under `TEST_DATABASE_URL` — visible skips locally, a
|
|
45
|
+
real service container in CI). Driver-physical behavior stays in each driver's
|
|
46
|
+
own tests. One deliberate semantic difference is recorded in DD-010: sqlite
|
|
47
|
+
serializes ALL writes globally as a side effect of its single synchronous
|
|
48
|
+
connection; Postgres guarantees per-resource atomicity only — the interface
|
|
49
|
+
contract is the narrower one.
|
|
50
|
+
|
|
51
|
+
Schema continuity across the Go cutover (any Go-created database adopts forward)
|
|
52
|
+
is proven by `sqlite/__tests__/migrations.test.ts` against a real Go-created
|
|
53
|
+
fixture. The fixture is FROZEN: the Go server retired (go-server-retirement, D4
|
|
54
|
+
#25), its schema can no longer change, and the committed v6 dump is the
|
|
55
|
+
permanent record of what real pre-cutover databases look like. The generator
|
|
56
|
+
script (`scripts/regen-go-db-fixture.sh`) lived until #25 and remains in git
|
|
57
|
+
history should the fixture ever need forensic regeneration. The Postgres chain
|
|
58
|
+
has no adoption story by construction — no Postgres database predates its
|
|
59
|
+
driver.
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a list read costs at production's shape, measured on both drivers.
|
|
3
|
+
* Gated on `STORE_MEASURE=1` and SKIPPED otherwise (the
|
|
4
|
+
* `AUTHORIZATION_MEASURE` idiom of authorization/__tests__/
|
|
5
|
+
* list-read-scope.measure.test.ts): seeding tens of megabytes takes a
|
|
6
|
+
* while, and the numbers are recorded beside the change that moves them,
|
|
7
|
+
* not asserted — no budget is ruled for open source.
|
|
8
|
+
*
|
|
9
|
+
* The seed is the hosted edition's population as read on 2026-09-23:
|
|
10
|
+
* 1,279 agent executions averaging about 37 KB each (the kind's rows are
|
|
11
|
+
* fat: every message, tool call and todo of a run rides the row), 865
|
|
12
|
+
* sessions of about half a kilobyte, twelve organizations with one third
|
|
13
|
+
* of every kind in the busiest. The payload is uniform where production's
|
|
14
|
+
* is skewed (its largest execution is 4 MB), so these numbers understate
|
|
15
|
+
* the tail, never the median.
|
|
16
|
+
*
|
|
17
|
+
* Each shape is timed as the lane pays for it — the store read, the
|
|
18
|
+
* decode, the lane's own predicate — as the median of five runs after one
|
|
19
|
+
* warm-up. The "today" shapes read the whole kind the way every lane did
|
|
20
|
+
* before the list index; the "indexed" shapes read the same answer through
|
|
21
|
+
* `queryResources`. The status-write shape times the run's hottest write,
|
|
22
|
+
* an `updateResource` that changes no list key, which the index must not
|
|
23
|
+
* slow; it uses only the store API that predates the index, so the same
|
|
24
|
+
* file measures the code before and after.
|
|
25
|
+
*/
|
|
26
|
+
import { create, fromBinary } from "@bufbuild/protobuf";
|
|
27
|
+
import { afterAll, describe, expect, it } from "vitest";
|
|
28
|
+
|
|
29
|
+
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
30
|
+
import { AgentExecutionSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
31
|
+
import { MessageType } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
32
|
+
import { SessionSchema } from "@stigmer/protos/ai/stigmer/agentic/session/v1/api_pb";
|
|
33
|
+
import { ApiResourceKind } from "@stigmer/protos/ai/stigmer/commons/apiresource/apiresourcekind/api_resource_kind_pb";
|
|
34
|
+
|
|
35
|
+
import { LIST_INDEXES } from "../../boot/list-indexes.js";
|
|
36
|
+
import { agentExecutionListIndex } from "../../domain/agentexecution/list-index.js";
|
|
37
|
+
import { sessionListIndex } from "../../domain/session/list-index.js";
|
|
38
|
+
import type { Store } from "../interface.js";
|
|
39
|
+
import { PostgresStore } from "../postgres/store.js";
|
|
40
|
+
import {
|
|
41
|
+
createTestDatabase,
|
|
42
|
+
testDatabaseAdminUrl,
|
|
43
|
+
} from "../postgres/__tests__/support.js";
|
|
44
|
+
import type { TestDatabase } from "../postgres/__tests__/support.js";
|
|
45
|
+
import { tempStore } from "../sqlite/__tests__/support.js";
|
|
46
|
+
|
|
47
|
+
const MEASURE = process.env["STORE_MEASURE"] === "1";
|
|
48
|
+
|
|
49
|
+
const ORGS = 12;
|
|
50
|
+
const EXECUTIONS = 1_279;
|
|
51
|
+
const SESSIONS = 865;
|
|
52
|
+
const MESSAGES_PER_EXECUTION = 40;
|
|
53
|
+
const MESSAGE_CHARS = 900;
|
|
54
|
+
const BUSY_ORG = "org-00";
|
|
55
|
+
const RUNS = 5;
|
|
56
|
+
|
|
57
|
+
function orgOf(index: number): string {
|
|
58
|
+
// One third of every kind in the busiest organization, the rest spread.
|
|
59
|
+
return index % 3 === 0
|
|
60
|
+
? BUSY_ORG
|
|
61
|
+
: `org-${String((index % (ORGS - 1)) + 1).padStart(2, "0")}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sessionIdOf(index: number): string {
|
|
65
|
+
return `ses_${String(index).padStart(26, "0")}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function createdAt(index: number): { seconds: bigint; nanos: number } {
|
|
69
|
+
return { seconds: BigInt(1_790_000_000 + index), nanos: 0 };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function seed(store: Store): Promise<void> {
|
|
73
|
+
const content = "x".repeat(MESSAGE_CHARS);
|
|
74
|
+
for (let i = 0; i < SESSIONS; i++) {
|
|
75
|
+
const id = sessionIdOf(i);
|
|
76
|
+
await store.saveResource(
|
|
77
|
+
ApiResourceKind.session,
|
|
78
|
+
id,
|
|
79
|
+
SessionSchema,
|
|
80
|
+
create(SessionSchema, {
|
|
81
|
+
metadata: { id, org: orgOf(i), name: `session ${i}` },
|
|
82
|
+
spec: { subject: `subject ${i}` },
|
|
83
|
+
status: {
|
|
84
|
+
audit: { specAudit: { createdAt: createdAt(i) } },
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
for (let i = 0; i < EXECUTIONS; i++) {
|
|
90
|
+
const id = `aex_${String(i).padStart(26, "0")}`;
|
|
91
|
+
const session = i % SESSIONS;
|
|
92
|
+
await store.saveResource(
|
|
93
|
+
ApiResourceKind.agent_execution,
|
|
94
|
+
id,
|
|
95
|
+
AgentExecutionSchema,
|
|
96
|
+
create(AgentExecutionSchema, {
|
|
97
|
+
metadata: { id, org: orgOf(session) },
|
|
98
|
+
spec: { sessionId: sessionIdOf(session), message: "run it" },
|
|
99
|
+
status: {
|
|
100
|
+
messages: Array.from({ length: MESSAGES_PER_EXECUTION }, (_, m) => ({
|
|
101
|
+
type:
|
|
102
|
+
m % 2 === 0 ? MessageType.MESSAGE_HUMAN : MessageType.MESSAGE_AI,
|
|
103
|
+
content,
|
|
104
|
+
timestamp: "2026-09-23T10:00:00Z",
|
|
105
|
+
})),
|
|
106
|
+
audit: { specAudit: { createdAt: createdAt(i) } },
|
|
107
|
+
},
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function median(
|
|
114
|
+
work: () => Promise<number>,
|
|
115
|
+
): Promise<{ ms: number; rows: number }> {
|
|
116
|
+
await work();
|
|
117
|
+
const samples: number[] = [];
|
|
118
|
+
let rows = 0;
|
|
119
|
+
for (let run = 0; run < RUNS; run++) {
|
|
120
|
+
const started = performance.now();
|
|
121
|
+
rows = await work();
|
|
122
|
+
samples.push(performance.now() - started);
|
|
123
|
+
}
|
|
124
|
+
samples.sort((a, b) => a - b);
|
|
125
|
+
return { ms: Math.round(samples[Math.floor(RUNS / 2)] ?? 0), rows };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function decodeExecutions(rows: ReadonlyArray<Uint8Array>): AgentExecution[] {
|
|
129
|
+
return rows.map((row) => fromBinary(AgentExecutionSchema, row));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
interface Shape {
|
|
133
|
+
readonly name: string;
|
|
134
|
+
run(store: Store): Promise<number>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** What each lane pays today: the whole kind, decoded, then its predicate. */
|
|
138
|
+
const TODAY: ReadonlyArray<Shape> = [
|
|
139
|
+
{
|
|
140
|
+
name: "agentExecution.list(org) — whole kind, org in memory",
|
|
141
|
+
async run(store) {
|
|
142
|
+
const rows = await store.listResources(ApiResourceKind.agent_execution);
|
|
143
|
+
return decodeExecutions(rows).filter((e) => e.metadata?.org === BUSY_ORG)
|
|
144
|
+
.length;
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "agentExecution.listBySession — whole kind, session in memory",
|
|
149
|
+
async run(store) {
|
|
150
|
+
const rows = await store.listResources(ApiResourceKind.agent_execution);
|
|
151
|
+
return decodeExecutions(rows).filter(
|
|
152
|
+
(e) => e.spec?.sessionId === sessionIdOf(0),
|
|
153
|
+
).length;
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "session.list — whole kind",
|
|
158
|
+
async run(store) {
|
|
159
|
+
const rows = await store.listResources(ApiResourceKind.session);
|
|
160
|
+
return rows.map((row) => fromBinary(SessionSchema, row)).length;
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
/** The same answers read through the list index. */
|
|
166
|
+
const INDEXED: ReadonlyArray<Shape> = [
|
|
167
|
+
{
|
|
168
|
+
name: "agentExecution.list(org) — the organization's rows, every one",
|
|
169
|
+
async run(store) {
|
|
170
|
+
const rows = await store.queryResources(agentExecutionListIndex, {
|
|
171
|
+
org: BUSY_ORG,
|
|
172
|
+
});
|
|
173
|
+
return decodeExecutions(rows.map((row) => row.data)).length;
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: "agentExecution.list(org) — one page of 50",
|
|
178
|
+
async run(store) {
|
|
179
|
+
const rows = await store.queryResources(agentExecutionListIndex, {
|
|
180
|
+
org: BUSY_ORG,
|
|
181
|
+
limit: 50,
|
|
182
|
+
});
|
|
183
|
+
return decodeExecutions(rows.map((row) => row.data)).length;
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "agentExecution.listBySession — the session key",
|
|
188
|
+
async run(store) {
|
|
189
|
+
const rows = await store.queryResources(agentExecutionListIndex, {
|
|
190
|
+
anyKey: [{ name: "session", value: sessionIdOf(0) }],
|
|
191
|
+
});
|
|
192
|
+
return decodeExecutions(rows.map((row) => row.data)).length;
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "session.list(org) — one page of 50",
|
|
197
|
+
async run(store) {
|
|
198
|
+
const rows = await store.queryResources(sessionListIndex, {
|
|
199
|
+
org: BUSY_ORG,
|
|
200
|
+
limit: 50,
|
|
201
|
+
});
|
|
202
|
+
return rows.map((row) => fromBinary(SessionSchema, row.data)).length;
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const STATUS_WRITES = 200;
|
|
208
|
+
|
|
209
|
+
/** The run's hottest write: a status persist that changes no list key. */
|
|
210
|
+
async function timeStatusWrites(store: Store): Promise<number> {
|
|
211
|
+
const id = `aex_${String(1).padStart(26, "0")}`;
|
|
212
|
+
const started = performance.now();
|
|
213
|
+
for (let i = 0; i < STATUS_WRITES; i++) {
|
|
214
|
+
await store.updateResource(
|
|
215
|
+
ApiResourceKind.agent_execution,
|
|
216
|
+
id,
|
|
217
|
+
AgentExecutionSchema,
|
|
218
|
+
(execution) => {
|
|
219
|
+
execution.status!.error = `write ${i}`;
|
|
220
|
+
},
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
return (performance.now() - started) / STATUS_WRITES;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let postgresDatabase: TestDatabase | undefined;
|
|
227
|
+
|
|
228
|
+
const DRIVERS: ReadonlyArray<{
|
|
229
|
+
readonly name: string;
|
|
230
|
+
readonly skip: boolean;
|
|
231
|
+
open(): Promise<{ store: Store; close(): Promise<void> }>;
|
|
232
|
+
}> = [
|
|
233
|
+
{
|
|
234
|
+
name: "sqlite",
|
|
235
|
+
skip: false,
|
|
236
|
+
async open() {
|
|
237
|
+
const temp = tempStore();
|
|
238
|
+
return { store: temp.store, close: () => temp.cleanup() };
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: "postgres",
|
|
243
|
+
skip: testDatabaseAdminUrl() === undefined,
|
|
244
|
+
async open() {
|
|
245
|
+
postgresDatabase = await createTestDatabase();
|
|
246
|
+
const store = await PostgresStore.open(
|
|
247
|
+
postgresDatabase.databaseUrl,
|
|
248
|
+
undefined,
|
|
249
|
+
{
|
|
250
|
+
listIndexes: LIST_INDEXES,
|
|
251
|
+
},
|
|
252
|
+
);
|
|
253
|
+
return { store, close: () => store.close() };
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
afterAll(async () => {
|
|
259
|
+
await postgresDatabase?.drop();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe.skipIf(!MEASURE)("list reads at production's shape", () => {
|
|
263
|
+
describe.each(DRIVERS)("$name", (driver) => {
|
|
264
|
+
it.skipIf(driver.skip)(
|
|
265
|
+
"times every shape",
|
|
266
|
+
async () => {
|
|
267
|
+
const opened = await driver.open();
|
|
268
|
+
try {
|
|
269
|
+
await seed(opened.store);
|
|
270
|
+
for (const [label, shapes] of [
|
|
271
|
+
["today", TODAY],
|
|
272
|
+
["indexed", INDEXED],
|
|
273
|
+
] as const) {
|
|
274
|
+
for (const shape of shapes) {
|
|
275
|
+
const { ms, rows } = await median(() => shape.run(opened.store));
|
|
276
|
+
console.log(
|
|
277
|
+
`[measure] ${driver.name} ${label} ${shape.name}: ${ms} ms (${rows} rows kept)`,
|
|
278
|
+
);
|
|
279
|
+
expect(rows).toBeGreaterThan(0);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const perWrite = await timeStatusWrites(opened.store);
|
|
283
|
+
console.log(
|
|
284
|
+
`[measure] ${driver.name} status write (updateResource, no key change): ${perWrite.toFixed(2)} ms per write over ${STATUS_WRITES}`,
|
|
285
|
+
);
|
|
286
|
+
} finally {
|
|
287
|
+
await opened.close();
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
600_000,
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
});
|