From 44231b89a7f9e3b7276fd611e1d649b0a39a2bbc Mon Sep 17 00:00:00 2001 From: Sawyer Date: Sun, 6 Sep 2026 21:32:39 -0700 Subject: [PATCH 1/4] Pin the re-pin's test expectations before the vendor bump Cover PRINCIPAL_KEY_ENCRYPTION_KEY parsing in the hub config tests (64-hex, parsed exactly like CREDENTIAL_ENCRYPTION_KEY, with no ALLOW_PLAINTEXT_SECRETS fallback substituting a key), and repoint the tolerate-migration test at the migration file's renumbered 0091 name. --- apps/hub/test/config.test.ts | 33 +++++++++++++++++++ vendor/intx/db/src/tolerate-migration.test.ts | 6 ++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/apps/hub/test/config.test.ts b/apps/hub/test/config.test.ts index b4d41908e4..ae04ea0a3e 100644 --- a/apps/hub/test/config.test.ts +++ b/apps/hub/test/config.test.ts @@ -334,6 +334,39 @@ describe("readHubConfig", () => { expect(message).toContain("CREDENTIAL_ENCRYPTION_KEY"); }); + test("PRINCIPAL_KEY_ENCRYPTION_KEY absent by default", () => { + expect( + readHubConfig(validEnv).principalKeyEncryptionKeyHex, + ).toBeUndefined(); + }); + + test("PRINCIPAL_KEY_ENCRYPTION_KEY accepts a 64-char hex key", () => { + const key = "b".repeat(64); + const config = readHubConfig({ + ...validEnv, + PRINCIPAL_KEY_ENCRYPTION_KEY: key, + }); + expect(config.principalKeyEncryptionKeyHex).toBe(key); + }); + + test("PRINCIPAL_KEY_ENCRYPTION_KEY rejects a key of the wrong length or shape", () => { + const message = readExpectingError({ + ...validEnv, + PRINCIPAL_KEY_ENCRYPTION_KEY: "not-hex-and-too-short", + }); + expect(message).toContain("PRINCIPAL_KEY_ENCRYPTION_KEY"); + }); + + test("ALLOW_PLAINTEXT_SECRETS never substitutes a principal key — the key stays unset", () => { + const config = readHubConfig({ + ...validEnv, + BASE_URL: "http://localhost:3000", + ALLOW_PLAINTEXT_SECRETS: "1", + }); + expect(config.allowPlaintextSecrets).toBe(true); + expect(config.principalKeyEncryptionKeyHex).toBeUndefined(); + }); + test("allowPlaintextSecrets is false by default", () => { expect(readHubConfig(validEnv).allowPlaintextSecrets).toBe(false); }); diff --git a/vendor/intx/db/src/tolerate-migration.test.ts b/vendor/intx/db/src/tolerate-migration.test.ts index 6a41735515..d2abc16538 100644 --- a/vendor/intx/db/src/tolerate-migration.test.ts +++ b/vendor/intx/db/src/tolerate-migration.test.ts @@ -1,4 +1,4 @@ -// WORKBENCH DELTA (see VENDORED.md): migration 0088 rewrites the retired +// WORKBENCH DELTA (see VENDORED.md): migration 0091 rewrites the retired // `onBodyFailure: "continue"` literal to upstream's `"tolerate"` inside // frozen wire projections. Exercised against a scratch table carrying the // column the statement touches. DB-gated: skipped when DATABASE_URL is unset. @@ -15,10 +15,10 @@ const MIGRATION = path.resolve( import.meta.dir, "..", "migrations", - "0088_workflow_definition_version_tolerate_body_failure.sql", + "0091_workflow_definition_version_tolerate_body_failure.sql", ); -describeIfDb("0088 rewrites onBodyFailure continue -> tolerate", () => { +describeIfDb("0091 rewrites onBodyFailure continue -> tolerate", () => { const schema = `tolerate_test_${Date.now().toString(36)}`; const parsed = new URL(databaseUrl); const sql = postgres({ From 8cfa99cde041ad688751680202a245e1ceb26f2d Mon Sep 17 00:00:00 2001 From: Sawyer Date: Sun, 6 Sep 2026 21:32:52 -0700 Subject: [PATCH 2/4] Re-vendor the Interchange db/hub-api/types pins to c1b5bb73 and add hub-common Copy the per-principal signing-key series into the vendored packages: principal-key-store, principal-store, signer-identity, and the principal-key backfill land in @intx/db; the tenant/invite/run/workflow routes and the grant materializer gain the principalKeyStore seam in @intx/hub-api; @intx/types gains the signer-identity contract and the principalKeyAad domain separator. Upstream's new @intx/hub-common is vendored for the first time (pky_ id prefix, generateId). Upstream's 0089 principal-key migration is renumbered to 0093 behind the four workbench-delta migrations, with the journal entry and a snapshot reconciled to the local columns; upstream 447ebb08's snapshot chain repair (relinked 0087 prevId, credential_refs restored) is applied to 0087/0088. All workbench-delta files are preserved. --- bun.lock | 16 +- tsconfig.build.json | 3 + .../db/migrations/0093_principal_keys.sql | 13 + .../db/migrations/meta/0087_snapshot.json | 8 +- .../db/migrations/meta/0088_snapshot.json | 6 + .../db/migrations/meta/0093_snapshot.json | 4395 +++++++++++++++++ vendor/intx/db/migrations/meta/_journal.json | 7 + vendor/intx/db/package.json | 1 + vendor/intx/db/src/backfill-principal-keys.ts | 74 + vendor/intx/db/src/index.ts | 12 + vendor/intx/db/src/parse-row.ts | 11 + vendor/intx/db/src/principal-key-store.ts | 126 + vendor/intx/db/src/principal-store.ts | 90 + vendor/intx/db/src/schema/index.ts | 1 + vendor/intx/db/src/schema/principal-keys.ts | 34 + vendor/intx/db/src/signer-identity.ts | 24 + vendor/intx/hub-api/src/app.ts | 79 +- .../workflow-run-deploy-auth.test.ts | 16 +- vendor/intx/hub-api/src/routes/principals.ts | 53 +- vendor/intx/hub-api/src/routes/runs.ts | 5 +- vendor/intx/hub-api/src/routes/tenants.ts | 213 +- vendor/intx/hub-api/src/routes/workflows.ts | 9 +- .../hub-api/src/run-grant-materialization.ts | 31 +- .../intx/hub-api/src/workflow-run-trigger.ts | 15 +- vendor/intx/hub-common/README.md | 24 + vendor/intx/hub-common/package.json | 32 + vendor/intx/hub-common/src/action-mapping.ts | 126 + vendor/intx/hub-common/src/glob.ts | 94 + vendor/intx/hub-common/src/ids.ts | 85 + vendor/intx/hub-common/src/index.ts | 14 + vendor/intx/hub-common/tsconfig.json | 23 + vendor/intx/hub-common/tsconfig.src.json | 26 + vendor/intx/types/src/credential-cipher.ts | 13 + vendor/intx/types/src/index.ts | 1 + vendor/intx/types/src/signer-identity.ts | 33 + 35 files changed, 5548 insertions(+), 165 deletions(-) create mode 100644 vendor/intx/db/migrations/0093_principal_keys.sql create mode 100644 vendor/intx/db/migrations/meta/0093_snapshot.json create mode 100644 vendor/intx/db/src/backfill-principal-keys.ts create mode 100644 vendor/intx/db/src/principal-key-store.ts create mode 100644 vendor/intx/db/src/principal-store.ts create mode 100644 vendor/intx/db/src/schema/principal-keys.ts create mode 100644 vendor/intx/db/src/signer-identity.ts create mode 100644 vendor/intx/hub-common/README.md create mode 100644 vendor/intx/hub-common/package.json create mode 100644 vendor/intx/hub-common/src/action-mapping.ts create mode 100644 vendor/intx/hub-common/src/glob.ts create mode 100644 vendor/intx/hub-common/src/ids.ts create mode 100644 vendor/intx/hub-common/src/index.ts create mode 100644 vendor/intx/hub-common/tsconfig.json create mode 100644 vendor/intx/hub-common/tsconfig.src.json create mode 100644 vendor/intx/types/src/signer-identity.ts diff --git a/bun.lock b/bun.lock index 3056747e56..dd09e690a6 100644 --- a/bun.lock +++ b/bun.lock @@ -16,7 +16,9 @@ "@corbits/run-scope": "workspace:*", "@corbits/webhook-triggers": "workspace:*", "@eslint/js": "^10.0.0", + "@intx/crypto": "workspace:*", "@intx/db": "workspace:*", + "@intx/log": "workspace:*", "@intx/mime": "0.3.0", "@intx/types": "0.3.0", "@types/bun": "1.3.14", @@ -1527,6 +1529,7 @@ "dependencies": { "@intx/authz": "0.3.0", "@intx/crypto": "0.3.0", + "@intx/hub-common": "0.3.0", "@intx/log": "0.3.0", "@intx/types": "workspace:*", "arktype": "catalog:", @@ -1607,6 +1610,17 @@ "typescript": "catalog:", }, }, + "vendor/intx/hub-common": { + "name": "@intx/hub-common", + "version": "0.3.0", + "dependencies": { + "@intx/types": "workspace:*", + }, + "devDependencies": { + "@types/bun": "catalog:", + "typescript": "catalog:", + }, + }, "vendor/intx/hub-sessions": { "name": "@intx/hub-sessions", "version": "0.3.0", @@ -2390,7 +2404,7 @@ "@intx/hub-api": ["@intx/hub-api@workspace:vendor/intx/hub-api"], - "@intx/hub-common": ["@intx/hub-common@0.3.0", "", { "dependencies": { "@intx/types": "0.3.0" } }, "sha512-dA8pTmBTu1JHGd3rGJrMsRWrai4GBBNnPxpZFFtXwQPzgL290lx2R+9BpRo2apdKbmgd+KOVshLvOdcUhAqghw=="], + "@intx/hub-common": ["@intx/hub-common@workspace:vendor/intx/hub-common"], "@intx/hub-sessions": ["@intx/hub-sessions@workspace:vendor/intx/hub-sessions"], diff --git a/tsconfig.build.json b/tsconfig.build.json index 471f55c9b9..148b54417c 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -193,6 +193,9 @@ { "path": "./vendor/intx/hub-api/tsconfig.src.json" }, + { + "path": "./vendor/intx/hub-common/tsconfig.src.json" + }, { "path": "./vendor/intx/hub-sessions/tsconfig.src.json" }, diff --git a/vendor/intx/db/migrations/0093_principal_keys.sql b/vendor/intx/db/migrations/0093_principal_keys.sql new file mode 100644 index 0000000000..344b9c8ede --- /dev/null +++ b/vendor/intx/db/migrations/0093_principal_keys.sql @@ -0,0 +1,13 @@ +CREATE TABLE "principal_key" ( + "id" text PRIMARY KEY NOT NULL, + "principal_id" text NOT NULL, + "public_key" text NOT NULL, + "private_key" text NOT NULL, + "status" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "principal_key_public_key_unique" UNIQUE("public_key") +); +--> statement-breakpoint +ALTER TABLE "principal_key" ADD CONSTRAINT "principal_key_principal_id_principal_id_fk" FOREIGN KEY ("principal_id") REFERENCES "public"."principal"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "principal_key_one_active" ON "principal_key" USING btree ("principal_id") WHERE "principal_key"."status" = 'active'; \ No newline at end of file diff --git a/vendor/intx/db/migrations/meta/0087_snapshot.json b/vendor/intx/db/migrations/meta/0087_snapshot.json index e1de7ab220..b836d7a273 100644 --- a/vendor/intx/db/migrations/meta/0087_snapshot.json +++ b/vendor/intx/db/migrations/meta/0087_snapshot.json @@ -1,6 +1,6 @@ { "id": "b673a276-410f-4e34-99f9-b563273d63e1", - "prevId": "0f093e94-6e56-4f60-8600-53e8592155b5", + "prevId": "c5723916-35b0-432b-a488-4f54f49ad26f", "version": "7", "dialect": "postgresql", "tables": { @@ -3548,6 +3548,12 @@ "primaryKey": false, "notNull": false }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "created_at": { "name": "created_at", "type": "timestamp", diff --git a/vendor/intx/db/migrations/meta/0088_snapshot.json b/vendor/intx/db/migrations/meta/0088_snapshot.json index 071def0b4f..fd4c04b560 100644 --- a/vendor/intx/db/migrations/meta/0088_snapshot.json +++ b/vendor/intx/db/migrations/meta/0088_snapshot.json @@ -3536,6 +3536,12 @@ "primaryKey": false, "notNull": false }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "created_at": { "name": "created_at", "type": "timestamp", diff --git a/vendor/intx/db/migrations/meta/0093_snapshot.json b/vendor/intx/db/migrations/meta/0093_snapshot.json new file mode 100644 index 0000000000..5464d0982e --- /dev/null +++ b/vendor/intx/db/migrations/meta/0093_snapshot.json @@ -0,0 +1,4395 @@ +{ + "id": "29cab816-ceea-4770-a010-9e3a1b28d2dc", + "prevId": "bdac2dcb-57fe-4ec7-a524-42a2bbe2bc0d", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.federation_trust": { + "name": "federation_trust", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_tenant_id": { + "name": "target_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "federation_trust_tenant_id_tenant_id_fk": { + "name": "federation_trust_tenant_id_tenant_id_fk", + "tableFrom": "federation_trust", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "federation_trust_target_tenant_id_tenant_id_fk": { + "name": "federation_trust_target_tenant_id_tenant_id_fk", + "tableFrom": "federation_trust", + "tableTo": "tenant", + "columnsFrom": ["target_tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "federation_trust_tenant_id_target_tenant_id_unique": { + "name": "federation_trust_tenant_id_target_tenant_id_unique", + "nullsNotDistinct": false, + "columns": ["tenant_id", "target_tenant_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tenant": { + "name": "tenant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tenant_parent_id_tenant_id_fk": { + "name": "tenant_parent_id_tenant_id_fk", + "tableFrom": "tenant", + "tableTo": "tenant", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tenant_slug_unique": { + "name": "tenant_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + }, + "tenant_domain_unique": { + "name": "tenant_domain_unique", + "nullsNotDistinct": false, + "columns": ["domain"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal": { + "name": "principal", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref_id": { + "name": "ref_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "principal_tenant_id_tenant_id_fk": { + "name": "principal_tenant_id_tenant_id_fk", + "tableFrom": "principal", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "principal_tenant_id_kind_ref_id_unique": { + "name": "principal_tenant_id_kind_ref_id_unique", + "nullsNotDistinct": false, + "columns": ["tenant_id", "kind", "ref_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_key": { + "name": "principal_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "principal_key_one_active": { + "name": "principal_key_one_active", + "columns": [ + { + "expression": "principal_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"principal_key\".\"status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "principal_key_principal_id_principal_id_fk": { + "name": "principal_key_principal_id_principal_id_fk", + "tableFrom": "principal_key", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "principal_key_public_key_unique": { + "name": "principal_key_public_key_unique", + "nullsNotDistinct": false, + "columns": ["public_key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_role": { + "name": "agent_role", + "schema": "", + "columns": { + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "agent_role_agent_id_workflow_definition_id_fk": { + "name": "agent_role_agent_id_workflow_definition_id_fk", + "tableFrom": "agent_role", + "tableTo": "workflow_definition", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_role_role_id_role_id_fk": { + "name": "agent_role_role_id_role_id_fk", + "tableFrom": "agent_role", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "agent_role_agent_id_role_id_pk": { + "name": "agent_role_agent_id_role_id_pk", + "columns": ["agent_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.principal_role": { + "name": "principal_role", + "schema": "", + "columns": { + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "principal_role_principal_id_principal_id_fk": { + "name": "principal_role_principal_id_principal_id_fk", + "tableFrom": "principal_role", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "principal_role_role_id_role_id_fk": { + "name": "principal_role_role_id_role_id_fk", + "tableFrom": "principal_role", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "principal_role_principal_id_role_id_pk": { + "name": "principal_role_principal_id_role_id_pk", + "columns": ["principal_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.role": { + "name": "role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_system": { + "name": "is_system", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "role_tenant_id_tenant_id_fk": { + "name": "role_tenant_id_tenant_id_fk", + "tableFrom": "role", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.grant": { + "name": "grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effect": { + "name": "effect", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "grant_tenant_id_tenant_id_fk": { + "name": "grant_tenant_id_tenant_id_fk", + "tableFrom": "grant", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grant_role_id_role_id_fk": { + "name": "grant_role_id_role_id_fk", + "tableFrom": "grant", + "tableTo": "role", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grant_principal_id_principal_id_fk": { + "name": "grant_principal_id_principal_id_fk", + "tableFrom": "grant", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grant_target_exactly_one": { + "name": "grant_target_exactly_one", + "value": "num_nonnulls(\"grant\".\"principal_id\", \"grant\".\"role_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.approval": { + "name": "approval", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_address": { + "name": "agent_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_definition": { + "name": "tool_definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "tool_arguments": { + "name": "tool_arguments", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "timeout_at": { + "name": "timeout_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "approval_tenant_status_idx": { + "name": "approval_tenant_status_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_anchor_run_idx": { + "name": "approval_anchor_run_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "approval_run_idx": { + "name": "approval_run_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "approval_tenant_id_tenant_id_fk": { + "name": "approval_tenant_id_tenant_id_fk", + "tableFrom": "approval", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "approval_anchor_run_id_workflow_run_id_fk": { + "name": "approval_anchor_run_id_workflow_run_id_fk", + "tableFrom": "approval", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "approval_run_id_workflow_run_id_fk": { + "name": "approval_run_id_workflow_run_id_fk", + "tableFrom": "approval", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "approval_correlation_id_unique": { + "name": "approval_correlation_id_unique", + "nullsNotDistinct": false, + "columns": ["correlation_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.signal_correlation": { + "name": "signal_correlation", + "schema": "", + "columns": { + "correlation_id": { + "name": "correlation_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_address": { + "name": "agent_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signal_name": { + "name": "signal_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signal_id": { + "name": "signal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "signal_correlation_tenant_id_tenant_id_fk": { + "name": "signal_correlation_tenant_id_tenant_id_fk", + "tableFrom": "signal_correlation", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "signal_correlation_anchor_run_id_workflow_run_id_fk": { + "name": "signal_correlation_anchor_run_id_workflow_run_id_fk", + "tableFrom": "signal_correlation", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "signal_correlation_run_id_workflow_run_id_fk": { + "name": "signal_correlation_run_id_workflow_run_id_fk", + "tableFrom": "signal_correlation", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider": { + "name": "provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin": { + "name": "plugin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "api_base_url": { + "name": "api_base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_url": { + "name": "authorization_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_url": { + "name": "token_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_info_url": { + "name": "user_info_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "provider_tenant_id_tenant_id_fk": { + "name": "provider_tenant_id_tenant_id_fk", + "tableFrom": "provider", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_tenant_name": { + "name": "provider_tenant_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "default_scopes": { + "name": "default_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "oauth_client_tenant_id_tenant_id_fk": { + "name": "oauth_client_tenant_id_tenant_id_fk", + "tableFrom": "oauth_client", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_client_provider_id_provider_id_fk": { + "name": "oauth_client_provider_id_provider_id_fk", + "tableFrom": "oauth_client", + "tableTo": "provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_tenant_provider": { + "name": "oauth_client_tenant_provider", + "nullsNotDistinct": false, + "columns": ["tenant_id", "provider_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_secret": { + "name": "refresh_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "credential_tenant_id_tenant_id_fk": { + "name": "credential_tenant_id_tenant_id_fk", + "tableFrom": "credential", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_principal_id_principal_id_fk": { + "name": "credential_principal_id_principal_id_fk", + "tableFrom": "credential", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "credential_provider_id_provider_id_fk": { + "name": "credential_provider_id_provider_id_fk", + "tableFrom": "credential", + "tableTo": "provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_oauth_client_id_oauth_client_id_fk": { + "name": "credential_oauth_client_id_oauth_client_id_fk", + "tableFrom": "credential", + "tableTo": "oauth_client", + "columnsFrom": ["oauth_client_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "credential_tenant_name": { + "name": "credential_tenant_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.asset": { + "name": "asset", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_principal_id": { + "name": "creator_principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "asset_tenant_id_tenant_id_fk": { + "name": "asset_tenant_id_tenant_id_fk", + "tableFrom": "asset", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "asset_creator_principal_id_principal_id_fk": { + "name": "asset_creator_principal_id_principal_id_fk", + "tableFrom": "asset", + "tableTo": "principal", + "columnsFrom": ["creator_principal_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "asset_tenant_kind_name": { + "name": "asset_tenant_kind_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "kind", "name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transaction": { + "name": "transaction", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "wallet_id": { + "name": "wallet_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient_id": { + "name": "recipient_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_id": { + "name": "sender_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "transaction_wallet_id_wallet_id_fk": { + "name": "transaction_wallet_id_wallet_id_fk", + "tableFrom": "transaction", + "tableTo": "wallet", + "columnsFrom": ["wallet_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "transaction_run_id_workflow_run_id_fk": { + "name": "transaction_run_id_workflow_run_id_fk", + "tableFrom": "transaction", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.wallet": { + "name": "wallet", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backend_type": { + "name": "backend_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "balance": { + "name": "balance", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "wallet_tenant_id_tenant_id_fk": { + "name": "wallet_tenant_id_tenant_id_fk", + "tableFrom": "wallet", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.offering": { + "name": "offering", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pricing": { + "name": "pricing", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "offering_agent_id_workflow_definition_id_fk": { + "name": "offering_agent_id_workflow_definition_id_fk", + "tableFrom": "offering", + "tableTo": "workflow_definition", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "offering_tenant_id_tenant_id_fk": { + "name": "offering_tenant_id_tenant_id_fk", + "tableFrom": "offering", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model": { + "name": "model", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_name": { + "name": "canonical_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_tenant_id_tenant_id_fk": { + "name": "model_tenant_id_tenant_id_fk", + "tableFrom": "model", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_tenant_canonical_name": { + "name": "model_tenant_canonical_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "canonical_name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_offering": { + "name": "model_offering", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deployment_tags": { + "name": "deployment_tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "capabilities": { + "name": "capabilities", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "quirks": { + "name": "quirks", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_offering_tenant_id_tenant_id_fk": { + "name": "model_offering_tenant_id_tenant_id_fk", + "tableFrom": "model_offering", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_offering_model_id_model_id_fk": { + "name": "model_offering_model_id_model_id_fk", + "tableFrom": "model_offering", + "tableTo": "model", + "columnsFrom": ["model_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_offering_provider_id_model_provider_id_fk": { + "name": "model_offering_provider_id_model_provider_id_fk", + "tableFrom": "model_offering", + "tableTo": "model_provider", + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_offering_tenant_model_provider": { + "name": "model_offering_tenant_model_provider", + "nullsNotDistinct": false, + "columns": ["tenant_id", "model_id", "provider_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_pricing": { + "name": "model_pricing", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "offering_id": { + "name": "offering_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input_token_price": { + "name": "input_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output_token_price": { + "name": "output_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_read_token_price": { + "name": "cache_read_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_write_token_price": { + "name": "cache_write_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thinking_token_price": { + "name": "thinking_token_price", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "per_request_fee": { + "name": "per_request_fee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "per_image_fee": { + "name": "per_image_fee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "per_audio_fee": { + "name": "per_audio_fee", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "effective_from": { + "name": "effective_from", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_pricing_tenant_id_tenant_id_fk": { + "name": "model_pricing_tenant_id_tenant_id_fk", + "tableFrom": "model_pricing", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_pricing_offering_id_model_offering_id_fk": { + "name": "model_pricing_offering_id_model_offering_id_fk", + "tableFrom": "model_pricing", + "tableTo": "model_offering", + "columnsFrom": ["offering_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_pricing_offering_currency_effective_from": { + "name": "model_pricing_offering_currency_effective_from", + "nullsNotDistinct": false, + "columns": ["offering_id", "currency", "effective_from"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_provider": { + "name": "model_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin": { + "name": "plugin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wallet_id": { + "name": "wallet_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "model_provider_tenant_id_tenant_id_fk": { + "name": "model_provider_tenant_id_tenant_id_fk", + "tableFrom": "model_provider", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_provider_credential_id_credential_id_fk": { + "name": "model_provider_credential_id_credential_id_fk", + "tableFrom": "model_provider", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "model_provider_wallet_id_wallet_id_fk": { + "name": "model_provider_wallet_id_wallet_id_fk", + "tableFrom": "model_provider", + "tableTo": "wallet", + "columnsFrom": ["wallet_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_provider_tenant_name": { + "name": "model_provider_tenant_name", + "nullsNotDistinct": false, + "columns": ["tenant_id", "name"] + } + }, + "policies": {}, + "checkConstraints": { + "model_provider_auth_xor": { + "name": "model_provider_auth_xor", + "value": "(\"model_provider\".\"credential_id\" is not null) <> (\"model_provider\".\"wallet_id\" is not null)" + } + }, + "isRLSEnabled": false + }, + "public.sidecar": { + "name": "sidecar", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_hash_sha256": { + "name": "token_hash_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'online'" + }, + "last_heartbeat": { + "name": "last_heartbeat", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sidecar_token_hash_sha256_unique": { + "name": "sidecar_token_hash_sha256_unique", + "nullsNotDistinct": false, + "columns": ["token_hash_sha256"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sidecar_allocation": { + "name": "sidecar_allocation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioner_id": { + "name": "provisioner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioner_api_version": { + "name": "provisioner_api_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provisioner_binding_fingerprint": { + "name": "provisioner_binding_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sidecar_id": { + "name": "sidecar_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "ensure_accepted_generation": { + "name": "ensure_accepted_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconciliation_lease_id": { + "name": "reconciliation_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reconciliation_lease_expires_at": { + "name": "reconciliation_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ensure_attempts": { + "name": "ensure_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "destroy_attempts": { + "name": "destroy_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "connect_deadline": { + "name": "connect_deadline", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_message": { + "name": "failure_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sidecar_allocation_anchor_run_idx": { + "name": "sidecar_allocation_anchor_run_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sidecar_allocation_active_sidecar_idx": { + "name": "sidecar_allocation_active_sidecar_idx", + "columns": [ + { + "expression": "sidecar_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"sidecar_allocation\".\"status\" in ('provisioning', 'allocated', 'replacing', 'releasing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "sidecar_allocation_sidecar_idx": { + "name": "sidecar_allocation_sidecar_idx", + "columns": [ + { + "expression": "sidecar_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sidecar_allocation_reconciliation_idx": { + "name": "sidecar_allocation_reconciliation_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"sidecar_allocation\".\"status\" in ('pending', 'provisioning', 'allocated', 'replacing', 'releasing') and \"sidecar_allocation\".\"next_attempt_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sidecar_allocation_anchor_run_id_workflow_run_id_fk": { + "name": "sidecar_allocation_anchor_run_id_workflow_run_id_fk", + "tableFrom": "sidecar_allocation", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sidecar_allocation_tenant_id_tenant_id_fk": { + "name": "sidecar_allocation_tenant_id_tenant_id_fk", + "tableFrom": "sidecar_allocation", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "sidecar_allocation_sidecar_id_sidecar_id_fk": { + "name": "sidecar_allocation_sidecar_id_sidecar_id_fk", + "tableFrom": "sidecar_allocation", + "tableTo": "sidecar", + "columnsFrom": ["sidecar_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sidecar_allocation_status_check": { + "name": "sidecar_allocation_status_check", + "value": "\"sidecar_allocation\".\"status\" in ('pending', 'provisioning', 'allocated', 'replacing', 'releasing', 'released', 'failed')" + }, + "sidecar_allocation_generation_check": { + "name": "sidecar_allocation_generation_check", + "value": "\"sidecar_allocation\".\"generation\" >= 0" + }, + "sidecar_allocation_accepted_generation_check": { + "name": "sidecar_allocation_accepted_generation_check", + "value": "\"sidecar_allocation\".\"ensure_accepted_generation\" is null or \"sidecar_allocation\".\"ensure_accepted_generation\" <= \"sidecar_allocation\".\"generation\"" + } + }, + "isRLSEnabled": false + }, + "public.agent_session": { + "name": "agent_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_session_tenant_id_tenant_id_fk": { + "name": "agent_session_tenant_id_tenant_id_fk", + "tableFrom": "agent_session", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_session_agent_id_workflow_definition_id_fk": { + "name": "agent_session_agent_id_workflow_definition_id_fk", + "tableFrom": "agent_session", + "tableTo": "workflow_definition", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "agent_session_principal_id_principal_id_fk": { + "name": "agent_session_principal_id_principal_id_fk", + "tableFrom": "agent_session", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_asset": { + "name": "session_asset", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mount_path": { + "name": "mount_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "asset_pack_sha": { + "name": "asset_pack_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_commit_sha": { + "name": "source_commit_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "materialized_at": { + "name": "materialized_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_asset_pack_sha_idx": { + "name": "session_asset_pack_sha_idx", + "columns": [ + { + "expression": "asset_pack_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "session_asset_instance_id_mount_path_pk": { + "name": "session_asset_instance_id_mount_path_pk", + "columns": ["instance_id", "mount_path"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inference_turn": { + "name": "inference_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inference_turn_instance_id_started_at_idx": { + "name": "inference_turn_instance_id_started_at_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inference_turn_session_id_agent_session_id_fk": { + "name": "inference_turn_session_id_agent_session_id_fk", + "tableFrom": "inference_turn", + "tableTo": "agent_session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inference_turn_tenant_id_tenant_id_fk": { + "name": "inference_turn_tenant_id_tenant_id_fk", + "tableFrom": "inference_turn", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session_mail": { + "name": "session_mail", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "raw": { + "name": "raw", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "session_mail_instance_id_created_at_idx": { + "name": "session_mail_instance_id_created_at_idx", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_mail_session_id_created_at_idx": { + "name": "session_mail_session_id_created_at_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_mail_session_id_agent_session_id_fk": { + "name": "session_mail_session_id_agent_session_id_fk", + "tableFrom": "session_mail", + "tableTo": "agent_session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_mail_tenant_id_tenant_id_fk": { + "name": "session_mail_tenant_id_tenant_id_fk", + "tableFrom": "session_mail", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.turn_part": { + "name": "turn_part", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "turn_part_turn_id_inference_turn_id_fk": { + "name": "turn_part_turn_id_inference_turn_id_fk", + "tableFrom": "turn_part", + "tableTo": "inference_turn", + "columnsFrom": ["turn_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "turn_part_session_id_agent_session_id_fk": { + "name": "turn_part_session_id_agent_session_id_fk", + "tableFrom": "turn_part", + "tableTo": "agent_session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_token": { + "name": "git_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash_sha256": { + "name": "token_hash_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ref_pattern": { + "name": "ref_pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actions": { + "name": "actions", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "git_token_user_id_name_active_idx": { + "name": "git_token_user_id_name_active_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"git_token\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "git_token_tenant_id_tenant_id_fk": { + "name": "git_token_tenant_id_tenant_id_fk", + "tableFrom": "git_token", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "git_token_user_id_user_id_fk": { + "name": "git_token_user_id_user_id_fk", + "tableFrom": "git_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "git_token_principal_id_principal_id_fk": { + "name": "git_token_principal_id_principal_id_fk", + "tableFrom": "git_token", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "git_token_token_hash_sha256_unique": { + "name": "git_token_token_hash_sha256_unique", + "nullsNotDistinct": false, + "columns": ["token_hash_sha256"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_definition": { + "name": "workflow_definition", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "creator_principal_id": { + "name": "creator_principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "asset_id": { + "name": "asset_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "wire_hash": { + "name": "wire_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_requirements": { + "name": "grant_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "model_requirements": { + "name": "model_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_bindings": { + "name": "credential_bindings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "current_version": { + "name": "current_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1'" + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'authored'" + }, + "schedule_claimed_minute": { + "name": "schedule_claimed_minute", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'deployed'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_definition_tenant_idx": { + "name": "workflow_definition_tenant_idx", + "columns": [ + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_definition_asset_wire_hash_idx": { + "name": "workflow_definition_asset_wire_hash_idx", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "wire_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_definition_tenant_id_tenant_id_fk": { + "name": "workflow_definition_tenant_id_tenant_id_fk", + "tableFrom": "workflow_definition", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_definition_creator_principal_id_principal_id_fk": { + "name": "workflow_definition_creator_principal_id_principal_id_fk", + "tableFrom": "workflow_definition", + "tableTo": "principal", + "columnsFrom": ["creator_principal_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_definition_asset_id_asset_id_fk": { + "name": "workflow_definition_asset_id_asset_id_fk", + "tableFrom": "workflow_definition", + "tableTo": "asset", + "columnsFrom": ["asset_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_definition_version": { + "name": "workflow_definition_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "definition_id": { + "name": "definition_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "approved_wire_hash": { + "name": "approved_wire_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_snapshot": { + "name": "grant_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "wire_projection": { + "name": "wire_projection", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_definition_version_definition_idx": { + "name": "workflow_definition_version_definition_idx", + "columns": [ + { + "expression": "definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_definition_version_definition_id_workflow_definition_id_fk": { + "name": "workflow_definition_version_definition_id_workflow_definition_id_fk", + "tableFrom": "workflow_definition_version", + "tableTo": "workflow_definition", + "columnsFrom": ["definition_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workflow_definition_version_definition_version": { + "name": "workflow_definition_version_definition_version", + "nullsNotDistinct": false, + "columns": ["definition_id", "version"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_run": { + "name": "workflow_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "definition_id": { + "name": "definition_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "principal_id": { + "name": "principal_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sidecar_id": { + "name": "sidecar_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kernel_id": { + "name": "kernel_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_preferences": { + "name": "model_preferences", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_refs": { + "name": "credential_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_run_definition_idx": { + "name": "workflow_run_definition_idx", + "columns": [ + { + "expression": "definition_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_address_idx": { + "name": "workflow_run_address_idx", + "columns": [ + { + "expression": "address", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_run\".\"address\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_run_definition_id_workflow_definition_id_fk": { + "name": "workflow_run_definition_id_workflow_definition_id_fk", + "tableFrom": "workflow_run", + "tableTo": "workflow_definition", + "columnsFrom": ["definition_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_anchor_run_id_workflow_run_id_fk": { + "name": "workflow_run_anchor_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_tenant_id_tenant_id_fk": { + "name": "workflow_run_tenant_id_tenant_id_fk", + "tableFrom": "workflow_run", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_principal_id_principal_id_fk": { + "name": "workflow_run_principal_id_principal_id_fk", + "tableFrom": "workflow_run", + "tableTo": "principal", + "columnsFrom": ["principal_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_run_sidecar_id_sidecar_id_fk": { + "name": "workflow_run_sidecar_id_sidecar_id_fk", + "tableFrom": "workflow_run", + "tableTo": "sidecar", + "columnsFrom": ["sidecar_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_run_dispatch": { + "name": "workflow_run_dispatch", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'mail'" + }, + "raw_message": { + "name": "raw_message", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "step_grants": { + "name": "step_grants", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "acknowledged_generation": { + "name": "acknowledged_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "delivery_lease_id": { + "name": "delivery_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delivery_lease_expires_at": { + "name": "delivery_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_message": { + "name": "failure_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_run_dispatch_anchor_message_idx": { + "name": "workflow_run_dispatch_anchor_message_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_dispatch_delivery_idx": { + "name": "workflow_run_dispatch_delivery_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_run_dispatch\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_dispatch_anchor_status_idx": { + "name": "workflow_run_dispatch_anchor_status_idx", + "columns": [ + { + "expression": "anchor_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_run_dispatch_anchor_run_id_workflow_run_id_fk": { + "name": "workflow_run_dispatch_anchor_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run_dispatch", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workflow_run_dispatch_status_check": { + "name": "workflow_run_dispatch_status_check", + "value": "\"workflow_run_dispatch\".\"status\" in ('pending', 'acknowledged', 'settled', 'failed')" + }, + "workflow_run_dispatch_kind_check": { + "name": "workflow_run_dispatch_kind_check", + "value": "\"workflow_run_dispatch\".\"kind\" in ('mail', 'signal')" + }, + "workflow_run_dispatch_attempt_count_check": { + "name": "workflow_run_dispatch_attempt_count_check", + "value": "\"workflow_run_dispatch\".\"attempt_count\" >= 0" + }, + "workflow_run_dispatch_acknowledged_generation_check": { + "name": "workflow_run_dispatch_acknowledged_generation_check", + "value": "\"workflow_run_dispatch\".\"acknowledged_generation\" is null or \"workflow_run_dispatch\".\"acknowledged_generation\" >= 0" + }, + "workflow_run_dispatch_acknowledged_state_check": { + "name": "workflow_run_dispatch_acknowledged_state_check", + "value": "\"workflow_run_dispatch\".\"status\" <> 'acknowledged' or \"workflow_run_dispatch\".\"acknowledged_generation\" is not null" + }, + "workflow_run_dispatch_pending_schedule_check": { + "name": "workflow_run_dispatch_pending_schedule_check", + "value": "\"workflow_run_dispatch\".\"status\" <> 'pending' or \"workflow_run_dispatch\".\"next_attempt_at\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.workflow_run_launch_spec": { + "name": "workflow_run_launch_spec", + "schema": "", + "columns": { + "anchor_run_id": { + "name": "anchor_run_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_domain": { + "name": "deployment_domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_authority_principal_id": { + "name": "source_authority_principal_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "frozen_approval_bundle": { + "name": "frozen_approval_bundle", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source_offering_ids": { + "name": "source_offering_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "default_source_offering_id": { + "name": "default_source_offering_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deploy_content": { + "name": "deploy_content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "tool_package_pins": { + "name": "tool_package_pins", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workflow_run_launch_spec_anchor_run_id_workflow_run_id_fk": { + "name": "workflow_run_launch_spec_anchor_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run_launch_spec", + "tableTo": "workflow_run", + "columnsFrom": ["anchor_run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_run_launch_spec_source_authority_principal_id_principal_id_fk": { + "name": "workflow_run_launch_spec_source_authority_principal_id_principal_id_fk", + "tableFrom": "workflow_run_launch_spec", + "tableTo": "principal", + "columnsFrom": ["source_authority_principal_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_run_execution": { + "name": "workflow_run_execution", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_run_execution_run_id_id_idx": { + "name": "workflow_run_execution_run_id_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_run_execution_status_idx": { + "name": "workflow_run_execution_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_run_execution_run_id_workflow_run_id_fk": { + "name": "workflow_run_execution_run_id_workflow_run_id_fk", + "tableFrom": "workflow_run_execution", + "tableTo": "workflow_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_probe": { + "name": "workflow_probe", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition_asset_id": { + "name": "definition_asset_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "entry": { + "name": "entry", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pin": { + "name": "pin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "provisioner_id": { + "name": "provisioner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provisioner_api_version": { + "name": "provisioner_api_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provisioner_binding_fingerprint": { + "name": "provisioner_binding_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sidecar_id": { + "name": "sidecar_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "external_ref": { + "name": "external_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_message": { + "name": "failure_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_probe_active_idx": { + "name": "workflow_probe_active_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_probe\".\"status\" in ('pending', 'provisioning', 'probing', 'releasing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_probe_sidecar_idx": { + "name": "workflow_probe_sidecar_idx", + "columns": [ + { + "expression": "sidecar_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_probe_tenant_id_tenant_id_fk": { + "name": "workflow_probe_tenant_id_tenant_id_fk", + "tableFrom": "workflow_probe", + "tableTo": "tenant", + "columnsFrom": ["tenant_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "workflow_probe_definition_asset_id_asset_id_fk": { + "name": "workflow_probe_definition_asset_id_asset_id_fk", + "tableFrom": "workflow_probe", + "tableTo": "asset", + "columnsFrom": ["definition_asset_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "workflow_probe_sidecar_id_sidecar_id_fk": { + "name": "workflow_probe_sidecar_id_sidecar_id_fk", + "tableFrom": "workflow_probe", + "tableTo": "sidecar", + "columnsFrom": ["sidecar_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workflow_probe_status_check": { + "name": "workflow_probe_status_check", + "value": "\"workflow_probe\".\"status\" in ('pending', 'provisioning', 'probing', 'releasing', 'succeeded', 'failed')" + }, + "workflow_probe_succeeded_result_check": { + "name": "workflow_probe_succeeded_result_check", + "value": "\"workflow_probe\".\"status\" <> 'succeeded' or \"workflow_probe\".\"result\" is not null" + }, + "workflow_probe_generation_check": { + "name": "workflow_probe_generation_check", + "value": "\"workflow_probe\".\"generation\" >= 0" + } + }, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/vendor/intx/db/migrations/meta/_journal.json b/vendor/intx/db/migrations/meta/_journal.json index 137a65a141..5e717c2e62 100644 --- a/vendor/intx/db/migrations/meta/_journal.json +++ b/vendor/intx/db/migrations/meta/_journal.json @@ -645,6 +645,13 @@ "when": 1788000000000, "tag": "0092_workflow_definition_schedule_claimed_minute", "breakpoints": true + }, + { + "idx": 93, + "version": "7", + "when": 1788617577404, + "tag": "0093_principal_keys", + "breakpoints": true } ] } diff --git a/vendor/intx/db/package.json b/vendor/intx/db/package.json index c573f02049..6cb0a4723b 100644 --- a/vendor/intx/db/package.json +++ b/vendor/intx/db/package.json @@ -20,6 +20,7 @@ "dependencies": { "@intx/authz": "0.3.0", "@intx/crypto": "0.3.0", + "@intx/hub-common": "0.3.0", "@intx/log": "0.3.0", "@intx/types": "workspace:*", "arktype": "catalog:", diff --git a/vendor/intx/db/src/backfill-principal-keys.ts b/vendor/intx/db/src/backfill-principal-keys.ts new file mode 100644 index 0000000000..c6e2ef69a2 --- /dev/null +++ b/vendor/intx/db/src/backfill-principal-keys.ts @@ -0,0 +1,74 @@ +// Backfill per-principal signing keys onto the principals that predate the +// feature. +// +// Minting a key lives only in the principal-creation owner, so every principal +// CREATED after that landed already has an active key. This one-shot pass keys +// the pre-existing population so the whole non-agent principal set matches the +// invariant "every non-agent principal has an active key". +// +// Scope is every `user` and `workflow` principal REGARDLESS of status, not just +// active ones. A status flip (e.g. an invite moving from `invited` to `active`) +// reuses the same principal row and does not re-mint, so an active-only pass +// would leave a pre-existing `invited`/`suspended` principal keyless and turn it +// into a keyless ACTIVE principal the moment it is activated. `agent` is a +// legacy/inert kind that is never keyed at creation either, so it is skipped to +// keep the populations consistent. +// +// Idempotent: a principal that already holds an active key is skipped, so the +// pass is safe to re-run or resume after a partial failure. Each principal is +// keyed in its own autocommit, so a partial failure leaves the earlier +// principals keyed. Safe to run against a LIVE hub -- nothing reads principal +// keys yet, the hub only mints for newly-created principals (disjoint from this +// pass's pre-existing set), and the `principal_key` active-key unique index +// blocks a double active key. + +import { and, eq, inArray } from "drizzle-orm"; + +import type { DB } from "./client"; +import { principal } from "./schema/principals"; +import { principalKey } from "./schema/principal-keys"; +import type { PrincipalKeyStore } from "./principal-key-store"; + +const BACKFILLED_KINDS = ["user", "workflow"] as const; + +export type BackfillPrincipalKeysReport = { + /** Principals that were keyless and received a fresh active key. */ + keysGenerated: number; + /** Principals that already held an active key and were left untouched. */ + alreadyKeyed: number; +}; + +export async function backfillPrincipalKeys( + db: DB["db"], + principalKeyStore: PrincipalKeyStore, +): Promise { + // The left join is scoped to the active key, so `activeKeyId` is null exactly + // when the principal has no active key -- a principal holding only a retired + // key counts as keyless and is re-keyed. The active-key unique index makes at + // most one active row per principal, so each principal appears once. + const rows = await db + .select({ principalId: principal.id, activeKeyId: principalKey.id }) + .from(principal) + .leftJoin( + principalKey, + and( + eq(principalKey.principalId, principal.id), + eq(principalKey.status, "active"), + ), + ) + .where(inArray(principal.kind, [...BACKFILLED_KINDS])); + + const report: BackfillPrincipalKeysReport = { + keysGenerated: 0, + alreadyKeyed: 0, + }; + for (const row of rows) { + if (row.activeKeyId !== null) { + report.alreadyKeyed += 1; + continue; + } + await principalKeyStore.generate(row.principalId); + report.keysGenerated += 1; + } + return report; +} diff --git a/vendor/intx/db/src/index.ts b/vendor/intx/db/src/index.ts index 9f9c9e04b6..a085d231c1 100644 --- a/vendor/intx/db/src/index.ts +++ b/vendor/intx/db/src/index.ts @@ -10,7 +10,18 @@ export { rekeyCredentialSecrets, type RekeyReport, } from "./rekey-credential-secrets"; +export { + backfillPrincipalKeys, + type BackfillPrincipalKeysReport, +} from "./backfill-principal-keys"; export { createGrantStore } from "./grant-store"; +export { createPrincipalStore, type PrincipalStore } from "./principal-store"; +export { + createPrincipalKeyStore, + type CreatePrincipalKeyStoreDeps, + type PrincipalKeyStore, +} from "./principal-key-store"; +export { lookupLocalPrincipalSigner } from "./signer-identity"; export { createApprovalStore, type ApprovalStore, @@ -128,6 +139,7 @@ export { parseGrantRow, parseApprovalRow, parsePrincipalRow, + parsePrincipalKeyRow, parseSignalCorrelationRow, parseWorkflowRunRow, parseWorkflowRunDispatchRow, diff --git a/vendor/intx/db/src/parse-row.ts b/vendor/intx/db/src/parse-row.ts index a085a14f8d..0cc6c66651 100644 --- a/vendor/intx/db/src/parse-row.ts +++ b/vendor/intx/db/src/parse-row.ts @@ -30,6 +30,7 @@ import type { oauthClient, offering, principal, + principalKey, provider, signalCorrelation, tenant, @@ -90,6 +91,9 @@ const WorkflowDefinitionVersionStatusValidator = type.enumerated( const PrincipalKindValidator = type.enumerated(...principalKinds); const PrincipalStatusValidator = type.enumerated(...principalStatuses); +const principalKeyStatuses = ["active", "retired"] as const; +const PrincipalKeyStatusValidator = type.enumerated(...principalKeyStatuses); + const credentialTypes = [ "api_key", "oauth_token", @@ -190,6 +194,13 @@ export function parsePrincipalRow(row: typeof principal.$inferSelect) { }; } +export function parsePrincipalKeyRow(row: typeof principalKey.$inferSelect) { + return { + ...row, + status: PrincipalKeyStatusValidator.assert(row.status), + }; +} + export function parseSignalCorrelationRow( row: typeof signalCorrelation.$inferSelect, ) { diff --git a/vendor/intx/db/src/principal-key-store.ts b/vendor/intx/db/src/principal-key-store.ts new file mode 100644 index 0000000000..94c3286a5b --- /dev/null +++ b/vendor/intx/db/src/principal-key-store.ts @@ -0,0 +1,126 @@ +import { and, eq } from "drizzle-orm"; + +import { generateKeyPair, signEd25519 } from "@intx/crypto"; +import { hexDecode, hexEncode, principalKeyAad } from "@intx/types"; +import type { CredentialCipher } from "@intx/types"; +import { generateId } from "@intx/hub-common"; + +import type { DB, DBExecutor } from "./client"; +import { principalKey } from "./schema/principal-keys"; + +type DBHandle = DB["db"]; + +export type CreatePrincipalKeyStoreDeps = { + db: DBHandle; + /** + * Seals the private key seed at rest. Production supplies a real env-key + * cipher under `PRINCIPAL_KEY_ENCRYPTION_KEY`; tests and local dev may supply + * the noop cipher, which stores the seed as plaintext. + */ + cipher: CredentialCipher; +}; + +/** + * Store for the `principal_key` table -- a principal's Ed25519 signing key. + * + * The hub custodies the private key: it mints, seals, and signs with it on the + * principal's behalf. A signature therefore ATTRIBUTES an action to a principal + * but is NOT non-repudiable against the hub operator, who holds the key. See + * docs/AUTH.md. + * + * The store owns the one-active-key-per-principal invariant end to end: `sign` + * and `getPublicKey` resolve the single active row, and `generate` inserts a new + * active key that the table's partial unique index rejects if the principal + * already has one. The private seed never leaves this module -- `sign` decrypts, + * signs, and discards it; no method returns private material. + */ +export function createPrincipalKeyStore({ + db, + cipher, +}: CreatePrincipalKeyStoreDeps) { + async function loadActive(principalId: string, tx?: DBExecutor) { + const [row] = await (tx ?? db) + .select() + .from(principalKey) + .where( + and( + eq(principalKey.principalId, principalId), + eq(principalKey.status, "active"), + ), + ) + .limit(1); + return row; + } + + return { + /** + * Mint a fresh active signing key for a principal. Generates an Ed25519 key + * pair, seals the hex-encoded 32-byte seed with the cipher bound to the + * key's row and column, and inserts it. A principal that already holds an + * active key trips the partial unique index and this throws. Returns the + * hex-encoded public key; never returns private material. + */ + async generate(principalId: string, tx?: DBExecutor): Promise { + const id = generateId("principalKey"); + const keyPair = await generateKeyPair(); + const sealedPrivateKey = await cipher.encrypt( + hexEncode(keyPair.privateKey), + principalKeyAad(id, "private_key"), + ); + const publicKey = hexEncode(keyPair.publicKey); + const now = new Date(); + await (tx ?? db).insert(principalKey).values({ + id, + principalId, + publicKey, + privateKey: sealedPrivateKey, + status: "active", + createdAt: now, + updatedAt: now, + }); + return publicKey; + }, + + /** + * Sign a message with the principal's active key. Decrypts the sealed seed, + * produces the raw 64-byte Ed25519 signature, and discards the seed. Throws + * when the principal has no active key. The seed stays a local that goes out + * of scope; it is never returned or logged. + */ + async sign( + principalId: string, + message: Uint8Array, + tx?: DBExecutor, + ): Promise { + const row = await loadActive(principalId, tx); + if (row === undefined) { + throw new Error( + `principalKeyStore.sign: principal ${principalId} has no active key`, + ); + } + const seedHex = await cipher.decrypt( + row.privateKey, + principalKeyAad(row.id, "private_key"), + ); + const seed = hexDecode(seedHex); + return signEd25519(seed, message); + }, + + /** + * The hex-encoded public key of a principal's active signing key. Throws + * when the principal has no active key rather than defaulting, so a missing + * key surfaces at the call site instead of downstream. + */ + async getPublicKey(principalId: string, tx?: DBExecutor): Promise { + const row = await loadActive(principalId, tx); + if (row === undefined) { + throw new Error( + `principalKeyStore.getPublicKey: principal ${principalId} has no active key`, + ); + } + return row.publicKey; + }, + }; +} + +export type PrincipalKeyStore = ReturnType; diff --git a/vendor/intx/db/src/principal-store.ts b/vendor/intx/db/src/principal-store.ts new file mode 100644 index 0000000000..4879eabf0f --- /dev/null +++ b/vendor/intx/db/src/principal-store.ts @@ -0,0 +1,90 @@ +import type { DB, DBExecutor } from "./client"; +import { principal } from "./schema/principals"; +import { parsePrincipalRow } from "./parse-row"; +import type { PrincipalKeyStore } from "./principal-key-store"; + +type DBHandle = DB["db"]; + +type PrincipalInsert = typeof principal.$inferInsert; +type ParsedPrincipal = ReturnType; + +/** + * Store for the `principal` table -- the single owner of principal creation. + * Every principal insert in the system routes through this one factory, and + * every principal it creates is minted an active signing key in the same + * transaction, so a principal never exists without a key. + * + * Each method accepts an optional transaction handle so the principal row and + * its key join the transaction that co-writes the principal's roles and grants. + * When no handle is passed the store opens its own transaction, because the + * principal insert and the key mint are two writes that must commit together. + */ +export function createPrincipalStore( + db: DBHandle, + principalKeyStore: PrincipalKeyStore, +) { + return { + /** + * Insert a new principal and mint its signing key, failing loudly on a + * natural-key conflict. Callers that have already established the principal + * is new (a fresh tenant owner, an invite past its existence pre-check) use + * this so a concurrent duplicate surfaces as a unique violation rather than + * a silent success. + */ + async create( + row: PrincipalInsert, + tx?: DBExecutor, + ): Promise { + const run = async (executor: DBExecutor): Promise => { + const [inserted] = await executor + .insert(principal) + .values(row) + .returning(); + if (inserted === undefined) { + throw new Error( + `principalStore.create: insert returned no row for ${row.id}`, + ); + } + await principalKeyStore.generate(inserted.id, executor); + return parsePrincipalRow(inserted); + }; + return tx === undefined ? db.transaction(run) : run(tx); + }, + + /** + * Idempotent variant of `create` for the run path, where concurrent first + * deliveries race to reserve the same principal. On a win it inserts the + * principal, mints its key, and returns the parsed row; on a loss it returns + * `null` WITHOUT minting -- the winning transaction already minted the one + * active key -- so the caller can fall back to the winner's state. + * + * The conflict target is the natural key `(tenantId, kind, refId)`, NOT the + * surrogate `id`: user principals carry a random `generateId("principal")`, + * so their only real conflict key is the natural triple. The run path + * derives a deterministic id that agrees with the triple, so one target + * unifies both callers. Do not switch this to `principal.id`. + */ + async createIfAbsent( + row: PrincipalInsert, + tx?: DBExecutor, + ): Promise { + const run = async ( + executor: DBExecutor, + ): Promise => { + const [inserted] = await executor + .insert(principal) + .values(row) + .onConflictDoNothing({ + target: [principal.tenantId, principal.kind, principal.refId], + }) + .returning(); + if (inserted === undefined) return null; + await principalKeyStore.generate(inserted.id, executor); + return parsePrincipalRow(inserted); + }; + return tx === undefined ? db.transaction(run) : run(tx); + }, + }; +} + +export type PrincipalStore = ReturnType; diff --git a/vendor/intx/db/src/schema/index.ts b/vendor/intx/db/src/schema/index.ts index 84c489b793..158e2d8e5d 100644 --- a/vendor/intx/db/src/schema/index.ts +++ b/vendor/intx/db/src/schema/index.ts @@ -1,6 +1,7 @@ export * from "./auth"; export * from "./tenants"; export * from "./principals"; +export * from "./principal-keys"; export * from "./roles"; export * from "./grants"; export * from "./approvals"; diff --git a/vendor/intx/db/src/schema/principal-keys.ts b/vendor/intx/db/src/schema/principal-keys.ts new file mode 100644 index 0000000000..c32b4537a0 --- /dev/null +++ b/vendor/intx/db/src/schema/principal-keys.ts @@ -0,0 +1,34 @@ +import { sql } from "drizzle-orm"; +import { pgTable, text, timestamp, uniqueIndex } from "drizzle-orm/pg-core"; + +import { principal } from "./principals"; + +// A principal's Ed25519 signing key. `public_key` is the hex-encoded 32-byte +// public point; `private_key` holds the hex-encoded 32-byte seed sealed by the +// credential cipher (`enc:aead:...` ciphertext), so the seed is never at rest in +// plaintext under a configured key. The hub custodies the private key: a +// signature attributes an action to the principal, but is not non-repudiable +// against the hub operator. See docs/AUTH.md. +// +// The `status` axis expresses the one-active-key-per-principal invariant, which +// the partial unique index below enforces at the database. No path retires a +// key; every key a principal holds today is "active". +export const principalKey = pgTable( + "principal_key", + { + id: text("id").primaryKey(), + principalId: text("principal_id") + .notNull() + .references(() => principal.id, { onDelete: "cascade" }), + publicKey: text("public_key").notNull().unique(), + privateKey: text("private_key").notNull(), + status: text("status", { enum: ["active", "retired"] }).notNull(), + createdAt: timestamp("created_at").notNull().defaultNow(), + updatedAt: timestamp("updated_at").notNull().defaultNow(), + }, + (t) => [ + uniqueIndex("principal_key_one_active") + .on(t.principalId) + .where(sql`${t.status} = 'active'`), + ], +); diff --git a/vendor/intx/db/src/signer-identity.ts b/vendor/intx/db/src/signer-identity.ts new file mode 100644 index 0000000000..4ab4057432 --- /dev/null +++ b/vendor/intx/db/src/signer-identity.ts @@ -0,0 +1,24 @@ +import type { LocalPrincipalSigner } from "@intx/types"; + +import type { DBExecutor } from "./client"; +import type { PrincipalKeyStore } from "./principal-key-store"; + +/** + * Resolve a principal's trusted signer identity from the hub's own key store. + * + * The returned `publicKey` is the principal's active key as the hub holds it, so + * a verifier can check a signature against it without trusting any key on the + * wire. Throws when the principal has no active key -- an unresolvable signer + * must fail loudly rather than default. + */ +export async function lookupLocalPrincipalSigner( + principalKeyStore: PrincipalKeyStore, + principalId: string, + tx?: DBExecutor, +): Promise { + return { + kind: "local-principal", + principalId, + publicKey: await principalKeyStore.getPublicKey(principalId, tx), + }; +} diff --git a/vendor/intx/hub-api/src/app.ts b/vendor/intx/hub-api/src/app.ts index c0d51a60c6..186aa8f2ba 100644 --- a/vendor/intx/hub-api/src/app.ts +++ b/vendor/intx/hub-api/src/app.ts @@ -10,9 +10,11 @@ import type { CredentialCipher } from "@intx/types"; import { type DB, type ApprovalStore, + type PrincipalKeyStore, type SignalCorrelationStore, createGrantStore, createApprovalStore, + createPrincipalKeyStore, createSignalCorrelationStore, } from "@intx/db"; import type { ConditionRegistry, GrantStore } from "@intx/types/authz"; @@ -97,6 +99,26 @@ function resolveCredentialCipher( return createNoopCredentialCipher(); } +/** + * Resolve the principal key store, falling back to a noop-cipher store when + * none is provided. The fallback seals nothing, so a minted signing key's + * private seed is stored in the CLEAR; it is expected only in tests and local + * development and warns loudly so a production deployment that forgot to + * configure `PRINCIPAL_KEY_ENCRYPTION_KEY` is not silently persisting private + * keys unencrypted. The composition root (`apps/hub`) always supplies a real + * store, gated by a required key at boot. + */ +function resolvePrincipalKeyStore( + provided: PrincipalKeyStore | undefined, + db: DB["db"], +): PrincipalKeyStore { + if (provided) return provided; + log.warn( + "No principalKeyStore configured; principal signing keys will be stored UNENCRYPTED. Expected in tests/local dev but MUST NOT happen in production.", + ); + return createPrincipalKeyStore({ db, cipher: createNoopCredentialCipher() }); +} + export type CreateHubContextMiddlewareDeps = { getSession: GetSession; }; @@ -126,6 +148,22 @@ export type MountHubRoutesDeps = { * secrets can omit it. */ credentialCipher?: CredentialCipher; + /** + * Mints and custodies the per-principal signing key on principal creation. + * Optional: when omitted, a noop-cipher store is used and a warning is + * logged. Production supplies a real store gated by + * `PRINCIPAL_KEY_ENCRYPTION_KEY`; tests may omit it. + */ + principalKeyStore?: PrincipalKeyStore; + /** + * Resolves a sidecar bearer token + run address to the tenant/principal + * it acts as. When supplied, `POST/GET .../workflows/deployments` + * additionally accepts a bearer-authenticated workflow-run caller (see + * `./middleware/workflow-run-deploy-auth.ts`), mirroring every other + * workflow-run write surface. Omitted in tests that have no reason to + * exercise that path -- the session-cookie path is unaffected either way. + */ + workflowRunAuthenticator?: WorkflowRunAuthenticator; grantStore?: GrantStore; conditionRegistry?: ConditionRegistry; approvalStore?: ApprovalStore; @@ -147,15 +185,6 @@ export type MountHubRoutesDeps = { * asset surface supply their own cap. */ maxTarballBytes: number; - /** - * Resolves a sidecar bearer token + run address to the tenant/principal - * it acts as. When supplied, `POST/GET .../workflows/deployments` - * additionally accepts a bearer-authenticated workflow-run caller (see - * `./middleware/workflow-run-deploy-auth.ts`), mirroring every other - * workflow-run write surface. Omitted in tests that have no reason to - * exercise that path -- the session-cookie path is unaffected either way. - */ - workflowRunAuthenticator?: WorkflowRunAuthenticator; }; /** @@ -173,6 +202,10 @@ export function mountHubRoutes( opts: MountHubRoutesDeps, ): void { const credentialCipher = resolveCredentialCipher(opts.credentialCipher); + const principalKeyStore = resolvePrincipalKeyStore( + opts.principalKeyStore, + opts.db, + ); const { db, sidecarRouter, @@ -185,6 +218,7 @@ export function mountHubRoutes( repoStore, readRunLifecycles, maxTarballBytes, + workflowRunAuthenticator, } = opts; if ((assetService === null) !== (repoStore === null)) { throw new Error( @@ -277,7 +311,7 @@ export function mountHubRoutes( app.use("/api/tenants/:tenantId/*", resolveTenant); // Global tenant routes (create needs auth, detail/update handle auth inline) - app.route("/api/tenants", createTenantRoutes({ db })); + app.route("/api/tenants", createTenantRoutes({ db, principalKeyStore })); // Tenant-scoped routes app.route( @@ -286,7 +320,7 @@ export function mountHubRoutes( ); app.route( "/api/tenants/:tenantId/members/invite", - createInviteRoutes({ db, requireGrant }), + createInviteRoutes({ db, principalKeyStore, requireGrant }), ); app.route( "/api/tenants/:tenantId/roles", @@ -313,6 +347,7 @@ export function mountHubRoutes( "/api/tenants/:tenantId/workflows/runs", createRunRoutes({ db, + principalKeyStore, sessionService, sidecarRouter, eventCollectors, @@ -346,6 +381,7 @@ export function mountHubRoutes( "/api/tenants/:tenantId/workflows", createWorkflowRoutes({ db, + principalKeyStore, ...(workflowAllocationService !== undefined ? { workflowAllocationService } : {}), @@ -534,6 +570,15 @@ export type CreateAppOpts = { * secrets can omit it. */ credentialCipher?: CredentialCipher; + /** + * Mints and custodies the per-principal signing key on principal creation. + * Optional: when omitted, a noop-cipher store is used and a warning is + * logged. Production supplies a real store gated by + * `PRINCIPAL_KEY_ENCRYPTION_KEY`. + */ + principalKeyStore?: PrincipalKeyStore; + /** See `MountHubRoutesDeps.workflowRunAuthenticator`. */ + workflowRunAuthenticator?: WorkflowRunAuthenticator; grantStore?: GrantStore; approvalStore?: ApprovalStore; signalCorrelationStore?: SignalCorrelationStore; @@ -547,8 +592,6 @@ export type CreateAppOpts = { * (or its config default) and supplies a concrete value. */ maxTarballBytes: number; - /** See `MountHubRoutesDeps.workflowRunAuthenticator`. */ - workflowRunAuthenticator?: WorkflowRunAuthenticator; }; export function createApp({ @@ -561,6 +604,8 @@ export function createApp({ workflowDispatchService, eventCollectors, credentialCipher, + principalKeyStore, + workflowRunAuthenticator, grantStore, approvalStore, signalCorrelationStore, @@ -569,7 +614,6 @@ export function createApp({ assetService, repoStore, maxTarballBytes, - workflowRunAuthenticator, }: CreateAppOpts) { const app = new Hono(); @@ -596,12 +640,13 @@ export function createApp({ : {}), eventCollectors, ...(credentialCipher ? { credentialCipher } : {}), - assetService, - repoStore, - maxTarballBytes, + ...(principalKeyStore ? { principalKeyStore } : {}), ...(workflowRunAuthenticator !== undefined ? { workflowRunAuthenticator } : {}), + assetService, + repoStore, + maxTarballBytes, ...(grantStore ? { grantStore } : {}), ...(approvalStore ? { approvalStore } : {}), ...(signalCorrelationStore ? { signalCorrelationStore } : {}), diff --git a/vendor/intx/hub-api/src/middleware/workflow-run-deploy-auth.test.ts b/vendor/intx/hub-api/src/middleware/workflow-run-deploy-auth.test.ts index 0ff1cf7895..31996bebd7 100644 --- a/vendor/intx/hub-api/src/middleware/workflow-run-deploy-auth.test.ts +++ b/vendor/intx/hub-api/src/middleware/workflow-run-deploy-auth.test.ts @@ -16,8 +16,14 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { and, eq } from "drizzle-orm"; import { Hono } from "hono"; -import { createDB, schema, type DB } from "@intx/db"; +import { + createDB, + createPrincipalKeyStore, + schema, + type DB, +} from "@intx/db"; import { createInMemoryGrantStore } from "@intx/authz"; +import { createNoopCredentialCipher } from "@intx/crypto"; import { generateId } from "@intx/hub-common"; import type { PrepareProvisionedWorkflowDeploymentArgs, @@ -204,6 +210,10 @@ describeIfDb("workflow deploy: bearer mirror of the session deploy route", () => "/api/tenants/:tenantId/workflows", createWorkflowRoutes({ db: db.db, + principalKeyStore: createPrincipalKeyStore({ + db: db.db, + cipher: createNoopCredentialCipher(), + }), workflowAllocationService: fakeAllocationService(), sidecarRouter: {} as never, repoStore: {} as never, @@ -283,6 +293,10 @@ describeIfDb("workflow deploy: bearer mirror of the session deploy route", () => "/api/tenants/:tenantId/workflows", createWorkflowRoutes({ db: db.db, + principalKeyStore: createPrincipalKeyStore({ + db: db.db, + cipher: createNoopCredentialCipher(), + }), workflowAllocationService: fakeAllocationService(), sidecarRouter: {} as never, repoStore: {} as never, diff --git a/vendor/intx/hub-api/src/routes/principals.ts b/vendor/intx/hub-api/src/routes/principals.ts index 0fa5e710c1..cdf8ced236 100644 --- a/vendor/intx/hub-api/src/routes/principals.ts +++ b/vendor/intx/hub-api/src/routes/principals.ts @@ -3,8 +3,8 @@ import { Hono } from "hono"; import { describeRoute, resolver, validator } from "hono-openapi"; import { principal, principalRole, role, user } from "@intx/db/schema"; -import { parsePrincipalRow } from "@intx/db"; -import type { DB } from "@intx/db"; +import { createPrincipalStore, parsePrincipalRow } from "@intx/db"; +import type { DB, PrincipalKeyStore } from "@intx/db"; import { PrincipalResponse, UpdatePrincipal, @@ -14,7 +14,7 @@ import { } from "@intx/types"; import type { TenantEnv } from "../context"; -import { first, ts } from "../format"; +import { ts } from "../format"; import { generateId } from "@intx/hub-common"; import { idResource } from "../middleware/grant"; import type { RequireGrant } from "../middleware/grant"; @@ -373,14 +373,17 @@ export function createPrincipalRoutes({ // Invite is mounted separately at ../members/invite in app.ts export type CreateInviteRoutesDeps = { db: DB["db"]; + principalKeyStore: PrincipalKeyStore; requireGrant: RequireGrant; }; export function createInviteRoutes({ db, + principalKeyStore, requireGrant, }: CreateInviteRoutesDeps): Hono { const inviteApp = new Hono(); + const principalStore = createPrincipalStore(db, principalKeyStore); inviteApp.post( "/", @@ -447,38 +450,42 @@ export function createInviteRoutes({ } const now = new Date(); - const principalId = generateId("principal"); - const row = first( - await db - .insert(principal) - .values({ - id: principalId, + const roleRow = body.roleId + ? await db.query.role.findFirst({ + where: and( + eq(role.id, body.roleId), + eq(role.tenantId, tenantCtx.id), + ), + }) + : undefined; + + const row = await db.transaction(async (tx) => { + const invited = await principalStore.create( + { + id: generateId("principal"), tenantId: tenantCtx.id, kind: "user", refId: invitedUser.id, status: "invited", createdAt: now, updatedAt: now, - }) - .returning(), - ); - - let roles: { id: string; name: string }[] = []; - - if (body.roleId) { - const roleRow = await db.query.role.findFirst({ - where: and(eq(role.id, body.roleId), eq(role.tenantId, tenantCtx.id)), - }); + }, + tx, + ); if (roleRow) { - await db.insert(principalRole).values({ - principalId, + await tx.insert(principalRole).values({ + principalId: invited.id, roleId: roleRow.id, createdAt: now, }); - roles = [{ id: roleRow.id, name: roleRow.name }]; } - } + return invited; + }); + + const roles: { id: string; name: string }[] = roleRow + ? [{ id: roleRow.id, name: roleRow.name }] + : []; const identity: ResolvedIdentity = { displayName: invitedUser.name, diff --git a/vendor/intx/hub-api/src/routes/runs.ts b/vendor/intx/hub-api/src/routes/runs.ts index c1d85ccfc1..05812ccfdf 100644 --- a/vendor/intx/hub-api/src/routes/runs.ts +++ b/vendor/intx/hub-api/src/routes/runs.ts @@ -9,7 +9,7 @@ import { workflowDefinition, workflowRun, } from "@intx/db/schema"; -import type { DB, ApprovalStore } from "@intx/db"; +import type { DB, ApprovalStore, PrincipalKeyStore } from "@intx/db"; import { authorize } from "@intx/authz"; import type { ConditionRegistry, GrantStore } from "@intx/types/authz"; import { extractPartByPath } from "@intx/mime"; @@ -150,6 +150,7 @@ function runToRecord(row: RunListRow): RoutableRecord { export type CreateRunRoutesDeps = { db: DB["db"]; + principalKeyStore: PrincipalKeyStore; sessionService: SessionService; sidecarRouter: SidecarRouter; eventCollectors: EventCollectorRegistry; @@ -172,6 +173,7 @@ export type CreateRunRoutesDeps = { export function createRunRoutes({ db, + principalKeyStore, sidecarRouter, eventCollectors, repoStore, @@ -196,6 +198,7 @@ export function createRunRoutes({ repoStore !== null ? createWorkflowRunTrigger({ db, + principalKeyStore, grantStore, sidecarRouter, ...(workflowDispatchService !== undefined diff --git a/vendor/intx/hub-api/src/routes/tenants.ts b/vendor/intx/hub-api/src/routes/tenants.ts index 32ff5a10a1..2a57ef7a19 100644 --- a/vendor/intx/hub-api/src/routes/tenants.ts +++ b/vendor/intx/hub-api/src/routes/tenants.ts @@ -3,8 +3,8 @@ import { Hono } from "hono"; import { describeRoute, resolver, validator } from "hono-openapi"; import { tenant, principal, role, principalRole, grant } from "@intx/db/schema"; -import { parseTenantRow } from "@intx/db"; -import type { DB } from "@intx/db"; +import { createPrincipalStore, parseTenantRow } from "@intx/db"; +import type { DB, PrincipalKeyStore } from "@intx/db"; import { CreateTenant, UpdateTenant, @@ -34,12 +34,15 @@ function formatTenant(row: typeof tenant.$inferSelect) { export type CreateTenantRoutesDeps = { db: DB["db"]; + principalKeyStore: PrincipalKeyStore; }; export function createTenantRoutes({ db, + principalKeyStore, }: CreateTenantRoutesDeps): Hono { const app = new Hono(); + const principalStore = createPrincipalStore(db, principalKeyStore); app.post( "/", @@ -94,124 +97,130 @@ export function createTenantRoutes({ const now = new Date(); - const tenantRow = first( - await db - .insert(tenant) - .values({ - id: tenantId, - name: body.name, - slug: body.slug, - domain, - parentId: body.parentId ?? null, - createdAt: now, - updatedAt: now, - }) - .returning(), - ); - - const roleIds: Record = {}; - for (const roleName of SYSTEM_ROLES) { - const roleId = generateId("role"); - roleIds[roleName] = roleId; - await db.insert(role).values({ - id: roleId, - tenantId, - name: roleName, - description: `System ${roleName} role`, - isSystem: true, - createdAt: now, - updatedAt: now, - }); - } - - const ownerRoleId = roleIds["owner"]; - if (!ownerRoleId) throw new Error("Owner role was not created"); - - const principalId = generateId("principal"); - await db.insert(principal).values({ - id: principalId, - tenantId, - kind: "user", - refId: user.id, - status: "active", - createdAt: now, - updatedAt: now, - }); - - await db.insert(principalRole).values({ - principalId, - roleId: ownerRoleId, - createdAt: now, - }); - - // Grant owner role full access - await db.insert(grant).values({ - id: generateId("grant"), - tenantId, - roleId: ownerRoleId, - resource: "*", - action: "*", - effect: "allow", - origin: "system", - createdAt: now, - updatedAt: now, - }); + const tenantRow = await db.transaction(async (tx) => { + const inserted = first( + await tx + .insert(tenant) + .values({ + id: tenantId, + name: body.name, + slug: body.slug, + domain, + parentId: body.parentId ?? null, + createdAt: now, + updatedAt: now, + }) + .returning(), + ); - // Grant admin role broad management access - const adminRoleId = roleIds["admin"]; - if (adminRoleId) { - await db.insert(grant).values([ - { - id: generateId("grant"), - tenantId, - roleId: adminRoleId, - resource: "*", - action: "read", - effect: "allow", - origin: "system", - createdAt: now, - updatedAt: now, - }, - { - id: generateId("grant"), + const roleIds: Record = {}; + for (const roleName of SYSTEM_ROLES) { + const roleId = generateId("role"); + roleIds[roleName] = roleId; + await tx.insert(role).values({ + id: roleId, tenantId, - roleId: adminRoleId, - resource: "*", - action: "create", - effect: "allow", - origin: "system", + name: roleName, + description: `System ${roleName} role`, + isSystem: true, createdAt: now, updatedAt: now, - }, + }); + } + + const ownerRoleId = roleIds["owner"]; + if (!ownerRoleId) throw new Error("Owner role was not created"); + + const ownerPrincipal = await principalStore.create( { - id: generateId("grant"), + id: generateId("principal"), tenantId, - roleId: adminRoleId, - resource: "*", - action: "manage", - effect: "allow", - origin: "system", + kind: "user", + refId: user.id, + status: "active", createdAt: now, updatedAt: now, }, - ]); - } + tx, + ); - // Grant member role read-only access - const memberRoleId = roleIds["member"]; - if (memberRoleId) { - await db.insert(grant).values({ + await tx.insert(principalRole).values({ + principalId: ownerPrincipal.id, + roleId: ownerRoleId, + createdAt: now, + }); + + // Grant owner role full access + await tx.insert(grant).values({ id: generateId("grant"), tenantId, - roleId: memberRoleId, + roleId: ownerRoleId, resource: "*", - action: "read", + action: "*", effect: "allow", origin: "system", createdAt: now, updatedAt: now, }); - } + + // Grant admin role broad management access + const adminRoleId = roleIds["admin"]; + if (adminRoleId) { + await tx.insert(grant).values([ + { + id: generateId("grant"), + tenantId, + roleId: adminRoleId, + resource: "*", + action: "read", + effect: "allow", + origin: "system", + createdAt: now, + updatedAt: now, + }, + { + id: generateId("grant"), + tenantId, + roleId: adminRoleId, + resource: "*", + action: "create", + effect: "allow", + origin: "system", + createdAt: now, + updatedAt: now, + }, + { + id: generateId("grant"), + tenantId, + roleId: adminRoleId, + resource: "*", + action: "manage", + effect: "allow", + origin: "system", + createdAt: now, + updatedAt: now, + }, + ]); + } + + // Grant member role read-only access + const memberRoleId = roleIds["member"]; + if (memberRoleId) { + await tx.insert(grant).values({ + id: generateId("grant"), + tenantId, + roleId: memberRoleId, + resource: "*", + action: "read", + effect: "allow", + origin: "system", + createdAt: now, + updatedAt: now, + }); + } + + return inserted; + }); return c.json(formatTenant(tenantRow), 201); }, diff --git a/vendor/intx/hub-api/src/routes/workflows.ts b/vendor/intx/hub-api/src/routes/workflows.ts index 6a844b3e9b..ef41b8ccbf 100644 --- a/vendor/intx/hub-api/src/routes/workflows.ts +++ b/vendor/intx/hub-api/src/routes/workflows.ts @@ -12,7 +12,11 @@ import { workflowDefinition, workflowRun, } from "@intx/db/schema"; -import { WorkflowRunDispatchPayloadConflictError, type DB } from "@intx/db"; +import { + WorkflowRunDispatchPayloadConflictError, + type DB, + type PrincipalKeyStore, +} from "@intx/db"; import type { GrantStore } from "@intx/types/authz"; import { correlationIdFromSignalName, @@ -188,6 +192,7 @@ async function deploymentAnchorRunExists( export type CreateWorkflowRoutesDeps = { db: DB["db"]; + principalKeyStore: PrincipalKeyStore; workflowAllocationService?: WorkflowAllocationService; workflowDispatchService?: WorkflowDispatchService; sidecarRouter: SidecarRouter; @@ -198,6 +203,7 @@ export type CreateWorkflowRoutesDeps = { export function createWorkflowRoutes({ db, + principalKeyStore, workflowAllocationService, workflowDispatchService, sidecarRouter, @@ -209,6 +215,7 @@ export function createWorkflowRoutes({ const runReader = createWorkflowRunReader(repoStore); const triggerWorkflowRun = createWorkflowRunTrigger({ db, + principalKeyStore, grantStore, sidecarRouter, ...(workflowDispatchService !== undefined diff --git a/vendor/intx/hub-api/src/run-grant-materialization.ts b/vendor/intx/hub-api/src/run-grant-materialization.ts index 6f8b4469e7..c1592351e5 100644 --- a/vendor/intx/hub-api/src/run-grant-materialization.ts +++ b/vendor/intx/hub-api/src/run-grant-materialization.ts @@ -26,8 +26,12 @@ import { workflowDefinition, workflowRun, } from "@intx/db/schema"; -import type { DB, DBExecutor } from "@intx/db"; -import { createWorkflowRunStore, loadFrozenGrantSnapshot } from "@intx/db"; +import type { DB, DBExecutor, PrincipalKeyStore } from "@intx/db"; +import { + createPrincipalStore, + createWorkflowRunStore, + loadFrozenGrantSnapshot, +} from "@intx/db"; import type { GrantStore, GrantRule } from "@intx/types/authz"; import { isSidecarAllocationDispatchable, @@ -278,6 +282,7 @@ export async function collectCreatorGrants( export type CommitRunGrantsArgs = { db: DB["db"]; + principalKeyStore: PrincipalKeyStore; tenantId: string; anchorRunId: string; /** @@ -480,6 +485,7 @@ export async function commitRunGrants( tx?: DBExecutor, ): Promise { const workflowRunStore = createWorkflowRunStore(args.db); + const principalStore = createPrincipalStore(args.db, args.principalKeyStore); const commit = async ( executor: DBExecutor, ): Promise => { @@ -490,9 +496,8 @@ export async function commitRunGrants( ); if (existing !== null) return existing.stepGrants; - const [insertedPrincipal] = await executor - .insert(principalTable) - .values({ + const insertedPrincipal = await principalStore.createIfAbsent( + { id: args.runPrincipalId, tenantId: args.tenantId, kind: "workflow", @@ -500,16 +505,10 @@ export async function commitRunGrants( status: "active", createdAt: args.now, updatedAt: args.now, - }) - .onConflictDoNothing({ - target: [ - principalTable.tenantId, - principalTable.kind, - principalTable.refId, - ], - }) - .returning({ id: principalTable.id }); - if (insertedPrincipal === undefined) { + }, + executor, + ); + if (insertedPrincipal === null) { const winner = await loadCommittedRunGrantsFromExecutor( executor, args.tenantId, @@ -549,6 +548,7 @@ export async function commitRunGrants( export type MailTriggeredRunGrantsDeps = { db: DB["db"]; + principalKeyStore: PrincipalKeyStore; grantStore: GrantStore; }; @@ -745,6 +745,7 @@ export function createMailTriggeredRunGrantsMaterializer( return commitRunGrants( { db: deps.db, + principalKeyStore: deps.principalKeyStore, tenantId, anchorRunId, definitionId: anchor.definitionId, diff --git a/vendor/intx/hub-api/src/workflow-run-trigger.ts b/vendor/intx/hub-api/src/workflow-run-trigger.ts index 181dc6ae2a..cb43aad681 100644 --- a/vendor/intx/hub-api/src/workflow-run-trigger.ts +++ b/vendor/intx/hub-api/src/workflow-run-trigger.ts @@ -22,7 +22,7 @@ import { workflowDefinition, workflowRun, } from "@intx/db/schema"; -import type { DB } from "@intx/db"; +import type { DB, PrincipalKeyStore } from "@intx/db"; import { loadFrozenGrantSnapshot } from "@intx/db"; import type { GrantStore } from "@intx/types/authz"; import { @@ -82,6 +82,7 @@ export const WorkflowRunTriggerResponse = type({ export type TriggerWorkflowRunDeps = { db: DB["db"]; + principalKeyStore: PrincipalKeyStore; grantStore: GrantStore; sidecarRouter: SidecarRouter; workflowDispatchService?: WorkflowDispatchService; @@ -117,8 +118,14 @@ export type TriggerWorkflowRunResult = * result the caller maps onto its route surface. */ export function createWorkflowRunTrigger(deps: TriggerWorkflowRunDeps) { - const { db, grantStore, sidecarRouter, workflowDispatchService, repoStore } = - deps; + const { + db, + principalKeyStore, + grantStore, + sidecarRouter, + workflowDispatchService, + repoStore, + } = deps; async function readRunLifecycle( anchorRunId: string, @@ -436,6 +443,7 @@ export function createWorkflowRunTrigger(deps: TriggerWorkflowRunDeps) { const canonicalStepGrants = await commitRunGrants( { db, + principalKeyStore, tenantId: tenant.id, anchorRunId, definitionId: anchor.definitionId, @@ -495,6 +503,7 @@ export function createWorkflowRunTrigger(deps: TriggerWorkflowRunDeps) { return commitRunGrants( { db, + principalKeyStore, tenantId: tenant.id, anchorRunId, definitionId: anchor.definitionId, diff --git a/vendor/intx/hub-common/README.md b/vendor/intx/hub-common/README.md new file mode 100644 index 0000000000..a048ddda70 --- /dev/null +++ b/vendor/intx/hub-common/README.md @@ -0,0 +1,24 @@ +# @intx/hub-common + +Utilities shared across the hub packages. This package has a generic name and +is therefore at risk of becoming a junk drawer. A new module belongs here only +if it satisfies all of the following: + +1. It is genuinely needed by more than one of the hub packages + (`@intx/hub-api`, `@intx/hub-sessions`, or future hub + packages). Code used by exactly one package belongs in that package. +2. It encodes hub-schema-owner knowledge that does not belong in + `@intx/types`. For example, the id prefix table here is the + identity namespace of the hub's database schema, so it lives with the + schema owner. By contrast, a generic hex encoder belongs in + `@intx/types` (and in fact `@intx/types` already owns one); + do not duplicate it here. +3. It is not session-plane logic, HTTP-surface logic, or a database concern + -- those have dedicated packages. +4. It depends only on node built-in modules. External dependencies signal + that the code probably wants a more specific home. + +If this package grows beyond a handful of small modules, that is a signal +to find the real boundary, not to keep adding here. + +See `LAYOUT.md` for the package layout this fits into. diff --git a/vendor/intx/hub-common/package.json b/vendor/intx/hub-common/package.json new file mode 100644 index 0000000000..fbe28b7ded --- /dev/null +++ b/vendor/intx/hub-common/package.json @@ -0,0 +1,32 @@ +{ + "name": "@intx/hub-common", + "description": "Shared utilities for the Interchange hub packages", + "version": "0.3.0", + "license": "LGPL-2.1-only", + "type": "module", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + }, + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@intx/types": "workspace:*" + }, + "devDependencies": { + "@types/bun": "catalog:", + "typescript": "catalog:" + }, + "files": [ + "src", + "README.md", + "LICENSE" + ], + "sideEffects": false, + "publishConfig": { + "access": "public" + } +} diff --git a/vendor/intx/hub-common/src/action-mapping.ts b/vendor/intx/hub-common/src/action-mapping.ts new file mode 100644 index 0000000000..dae9712348 --- /dev/null +++ b/vendor/intx/hub-common/src/action-mapping.ts @@ -0,0 +1,126 @@ +import type { RepoAction } from "@intx/types/sidecar"; + +/** + * Translations between the HTTP request shape, the `RepoAction` + * vocabulary used by the repo-store substrate, and the verb + * vocabulary used by the grant store. This module is the single + * source of truth: both the bearer middleware (which queries authz + * with `repoActionToGrantVerb(httpToRepoAction(req))`) and the kind + * handler (which receives the resolved `RepoAction`) import from + * here, so the two layers cannot drift. + */ + +export type HTTPRequestShape = { + method: string; + path: string; + query: Record; +}; + +const UPLOAD_PACK_SERVICE = "git-upload-pack"; +const RECEIVE_PACK_SERVICE = "git-receive-pack"; + +/** + * Resolve a smart-HTTP request to the `RepoAction` it requires. The + * match is on the trailing smart-HTTP suffix so the same logic works + * for every mount prefix (asset routes, agent-state routes, etc.). + * + * Throws when the request shape is not a recognised smart-HTTP + * endpoint; callers are expected to gate this behind their route + * matcher. + */ +export function httpToRepoAction(req: HTTPRequestShape): RepoAction { + const path = req.path; + const method = req.method.toUpperCase(); + + if (method === "GET" && hasSuffix(path, "/info/refs")) { + const service = req.query.service; + if (service === undefined) { + throw new Error( + "unrecognised git smart-HTTP request: /info/refs missing service query", + ); + } + if (service !== UPLOAD_PACK_SERVICE && service !== RECEIVE_PACK_SERVICE) { + throw new Error( + `unrecognised git smart-HTTP request: unknown service ${service}`, + ); + } + return "resolveRef"; + } + + if (method === "POST" && hasSuffix(path, "/git-upload-pack")) { + return "createPack"; + } + + if (method === "POST" && hasSuffix(path, "/git-receive-pack")) { + return "receivePack"; + } + + throw new Error(`unrecognised git smart-HTTP request: ${method} ${path}`); +} + +function hasSuffix(path: string, suffix: string): boolean { + return path === suffix || path.endsWith(suffix); +} + +/** + * Translate a `RepoAction` to the grant verb used by the authz + * grant store. Every `RepoAction` has exactly one verb; an + * exhaustive switch makes the compiler enforce coverage when the + * `RepoAction` union grows. + */ +export function repoActionToGrantVerb(action: RepoAction): string { + switch (action) { + case "init": + return "create"; + case "writeTree": + case "receivePack": + return "write"; + case "createPack": + case "resolveRef": + return "read"; + } +} + +/** + * Mint-API friendly aliases that expand to one or more + * `RepoAction`s. Callers that issue a token specify `actions: + * ["can_read"]` or `actions: ["can_push"]` rather than enumerating + * the underlying repo-store verbs. + */ +export const RepoActionAliases = { + can_read: ["createPack", "resolveRef"], + can_push: ["receivePack"], +} as const satisfies Record; + +/** + * Expand a mint-API actions string into the underlying RepoActions. + * Accepts both aliases (`can_read`, `can_push`) and bare RepoAction + * names. Unknown strings throw — callers receive the rejection at + * the mint boundary rather than producing an empty grant set. + */ +export function expandRepoActionAlias(name: string): RepoAction[] { + const alias = lookupAlias(name); + if (alias !== null) return [...alias]; + const action = lookupRepoAction(name); + if (action !== null) return [action]; + throw new Error(`unknown RepoAction alias: ${name}`); +} + +function lookupAlias(name: string): readonly RepoAction[] | null { + if (name === "can_read") return RepoActionAliases.can_read; + if (name === "can_push") return RepoActionAliases.can_push; + return null; +} + +function lookupRepoAction(name: string): RepoAction | null { + switch (name) { + case "init": + case "writeTree": + case "receivePack": + case "createPack": + case "resolveRef": + return name; + default: + return null; + } +} diff --git a/vendor/intx/hub-common/src/glob.ts b/vendor/intx/hub-common/src/glob.ts new file mode 100644 index 0000000000..858cfa5ea6 --- /dev/null +++ b/vendor/intx/hub-common/src/glob.ts @@ -0,0 +1,94 @@ +/** + * Self-contained simple-glob matcher used to validate `refPattern` + * grammar at the mint-endpoint boundary and to filter ref + * advertisements at the route layer. Grammar: + * + * - literal characters match themselves + * - `*` matches any run of characters within a single `/`-delimited + * segment (does not cross `/`) + * - `**` matches any run of characters including `/` (crosses + * segments; may match zero segments) + * + * No regex backend — the matcher operates directly on the input and + * pattern token streams. + */ + +type Token = + | { kind: "literal"; text: string } + | { kind: "star" } + | { kind: "doublestar" }; + +function tokenize(pattern: string): Token[] { + const out: Token[] = []; + let i = 0; + let literal = ""; + while (i < pattern.length) { + if (pattern[i] === "*") { + if (literal.length > 0) { + out.push({ kind: "literal", text: literal }); + literal = ""; + } + if (i + 1 < pattern.length && pattern[i + 1] === "*") { + out.push({ kind: "doublestar" }); + i += 2; + } else { + out.push({ kind: "star" }); + i += 1; + } + } else { + literal += pattern[i]; + i += 1; + } + } + if (literal.length > 0) { + out.push({ kind: "literal", text: literal }); + } + return out; +} + +function matchTokens( + tokens: Token[], + ti: number, + input: string, + ii: number, +): boolean { + if (ti === tokens.length) { + return ii === input.length; + } + const tok = tokens[ti]; + if (tok === undefined) { + throw new Error(`glob: token index ${ti} out of range`); + } + if (tok.kind === "literal") { + const text = tok.text; + if (ii + text.length > input.length) return false; + for (let k = 0; k < text.length; k++) { + if (input[ii + k] !== text[k]) return false; + } + return matchTokens(tokens, ti + 1, input, ii + text.length); + } + if (tok.kind === "star") { + for (let j = ii; j <= input.length; j++) { + if (j > ii && input[j - 1] === "/") break; + if (matchTokens(tokens, ti + 1, input, j)) return true; + if (j === input.length) break; + } + return matchTokens(tokens, ti + 1, input, ii); + } + // doublestar + for (let j = ii; j <= input.length; j++) { + if (matchTokens(tokens, ti + 1, input, j)) return true; + } + return false; +} + +export const glob = { + /** + * Test whether `input` matches `pattern` under the simple-glob + * grammar described above. + */ + match(pattern: string, input: string): boolean { + const tokens = tokenize(pattern); + return matchTokens(tokens, 0, input, 0); + }, +}; diff --git a/vendor/intx/hub-common/src/ids.ts b/vendor/intx/hub-common/src/ids.ts new file mode 100644 index 0000000000..3e04db7939 --- /dev/null +++ b/vendor/intx/hub-common/src/ids.ts @@ -0,0 +1,85 @@ +import { hexEncode } from "@intx/types"; + +const PREFIXES = { + tenant: "tnt_", + principal: "prn_", + principalKey: "pky_", + role: "rol_", + grant: "grt_", + federationTrust: "ftr_", + provider: "prv_", + oauthClient: "ocl_", + credential: "crd_", + wallet: "wlt_", + transaction: "txn_", + offering: "ofr_", + model: "mdl_", + modelProvider: "mpv_", + modelOffering: "mof_", + modelPricing: "mpr_", + session: "ses_", + sessionMail: "sml_", + inferenceTurn: "itn_", + turnPart: "tp_", + asset: "ast_", + gitToken: "gtk_", + workflowRun: "run_", + approval: "apr_", + signal: "sig_", + workflowDefinition: "wfd_", + workflowDefinitionVersion: "wdv_", +} as const; + +type IDKind = keyof typeof PREFIXES; + +export function generateId(kind: IDKind): string { + const prefix = PREFIXES[kind]; + const bytes = hexEncode(crypto.getRandomValues(new Uint8Array(16))); + return `${prefix}${bytes}`; +} + +/** + * Derive the DETERMINISTIC principal id a workflow run's principal is + * minted under, keyed on `(tenantId, runId)`. Both the external trigger + * route and the mail-triggered run path derive the id this way, so a run + * has ONE principal id regardless of how many times its birth is + * attempted. + * + * Determinism is load-bearing for idempotency: a run's `runId` is the + * deployment's mail address (stable across a redelivery AND across every + * trigger of the deployment), and the `principal` table's + * `unique(tenantId, kind, refId=runId)` makes the insert an + * `onConflictDoNothing` no-op on every attempt after the first. A RANDOM + * id would leave that no-op pointing the fresh id nowhere while the grant + * rows referenced it, breaking the principal foreign key. Deriving the id + * from the run means the second attempt reuses the id already written, so + * the grant rows resolve against the principal that is actually present. + * + * The id is `prn_` + the first 16 bytes of `SHA-256(tenantId "\0" runId)` + * as hex, matching the shape `generateId("principal")` produces. The NUL + * separator keeps the two fields unambiguous so no `(tenantId, runId)` + * pair collides with another by concatenation. + */ +export async function deriveRunPrincipalId( + tenantId: string, + runId: string, +): Promise { + const digest = new Uint8Array( + await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(`${tenantId}\0${runId}`), + ), + ); + return `${PREFIXES.principal}${hexEncode(digest.subarray(0, 16))}`; +} + +/** + * Prefixes for the two flavours of git bearer-token secret. Personal + * access tokens (`PAT_PREFIX`) belong to a user; service tokens + * (`SVC_PREFIX`) are minted under a tenant on behalf of a principal. + * The single source of truth: both the mint endpoint and the bearer + * middleware import these so the secret shape cannot drift between + * the issuer and the validator. + */ +export const PAT_PREFIX = "itx_pat_"; +export const SVC_PREFIX = "itx_svc_"; diff --git a/vendor/intx/hub-common/src/index.ts b/vendor/intx/hub-common/src/index.ts new file mode 100644 index 0000000000..b0863eb50b --- /dev/null +++ b/vendor/intx/hub-common/src/index.ts @@ -0,0 +1,14 @@ +export { + generateId, + deriveRunPrincipalId, + PAT_PREFIX, + SVC_PREFIX, +} from "./ids"; +export { glob } from "./glob"; +export { + httpToRepoAction, + repoActionToGrantVerb, + RepoActionAliases, + expandRepoActionAlias, + type HTTPRequestShape, +} from "./action-mapping"; diff --git a/vendor/intx/hub-common/tsconfig.json b/vendor/intx/hub-common/tsconfig.json new file mode 100644 index 0000000000..3acc53cacc --- /dev/null +++ b/vendor/intx/hub-common/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.src.json", + "compilerOptions": { + "composite": false, + "noEmit": true, + "disableSourceOfProjectReferenceRedirect": true, + "declaration": false, + "declarationMap": false, + "emitDeclarationOnly": false, + "tsBuildInfoFile": "tsconfig.tsbuildinfo", + "rootDir": "../../.." + }, + "include": [ + "src", + "src/**/*.ts" + ], + "exclude": [], + "references": [ + { + "path": "../types/tsconfig.src.json" + } + ] +} diff --git a/vendor/intx/hub-common/tsconfig.src.json b/vendor/intx/hub-common/tsconfig.src.json new file mode 100644 index 0000000000..cb9a26719f --- /dev/null +++ b/vendor/intx/hub-common/tsconfig.src.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": [ + "src/**/*.ts", + "package.json", + "src/**/*.json" + ], + "exclude": [ + "src/**/*.test.ts", + "src/**/*.test.tsx" + ], + "compilerOptions": { + "types": [ + "bun" + ], + "composite": true, + "emitDeclarationOnly": true, + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.tsbuildinfo" + }, + "references": [ + { + "path": "../types/tsconfig.src.json" + } + ] +} diff --git a/vendor/intx/types/src/credential-cipher.ts b/vendor/intx/types/src/credential-cipher.ts index 1e3c253505..9f475c7de7 100644 --- a/vendor/intx/types/src/credential-cipher.ts +++ b/vendor/intx/types/src/credential-cipher.ts @@ -40,3 +40,16 @@ export interface CredentialCipher { export function credentialAad(id: string, column: string): string { return JSON.stringify(["credential-secret", id, column]); } + +/** + * Build the additional-authenticated-data string binding a principal signing + * key's sealed private material to the `principal_key` row and column it belongs + * to. Shares the AEAD primitive and encoding rules with `credentialAad` but uses + * a distinct `"principal-key"` tag domain, so a credential-secret ciphertext and + * a principal-key ciphertext are never interchangeable even under the same key. + * The mint (write) and sign (read) sites MUST build the `aad` through this one + * function so the value matches. + */ +export function principalKeyAad(id: string, column: string): string { + return JSON.stringify(["principal-key", id, column]); +} diff --git a/vendor/intx/types/src/index.ts b/vendor/intx/types/src/index.ts index a4ce686fe2..755229df80 100644 --- a/vendor/intx/types/src/index.ts +++ b/vendor/intx/types/src/index.ts @@ -16,6 +16,7 @@ export * from "./providers"; export * from "./oauth-clients"; export * from "./credentials"; export * from "./credential-cipher"; +export * from "./signer-identity"; export * from "./mediated-credential"; export * from "./assets"; export * from "./offerings"; diff --git a/vendor/intx/types/src/signer-identity.ts b/vendor/intx/types/src/signer-identity.ts new file mode 100644 index 0000000000..3737d06e04 --- /dev/null +++ b/vendor/intx/types/src/signer-identity.ts @@ -0,0 +1,33 @@ +// How the signer behind a signature is identified. +// +// The only signer today is a principal whose Ed25519 private key the hub +// custodies (`local-principal`). The union is keyed on `kind` so a future +// signer flavour (say a hub-held key, or an externally-held key) is added with +// `.or()` and every by-value consumer that switches on `kind` gains a compile +// error for the unhandled variant. + +import { type } from "arktype"; + +/** + * A signer whose private key the hub custodies on a principal's behalf. + * + * `publicKey` is the RESOLVED, hex-encoded Ed25519 public key read from the + * hub's own principal-key store -- it is the trusted key for `principalId`. It + * MUST NOT be populated from untrusted input (e.g. a public key claimed on an + * inbound message): a verifier resolves the key from the store by `principalId` + * and checks the signature against that, never against a key from the wire. + */ +export const LocalPrincipalSigner = type({ + kind: "'local-principal'", + principalId: "string", + publicKey: "string", +}); +export type LocalPrincipalSigner = typeof LocalPrincipalSigner.infer; + +/** + * Discriminated union over how a signature's signer is identified, keyed on + * `kind`. Only the hub-custodied `local-principal` signer exists today; widen + * it here with `.or()` and every by-value consumer follows. + */ +export const SignerIdentity = LocalPrincipalSigner; +export type SignerIdentity = typeof SignerIdentity.infer; From cf6b05b0ff12b9a408d5502a1825d1895283a25a Mon Sep 17 00:00:00 2001 From: Sawyer Date: Sun, 6 Sep 2026 22:03:44 -0700 Subject: [PATCH 3/4] Adopt the per-principal signing keys in the hub Parse PRINCIPAL_KEY_ENCRYPTION_KEY in the hub config (64-hex, parsed exactly like CREDENTIAL_ENCRYPTION_KEY; ALLOW_PLAINTEXT_SECRETS is the only dev/test boot escape), and mint a real principal-key store at boot - the hub refuses to start without the key unless that escape is set. Route principal creation through @intx/db's createPrincipalStore everywhere the hub mints principals: boot's default-tenant owner membership, @corbits/chat's workbench-tenancy member creation (the store is threaded through its authz deps so the key joins the member transaction), and the invite route already covered by the vendored hub-api. Add scripts/backfill-principal-keys.ts, mirroring upstream's bin script, to seal keys onto principals that predate the feature. --- apps/hub/src/config.ts | 10 ++- apps/hub/src/default-tenant.test.ts | 89 ++++++++++++++++++++++---- apps/hub/src/default-tenant.ts | 37 +++++++---- apps/hub/src/index.ts | 55 ++++++++++++++++ package.json | 2 + packages/chat/src/workbench-tenancy.ts | 46 +++++++++---- scripts/backfill-principal-keys.ts | 63 ++++++++++++++++++ 7 files changed, 264 insertions(+), 38 deletions(-) create mode 100644 scripts/backfill-principal-keys.ts diff --git a/apps/hub/src/config.ts b/apps/hub/src/config.ts index 1d3afcf793..6afe17c414 100644 --- a/apps/hub/src/config.ts +++ b/apps/hub/src/config.ts @@ -184,8 +184,11 @@ const HubEnv = type({ "CREDENTIAL_ENCRYPTION_KEY?": type(/^[0-9a-fA-F]{64}$/).describe( "a 64-character hex-encoded 32-byte AES-256 key (openssl rand -hex 32) encrypting secrets at rest through Interchange's CredentialCipher seam — webhook-trigger signing secrets and onboarding's OAuth PKCE connect state; boot fails without it unless ALLOW_PLAINTEXT_SECRETS opts into dev/test's unencrypted fallback", ), + "PRINCIPAL_KEY_ENCRYPTION_KEY?": type(/^[0-9a-fA-F]{64}$/).describe( + "a 64-character hex-encoded 32-byte AES-256 key (openssl rand -hex 32) sealing every principal's per-principal signing key at rest through Interchange's principal-key store — deliberately separate from CREDENTIAL_ENCRYPTION_KEY so the two rotate independently; boot fails without it unless ALLOW_PLAINTEXT_SECRETS opts into dev/test's unencrypted fallback", + ), "ALLOW_PLAINTEXT_SECRETS?": type("'1' | 'true'").describe( - "dev/test-only opt-in to boot without CREDENTIAL_ENCRYPTION_KEY, storing secrets at rest unencrypted with a boot warning; refused unless BASE_URL is a loopback address, so a real deployment can never inherit it by accident", + "dev/test-only opt-in to boot without CREDENTIAL_ENCRYPTION_KEY or PRINCIPAL_KEY_ENCRYPTION_KEY, storing secrets and signing keys at rest unencrypted with a boot warning; refused unless BASE_URL is a loopback address, so a real deployment can never inherit it by accident", ), "ALLOW_UNVERIFIED_EMAILS?": type("'1' | 'true'").describe( "dev/test-only opt-in to let @workbench/access-policy trust an email that better-auth has not verified — self-signup domain checks and pending-invite redemption normally require emailVerified; never set this for a real deployment", @@ -377,6 +380,8 @@ export type HubConfig = { * tests/evals — never set this for a real deployment. */ readonly githubApiBaseUrl?: string; readonly credentialEncryptionKeyHex?: string; + /** Seals per-principal signing keys at rest; see PRINCIPAL_KEY_ENCRYPTION_KEY. */ + readonly principalKeyEncryptionKeyHex?: string; /** Dev/test-only opt-in to boot without CREDENTIAL_ENCRYPTION_KEY. */ readonly allowPlaintextSecrets: boolean; /** Dev/test-only opt-in to skip @workbench/access-policy's email- @@ -743,6 +748,9 @@ export function readHubConfig( hubConfig.githubApiBaseUrl = parsed.GITHUB_API_BASE_URL; if (parsed.CREDENTIAL_ENCRYPTION_KEY !== undefined) hubConfig.credentialEncryptionKeyHex = parsed.CREDENTIAL_ENCRYPTION_KEY; + if (parsed.PRINCIPAL_KEY_ENCRYPTION_KEY !== undefined) + hubConfig.principalKeyEncryptionKeyHex = + parsed.PRINCIPAL_KEY_ENCRYPTION_KEY; if (parsed.HUB_SIDECAR_WEBSOCKET_URL !== undefined) hubConfig.sidecarWebSocketUrl = parsed.HUB_SIDECAR_WEBSOCKET_URL; if (sidecarProvisioners.defaultProvisionerId !== undefined) diff --git a/apps/hub/src/default-tenant.test.ts b/apps/hub/src/default-tenant.test.ts index c06ef9207e..e32571f999 100644 --- a/apps/hub/src/default-tenant.test.ts +++ b/apps/hub/src/default-tenant.test.ts @@ -9,7 +9,8 @@ import { and, eq } from "drizzle-orm"; import { e2eDatabaseUrl } from "../../../scripts/e2e/database-url"; import { setupDatabase } from "../../../scripts/db-setup"; import { dbGate } from "../../../scripts/e2e/db-gate"; -import { createDB } from "@intx/db"; +import { createDB, createPrincipalKeyStore } from "@intx/db"; +import { createNoopCredentialCipher } from "@intx/crypto"; import { grant, principal, principalRole, role, tenant } from "@intx/db/schema"; import { ensureDefaultTenant, type BootAdminAuth } from "./default-tenant"; @@ -74,6 +75,12 @@ describeIfDb("ensureDefaultTenant", () => { password: decodeURIComponent(new URL(scratchUrl).password), database: scratchDatabase, }); + // The dev/test principal-key store: unencrypted at rest, matching the + // ALLOW_PLAINTEXT_SECRETS posture of every other suite in this package. + const principalKeyStore = createPrincipalKeyStore({ + db: db.db, + cipher: createNoopCredentialCipher(), + }); beforeAll(async () => { const maintenanceUrl = new URL(scratchUrl); @@ -119,7 +126,13 @@ describeIfDb("ensureDefaultTenant", () => { test("creates the root tenant when absent, with a derived name, domain, and null parent", async () => { const { auth } = fakeBootAuth(); - const id = await ensureDefaultTenant(db.db, auth, ADMIN, "acme"); + const id = await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "acme", + principalKeyStore, + ); expect(id).toMatch(/^tnt_/); const rows = await rowsFor("acme"); expect(rows).toHaveLength(1); @@ -131,7 +144,13 @@ describeIfDb("ensureDefaultTenant", () => { test("seeds the boot admin as an owner member of a freshly created root", async () => { const { auth, calls } = fakeBootAuth(); - const id = await ensureDefaultTenant(db.db, auth, ADMIN, "seeded-root"); + const id = await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "seeded-root", + principalKeyStore, + ); expect(calls.userCreated).toBe(1); expect(calls.accountsLinked).toBe(1); @@ -172,8 +191,20 @@ describeIfDb("ensureDefaultTenant", () => { test("re-runs are a no-op: the tenant, roles, grants, and membership are not duplicated", async () => { const { auth } = fakeBootAuth(); - const first = await ensureDefaultTenant(db.db, auth, ADMIN, "acme"); - const second = await ensureDefaultTenant(db.db, auth, ADMIN, "acme"); + const first = await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "acme", + principalKeyStore, + ); + const second = await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "acme", + principalKeyStore, + ); expect(second).toBe(first); expect(await rowsFor("acme")).toHaveLength(1); @@ -203,7 +234,13 @@ describeIfDb("ensureDefaultTenant", () => { }) .onConflictDoNothing(); const { auth } = fakeBootAuth(); - const id = await ensureDefaultTenant(db.db, auth, ADMIN, "bee-co"); + const id = await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "bee-co", + principalKeyStore, + ); expect(id).toBe(concurrentId); expect(await rowsFor("bee-co")).toHaveLength(1); }); @@ -218,7 +255,13 @@ describeIfDb("ensureDefaultTenant", () => { parentId: null, }); const { auth } = fakeBootAuth(); - const id = await ensureDefaultTenant(db.db, auth, ADMIN, "unowned"); + const id = await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "unowned", + principalKeyStore, + ); expect(id).toBe("tnt_unowned"); const memberships = await membershipsFor(id); @@ -254,7 +297,13 @@ describeIfDb("ensureDefaultTenant", () => { }); const { auth } = fakeBootAuth(); - const id = await ensureDefaultTenant(db.db, auth, ADMIN, "already"); + const id = await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "already", + principalKeyStore, + ); expect(id).toBe("tnt_already"); const memberships = await membershipsFor(id); @@ -292,7 +341,13 @@ describeIfDb("ensureDefaultTenant", () => { }); const { auth } = fakeBootAuth(); - const id = await ensureDefaultTenant(db.db, auth, ADMIN, "foreign"); + const id = await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "foreign", + principalKeyStore, + ); expect(id).toBe("tnt_foreign"); const memberships = await membershipsFor(id); @@ -306,7 +361,13 @@ describeIfDb("ensureDefaultTenant", () => { const { auth, calls } = fakeBootAuth({ preexistingUserWithoutCredential: true, }); - await ensureDefaultTenant(db.db, auth, ADMIN, "cred-repair"); + await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "cred-repair", + principalKeyStore, + ); expect(calls.userCreated).toBe(0); expect(calls.accountsLinked).toBe(1); @@ -330,7 +391,13 @@ describeIfDb("ensureDefaultTenant", () => { }); const { auth } = fakeBootAuth(); - const id = await ensureDefaultTenant(db.db, auth, ADMIN, "roleless"); + const id = await ensureDefaultTenant( + db.db, + auth, + ADMIN, + "roleless", + principalKeyStore, + ); expect(id).toBe("tnt_roleless"); const memberships = await membershipsFor(id); diff --git a/apps/hub/src/default-tenant.ts b/apps/hub/src/default-tenant.ts index 014c54734c..e2c2ba425b 100644 --- a/apps/hub/src/default-tenant.ts +++ b/apps/hub/src/default-tenant.ts @@ -17,7 +17,11 @@ import { and, eq } from "drizzle-orm"; import { generateId } from "@intx/hub-common"; -import type { DB } from "@intx/db"; +import { + createPrincipalStore, + type DB, + type PrincipalKeyStore, +} from "@intx/db"; import { grant, principal, principalRole, role, tenant } from "@intx/db/schema"; /** @@ -210,6 +214,7 @@ async function ensureOwnerMembership( tenantId: string, adminUserId: string, ownerRoleId: string, + principalKeyStore: PrincipalKeyStore, ): Promise { const adminMemberships = await db .select({ id: principal.id }) @@ -252,18 +257,15 @@ async function ensureOwnerMembership( if (userMemberships.length > 0) return; const now = new Date(); - await db - .insert(principal) - .values({ - id: generateId("principal"), - tenantId, - kind: "user", - refId: adminUserId, - status: "active", - createdAt: now, - updatedAt: now, - }) - .onConflictDoNothing(); + await createPrincipalStore(db, principalKeyStore).createIfAbsent({ + id: generateId("principal"), + tenantId, + kind: "user", + refId: adminUserId, + status: "active", + createdAt: now, + updatedAt: now, + }); const created = await db .select({ id: principal.id }) @@ -312,6 +314,7 @@ export async function ensureDefaultTenant( auth: BootAdminAuth, admin: { email: string; password: string }, slug: string, + principalKeyStore: PrincipalKeyStore, ): Promise { // The membership references the admin user, so the user exists first. const adminUserId = await ensureAdminUser(auth, admin); @@ -370,7 +373,13 @@ export async function ensureDefaultTenant( await ensureSystemRoleGrants(db, tenantId, roleName, roleIds[roleName]); } - await ensureOwnerMembership(db, tenantId, adminUserId, roleIds.owner); + await ensureOwnerMembership( + db, + tenantId, + adminUserId, + roleIds.owner, + principalKeyStore, + ); return tenantId; } diff --git a/apps/hub/src/index.ts b/apps/hub/src/index.ts index c77454c777..1ef79d6731 100644 --- a/apps/hub/src/index.ts +++ b/apps/hub/src/index.ts @@ -11,6 +11,8 @@ import { createApprovalStore, createDB, createGrantStore, + createPrincipalKeyStore, + createPrincipalStore, createSidecarAllocationStore, createSignalCorrelationStore, createWorkflowRunDispatchStore, @@ -510,6 +512,52 @@ export function credentialCipherFrom( ); } +/** + * Hub-boot mint of the per-principal signing-key store: seals every minted + * signing key's private seed under `PRINCIPAL_KEY_ENCRYPTION_KEY` — a key + * deliberately separate from `CREDENTIAL_ENCRYPTION_KEY` so the two rotate + * independently. An unset key hard-fails boot for the same reason the + * credential cipher does: a hub that forgets this variable must not + * silently persist private keys in the clear — unless + * `ALLOW_PLAINTEXT_SECRETS` opts into the identity no-op cipher with a + * boot warning, for dev/test only. + */ +export function principalKeyStoreFrom( + config: HubConfig, + db: ReturnType["db"], + log: ReturnType, +) { + if (config.principalKeyEncryptionKeyHex === undefined) { + if (!config.allowPlaintextSecrets) { + throw new Error( + [ + "PRINCIPAL_KEY_ENCRYPTION_KEY is not set.", + "It seals every principal's per-principal signing key at rest,", + "so the hub refuses to boot without it. Generate one and add it", + "to .env:", + "", + " openssl rand -hex 32", + "", + "For local dev/test only, set ALLOW_PLAINTEXT_SECRETS=1 instead to", + "boot with signing keys stored unencrypted; never do this for a", + "real deployment.", + ].join("\n"), + ); + } + log.warn`No PRINCIPAL_KEY_ENCRYPTION_KEY configured; per-principal signing keys will NOT be encrypted at rest. ALLOW_PLAINTEXT_SECRETS is set — expected in dev/test only, never for a real deployment.`; + return createPrincipalKeyStore({ + db, + cipher: createNoopCredentialCipher(), + }); + } + return createPrincipalKeyStore({ + db, + cipher: createEnvKeyCredentialCipher( + Buffer.from(config.principalKeyEncryptionKeyHex, "hex"), + ), + }); +} + /** * Hub-boot mint of the process-wide credential cipher: build from * config, then runtime-tag the result. Missing or wrong-shape input @@ -613,6 +661,9 @@ export async function createHub(config: HubConfig) { // Built once, tagged, and shared by every secret-at-rest seam in this // composition root — see `hubCredentialCipher`. const credentialCipher = hubCredentialCipher(config, log); + // Per-principal signing keys are sealed under their own operator key — + // see `principalKeyStoreFrom`. + const principalKeyStore = principalKeyStoreFrom(config, db, log); const auth = betterAuth({ baseURL: config.baseUrl, @@ -691,6 +742,7 @@ export async function createHub(config: HubConfig) { auth, config.envCredentialPlantAdmin, config.defaultTenantSlug, + principalKeyStore, ); // Account-keyed sign-in rate limit (CL-6494) — see `sign-in-rate-limit.ts` // for why this replaces better-auth's own IP-keyed sign-in enforcement @@ -750,6 +802,7 @@ export async function createHub(config: HubConfig) { // relies on to trigger a native multi-step deployment safely. const mailTriggeredRunGrants = createMailTriggeredRunGrantsMaterializer({ db, + principalKeyStore, grantStore: createGrantStore(db), }); // Hoisted ahead of their other uses below (`mountMemory`'s neighbors, @@ -1146,6 +1199,7 @@ export async function createHub(config: HubConfig) { workflowAllocationService, workflowDispatchService, credentialCipher, + principalKeyStore, // `@intx/hub-api`'s `GetSession` is a deliberately pluggable seam // (its own doc comment: "so a third-party identity provider can be // plugged in"), and this composition root owns it. `@corbits/chat`'s @@ -1456,6 +1510,7 @@ export async function createHub(config: HubConfig) { const chatTenancy = createDrizzleWorkbenchTenancyStore(db, { conditionRegistry: chatConditionRegistry, api: selfApi, + principalStore: createPrincipalStore(db, principalKeyStore), }); // Mounted outside the tenant prefix, like `/api/onboarding`: the bench // switcher asks this across every tenant a signed-in user belongs to, diff --git a/package.json b/package.json index 149233407a..0acdf99d1d 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "@corbits/evals": "workspace:*", "@corbits/ollama-adapter": "workspace:*", "@intx/db": "workspace:*", + "@intx/crypto": "workspace:*", + "@intx/log": "workspace:*", "@corbits/connections": "workspace:*", "@workbench/templates": "workspace:*", "@eslint/js": "^10.0.0", diff --git a/packages/chat/src/workbench-tenancy.ts b/packages/chat/src/workbench-tenancy.ts index 0c59a21a88..8e069d5e2d 100644 --- a/packages/chat/src/workbench-tenancy.ts +++ b/packages/chat/src/workbench-tenancy.ts @@ -524,7 +524,9 @@ export interface WorkbenchTenancyStore { getWorkbenchOwnerUserId(tenantId: string): Promise; } -export interface WorkbenchTenancyAuthzDeps { +export interface WorkbenchTenancyAuthzDeps< + TSchema extends Record = Record, +> { /** * Passed straight through to `evaluateGrants` for the destination * check `moveWorkbenchTenancy` runs inside its own transaction — no @@ -538,6 +540,22 @@ export interface WorkbenchTenancyAuthzDeps { readonly conditionRegistry?: ConditionRegistry; /** Self-HTTP caller used to mint native tenants and extra grants. */ readonly api: ApiCall; + /** + * `@intx/db`'s single owner of principal creation, wired by the hub + * with its principal-key store: `addWorkbenchMember` creates the + * principal through it so every new member is minted a signing key in + * the same transaction that inserts the row. Typed structurally over + * this store's own transaction handle so the member write joins the + * surrounding transaction. + */ + readonly principalStore: { + create( + row: typeof principal.$inferInsert, + tx: Parameters< + Parameters["transaction"]>[0] + >[0], + ): Promise<{ id: string }>; + }; } /** @@ -550,8 +568,9 @@ export function createDrizzleWorkbenchTenancyStore< TSchema extends Record, >( db: WorkbenchTenancyDb, - authz: WorkbenchTenancyAuthzDeps, + authz: WorkbenchTenancyAuthzDeps, ): WorkbenchTenancyStore { + const { principalStore } = authz; return { async createWorkbenchTenant(input) { const slug = slugForWorkbenchTenant(input.name); @@ -838,16 +857,19 @@ export function createDrizzleWorkbenchTenancyStore< } const now = new Date(); - const principalId = generateId("principal"); - await tx.insert(principal).values({ - id: principalId, - tenantId: link.tenantId, - kind: "user", - refId, - status: "active", - createdAt: now, - updatedAt: now, - }); + const created = await principalStore.create( + { + id: generateId("principal"), + tenantId: link.tenantId, + kind: "user", + refId, + status: "active", + createdAt: now, + updatedAt: now, + }, + tx, + ); + const principalId = created.id; await tx.insert(principalRole).values({ principalId, roleId: memberRole.id, diff --git a/scripts/backfill-principal-keys.ts b/scripts/backfill-principal-keys.ts new file mode 100644 index 0000000000..81faa19b05 --- /dev/null +++ b/scripts/backfill-principal-keys.ts @@ -0,0 +1,63 @@ +#!/usr/bin/env bun +// Backfill per-principal signing keys onto principals that predate the +// per-principal-key feature (upstream's bin/backfill-principal-keys.ts, +// adapted to workbench's DATABASE_URL plumbing). +// +// Run ONCE as the last step of the deploy that introduces per-principal +// keys, AFTER the key-minting hub is live. It is safe against a live hub: +// nothing reads principal keys yet, the hub only mints for newly-created +// principals, and the active-key unique index blocks a double key. +// Idempotent — a principal that already holds an active key is skipped — +// so it is safe to re-run or resume after a partial failure. On a database +// where every principal was created after the feature landed (e.g. local +// dev), it does nothing. +// +// set -a; . .env; set +a +// bun run scripts/backfill-principal-keys.ts +// +// It reads the same DATABASE_URL and PRINCIPAL_KEY_ENCRYPTION_KEY the hub +// uses. The key is required: the seeds must be sealed under the same cipher +// the hub decrypts with, so there is no noop fallback. + +import { + backfillPrincipalKeys, + createDB, + createPrincipalKeyStore, +} from "@intx/db"; +import { createEnvKeyCredentialCipher } from "@intx/crypto"; +import { getLogger, setup } from "@intx/log"; + +await setup({ dev: true }); +const log = getLogger(["backfill-principal-keys"]); + +const keyHex = process.env["PRINCIPAL_KEY_ENCRYPTION_KEY"]; +if (keyHex === undefined || keyHex.trim() === "") { + throw new Error( + "PRINCIPAL_KEY_ENCRYPTION_KEY environment variable is required", + ); +} +const cipher = createEnvKeyCredentialCipher(Buffer.from(keyHex, "hex")); + +const databaseUrl = process.env["DATABASE_URL"]; +if (databaseUrl === undefined || databaseUrl.trim() === "") { + throw new Error("DATABASE_URL environment variable is required"); +} +const url = new URL(databaseUrl); +const { db, close } = createDB({ + host: url.hostname, + port: url.port === "" ? 5432 : Number(url.port), + user: decodeURIComponent(url.username), + password: decodeURIComponent(url.password), + database: url.pathname.replace(/^\//, ""), +}); +try { + const store = createPrincipalKeyStore({ db, cipher }); + const report = await backfillPrincipalKeys(db, store); + log.info( + "Backfill complete: minted {keysGenerated} new signing key(s); " + + "{alreadyKeyed} principal(s) already keyed.", + report, + ); +} finally { + await close(); +} From a8113287bc9842dd9899e8f33e20c8c967cbc430 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Sun, 6 Sep 2026 22:03:53 -0700 Subject: [PATCH 4/4] Record the c1b5bb73 re-pin in the vendoring ledger Bump the db/hub-api/types ledger rows to c1b5bb73 (origin/main, 2026-09-06), add the first hub-common row, and refresh the why-not-published rationales: npm 0.3.0 predates the per-principal signing-key series and generateId's pky_ prefix. Update the touched packages' VENDORED-FROM delta lines and recompute the kill-date tree hashes; kill dates stay 2026-11-03 (already past change-date +30d). Document PRINCIPAL_KEY_ENCRYPTION_KEY in .env.example. --- .env.example | 10 +++++++- VENDORED.md | 35 ++++++++++++++-------------- scripts/checks/kill-dates.txt | 7 +++--- vendor/intx/db/VENDORED-FROM | 4 ++-- vendor/intx/hub-api/VENDORED-FROM | 2 +- vendor/intx/hub-common/VENDORED-FROM | 4 ++++ vendor/intx/types/VENDORED-FROM | 2 +- 7 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 vendor/intx/hub-common/VENDORED-FROM diff --git a/.env.example b/.env.example index 53063f75a0..47cd65c435 100644 --- a/.env.example +++ b/.env.example @@ -278,7 +278,15 @@ HUB_STATIC_DIR=../web/dist # for local dev/test. # CREDENTIAL_ENCRYPTION_KEY= -# Dev/test-only opt-out of the CREDENTIAL_ENCRYPTION_KEY requirement above: +# Seals every principal's per-principal signing key at rest, under its own +# key so it rotates independently of CREDENTIAL_ENCRYPTION_KEY. 64 hex +# characters (32 bytes, AES-256): openssl rand -hex 32. Required for any +# real deployment — the hub refuses to boot without it. Leave unset and +# instead set ALLOW_PLAINTEXT_SECRETS below for local dev/test. +# PRINCIPAL_KEY_ENCRYPTION_KEY= + +# Dev/test-only opt-out of the CREDENTIAL_ENCRYPTION_KEY and +# PRINCIPAL_KEY_ENCRYPTION_KEY requirements above: # set to boot without a key, storing those secrets unencrypted with a boot # warning. Uncommented here so a fresh checkout's `bun run dev` keeps # working out of the box. Safe to inherit as-is: the hub refuses to boot diff --git a/VENDORED.md b/VENDORED.md index d0fa3f94a7..c7ef88b6cc 100644 --- a/VENDORED.md +++ b/VENDORED.md @@ -22,23 +22,24 @@ never a convenience. ## Ledger -| Vendored path | What was copied | Upstream repo @ commit | Why not a published package | Owner | Kill date | Kill-date test | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------- | ----------------- | -| `apps/sidecar` | Derived from upstream's own `apps/sidecar`: six modules byte-identical (`default-harness.ts`, `signing-keypair.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), three near-verbatim (`atomic-write.ts` differs only by the repo-wide logger namespace), the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) substantially rewritten, plus workbench-only modules. A living fork, not a frozen copy, so this row carries no tree hash. | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | An app is never npm-published, so no publish can cover the execution host; retired by consuming an upstream-published host, or by renewing this row deliberately | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; one local delta (CL-7190): `ToolBundle.beforeToolExtension` (`tool.ts`) and its composition into `ResolvedTools.beforeToolExtensions` (`agent.ts`), so a tool package can contribute its own suspend-capable extension without the reactor or director special-casing it by name; retired by the next `@intx/agent` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0089`/`0090` behind upstream's `0088`, plus `0091` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections, plus `0092` adding `workflow_definition.schedule_claimed_minute` for the hub's native ScheduleTrigger poller (CL-4455); retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; one local delta (CL-6396): `SessionEventSink` / `publishWorkflowInferenceEvent` stamp optional `childRunId` on `agent.event`; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345) or the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer); retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478), the sealed-run terminal-status backfill (CL-6595), the CL-7190 `registerSignalCorrelation` approval-only guard (fails loud on a future `SignalKind` this RPC has no persistence for, rather than silently mis-persisting one), or the CL-6396 `childRunId` forward on `agent.event`; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); local deltas: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); and CL-7190's `message_response` resume branch in `reactor.ts`'s `resumePendingOperation`/`timeoutMessageFor`, plus its `reactor.test.ts`/`testing/fakes.ts` regression harness (this package previously had zero tests); retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); one local delta (CL-7190): `"message_response"` added to `signals.ts`'s `signalKinds`, alongside `signalKindToGateType`; another (CL-6396): optional `childRunId` on `AgentEventFrame`; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-11-03 | `check:killdates` | -| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the body-spawn authorize/mail-part-reader threading and grants head-collapse (CL-6448) -- the credential thread is retired at this pin, upstream now passing the run's live credential-material cell into every body spawn natively -- that let the fork run tool-bearing onTrigger bodies, or the CL-6396 event-channel `childRunId` stamp so overlapping turns HMAC-bind the occurrence they belong to; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | +| Vendored path | What was copied | Upstream repo @ commit | Why not a published package | Owner | Kill date | Kill-date test | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------ | ---------- | ----------------- | +| `apps/sidecar` | Derived from upstream's own `apps/sidecar`: six modules byte-identical (`default-harness.ts`, `signing-keypair.ts`, `source-asset-delivery.ts`, `workflow-closure-apply.ts`, `workflow-probe-handler.ts`, `workflow-run-pack-restore.ts`), three near-verbatim (`atomic-write.ts` differs only by the repo-wide logger namespace), the rest (`index.ts`, `config.ts`, `tool-materialization.ts`, `step-agent-tools.ts`, `workflow-host-wiring/`, `workflow-substrate-factory/`, …) substantially rewritten, plus workbench-only modules. A living fork, not a frozen copy, so this row carries no tree hash. | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | An app is never npm-published, so no publish can cover the execution host; retired by consuming an upstream-published host, or by renewing this row deliberately | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/agent` | `@intx/agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the operator-configurable doom-loop threshold (`afd0c82b`, `c421c092`) the re-vendored `workflow-host` configures; one local delta (CL-7190): `ToolBundle.beforeToolExtension` (`tool.ts`) and its composition into `ResolvedTools.beforeToolExtensions` (`agent.ts`), so a tool package can contribute its own suspend-capable extension without the reactor or director special-casing it by name; retired by the next `@intx/agent` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/db` | `@intx/db` source (`src/`, `migrations/`, drizzle config, manifest, tsconfigs) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the `wire_projection` column/loader delta (CL-6324) or the `workflow_definition.origin` column separating a definition from the per-run record of one folded run's deploy (CL-6452), shipped as migrations `0089`/`0090` behind upstream's `0088`, plus `0091` rewriting the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` in stored wire projections, plus `0092` adding `workflow_definition.schedule_claimed_minute` for the hub's native ScheduleTrigger poller (CL-4455); the pin also carries upstream's unpublished per-principal signing-key series (`principal_key` migration renumbered to `0093`, the principal/principal-key stores, and the backfill), which npm 0.3.0 lacks; retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/harness` | `@intx/harness` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the connector reply drain (`driveConnectorReplies`, `ConnectorReplyDrain`, `AgentEventStream`; `11590e66`) the sidecar's warm mail loop drives; no local delta; retired by the next `@intx/harness` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-agent` | `@intx/hub-agent` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `agentDir` path export (`927556de`) the sidecar's deploy-tree lookup uses, and its own `@intx/mail-memory`/`@intx/harness` pins must resolve the vendored copies; one local delta (CL-6396): `SessionEventSink` / `publishWorkflowInferenceEvent` stamp optional `childRunId` on `agent.event`; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-api` | `@intx/hub-api` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 covers the base package but not the null-principal `resolveApproval` for policy-resolved decisions (CL-6345), the bearer-authenticated workflow-deploy mirror (`middleware/workflow-run-deploy-auth.ts`, CL-workflow-deploy-bearer), or the upstream per-principal signing-key seam this pin re-vendors (`principalKeyStore` threaded through the tenant/invite/run/workflow routes, the grant materializer, and `createApp`); retired when upstream absorbs the deltas or publishes the signing-key series | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-sessions` | `@intx/hub-sessions` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the pack-acceptance fixes (`ownsWorkflowRunRepo`, `anchorAddressForPackSource`, `decideTerminalRunFlip`), the wire-projection writer (CL-6324), malformed tool-call-name sanitization (CL-6478), the sealed-run terminal-status backfill (CL-6595), the CL-7190 `registerSignalCorrelation` approval-only guard (fails loud on a future `SignalKind` this RPC has no persistence for, rather than silently mis-persisting one), or the CL-6396 `childRunId` forward on `agent.event`; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/inference` | `@intx/inference` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates doom-loop detection (`8da4c827`, `afd0c82b`, `c421c092`); local deltas: `providers/google-genai-files.ts` builds its upload body as `new Uint8Array(bytes)` because TS 6's lib.dom `BodyInit` rejects `Uint8Array` (upstream compiles ESNext-only under TS 5.9); and CL-7190's `message_response` resume branch in `reactor.ts`'s `resumePendingOperation`/`timeoutMessageFor`, plus its `reactor.test.ts`/`testing/fakes.ts` regression harness (this package previously had zero tests); retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mail-memory` | `@intx/mail-memory` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the `@intx/mailbox` extraction (`af03bb90`), on-demand body reads (`54f7c239`) and `expunge` returning the swept uids (`bcabb1f8`) that the re-vendored `workflow-host` binds against; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mailbox` | `@intx/mailbox` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | Never published: a new package at the target pin (`af03bb90`) that `workflow-host`'s substrate mailbox store and supervisor-backed transport import; no local delta; retired by its first publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/mime` | `@intx/mime` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 predates the non-RFC message-id guard `isMessageId` (`d97e1832`), the full `References` chain (`65c6fe70`) and the lossless `decodeMail` decoder (`3b6d06b2`) that `mailbox`/`mail-memory` at the same pin import; no local delta; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/types` | `@intx/types` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates the type surface the re-vendored trees compile against: `expunge` returning `expungedUids` (`bcabb1f8`), plain-string `PackRejectReason` (`7b42f405`), the run authorization/approvals REST types (`71ad6c08`), the decoded-mail `Mail`/`MailPartReader` model (`3b6d06b2`) and the `interchange.actions`/`loops` package-json refs (`3bd5b837`, `1ea2f39b`); one local delta (CL-7190): `"message_response"` added to `signals.ts`'s `signalKinds`, alongside `signalKindToGateType`; another (CL-6396): optional `childRunId` on `AgentEventFrame`; the pin also carries the unpublished `signer-identity` contract and `principalKeyAad` domain separator from the signing-key series; retired by the next publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/hub-common` | `@intx/hub-common` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `c1b5bb73` (origin/main, 2026-09-06) | npm 0.3.0 predates `generateId`'s `pky_` principal-key prefix from the signing-key series, which the re-vendored `db`/`hub-api` import; no local delta; retired by the next `@intx/hub-common` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow` | `@intx/workflow` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates the `onBodyFailure: "tolerate"` section policy (`b977ade6`) that `@corbits/agent-runtime` authors and the action/loop primitives (`3bd5b837`, `1ea2f39b`) the re-vendored `workflow-host` runs; retired by the next `@intx/workflow` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow-deploy` | `@intx/workflow-deploy` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | No local delta: npm 0.3.0 predates `inertLoopBody` and the loop-body source pin (`1ea2f39b`) that the re-vendored `hub-sessions` imports; retired by the next `@intx/workflow-deploy` publish | sawyer | 2026-11-03 | `check:killdates` | +| `vendor/intx/workflow-host` | `@intx/workflow-host` source (`src/`, manifest, tsconfig) | [faremeter/interchange](https://github.com/faremeter/interchange) @ `692c3106` (origin/main, 2026-09-03) | npm 0.3.0 covers the base package but not the body-spawn authorize/mail-part-reader threading and grants head-collapse (CL-6448) -- the credential thread is retired at this pin, upstream now passing the run's live credential-material cell into every body spawn natively -- that let the fork run tool-bearing onTrigger bodies, or the CL-6396 event-channel `childRunId` stamp so overlapping turns HMAC-bind the occurrence they belong to; retired when upstream absorbs the deltas | sawyer | 2026-11-03 | `check:killdates` | The pin is `692c3106` (upstream `origin/main`, 2026-09-03), 90 commits past the `v0.3.0` release tag `b5580a02`. npm is still `0.3.0`, so every tree a diff --git a/scripts/checks/kill-dates.txt b/scripts/checks/kill-dates.txt index 8e7724d9c4..313cf3277d 100644 --- a/scripts/checks/kill-dates.txt +++ b/scripts/checks/kill-dates.txt @@ -15,16 +15,17 @@ # package's VENDORED-FROM delta line in the same change. apps/sidecar | sawyer | 2026-11-03 vendor/intx/agent | sawyer | 2026-11-03 | 9dd132fda8d7aef9d70395de2d289abeb0361416b18a5079f219372df69d81a0 -vendor/intx/db | sawyer | 2026-11-03 | 69be69d849e1166c20412aafa02f254975ac9d1597c486f2bdd66044cec13855 +vendor/intx/db | sawyer | 2026-11-03 | e09d9ce7ade4dd28a1ef60be86578d94194864b318420e93a3b3845e7c5d296b vendor/intx/harness | sawyer | 2026-11-03 | 399b4e8b3b18e8969e815427625e1bfc6746f31e791fb92cdbc2aaf2c5080dbf vendor/intx/hub-agent | sawyer | 2026-11-03 | 0a24a0cf85cc0483dbeee815d89847440e2fbd1242fdfd992941379a9acc2a4a -vendor/intx/hub-api | sawyer | 2026-11-03 | 45725f12dd23344ce58f687a41725fb989fa00f6d0dc8e0a0f68cb93663d3478 +vendor/intx/hub-api | sawyer | 2026-11-03 | 64438be924e874555dc8742d0cbeef167af42e7d79eae512e2d212e12e4ca29b +vendor/intx/hub-common | sawyer | 2026-11-03 | 2fe74684593083f422023b2d6b337e414b5b319c6bfe02c7fa157130d7162e41 vendor/intx/hub-sessions | sawyer | 2026-11-03 | d31e093ef24edcd13ba410ad5a3fa4ba30ff8d7086bb692a8405000b1b0031e0 vendor/intx/inference | sawyer | 2026-11-03 | 81e1b31947cd8e6cac4e336d21d8a87ba063b638e0ac4ada1b0f375ec6861bf5 vendor/intx/mail-memory | sawyer | 2026-11-03 | e281498352adbc2face631744ce9a02cb838c60a6c318961022904f0fd05d192 vendor/intx/mailbox | sawyer | 2026-11-03 | 926bfef1b60a4ba7e600252e9ad6e37c17d387a200f79c2cbc0b3ceb612000ea vendor/intx/mime | sawyer | 2026-11-03 | ddf7e626172350c6a447890affc6c746b7938e04ee53af8f1d9c8cffd97d1954 -vendor/intx/types | sawyer | 2026-11-03 | 9434c1de5545459e775ce0c0f2efb68687dd814c30429f2b05b30bf5828f28f2 +vendor/intx/types | sawyer | 2026-11-03 | 71bd6493ae6df31abd9be8ff3d2feb79260d2aae3a29a5e275945dc92ea16a12 vendor/intx/workflow | sawyer | 2026-11-03 | c8f3a7a40d4ef429b58ba913c2faa27a5e7e98baf433dc57f864e12c907eb47a vendor/intx/workflow-deploy | sawyer | 2026-11-03 | 72a4f931ba5c404a091dc46a7ed60f5b0530a63535f7cacacfe31f95c397e473 vendor/intx/workflow-host | sawyer | 2026-11-03 | b9964010920cef377ba4be777c179d414c430c1709e665bbf8c5e4a413e249e9 diff --git a/vendor/intx/db/VENDORED-FROM b/vendor/intx/db/VENDORED-FROM index 4cf95ccb36..0690ec03b8 100644 --- a/vendor/intx/db/VENDORED-FROM +++ b/vendor/intx/db/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/db) -Commit: 692c31068cbb636ebb19c6d179ec6fe126a2d4e6 (origin/main, 2026-09-03) +Commit: c1b5bb73ff4815414af44e4662214e054e9e70e2 (origin/main, 2026-09-06) License: LGPL-2.1-only (see vendor/intx/LICENSE) -Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. Upstream test files are not vendored (they import the unvendored @intx/inference-testing and @intx/test-harness test packages); only the tests covering a workbench delta are kept. CL-6324: workflow_definition_version gains a wire_projection jsonb column (migration 0089_workflow_definition_version_wire_projection.sql -- renumbered from 0086 as upstream took 0086/0087/0088; the journal keeps our original `when` values, which still sort after upstream's), schema/workflow-definitions.ts declares it, and workflow-definition-store.ts adds loadFrozenWireProjection (exported from src/index.ts) to read it back beside the approved wire hash. CL-6452: workflow_definition gains an origin column (migration 0090_workflow_definition_origin.sql; declared in schema/workflow-definitions.ts) marking the per-run record a folded run's deploy mints for itself, so launch and refresh resolve the hub-authored definition instead of the newest same-named row. Migration 0091_workflow_definition_version_tolerate_body_failure.sql rewrites the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` inside stored wire projections. CL-4455: workflow_definition gains schedule_claimed_minute (migration 0092_workflow_definition_schedule_claimed_minute.sql; declared in schema/workflow-definitions.ts) so the hub's native ScheduleTrigger poller can CAS each UTC minute. Workbench applies these files by sorted filename through scripts/db-setup.ts (replay-from-scratch with a filename ledger); the drizzle journal exists for drizzle-kit only. +Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. Upstream test files are not vendored (they import the unvendored @intx/inference-testing and @intx/test-harness test packages); only the tests covering a workbench delta are kept. CL-6324: workflow_definition_version gains a wire_projection jsonb column (migration 0089_workflow_definition_version_wire_projection.sql -- renumbered from 0086 as upstream took 0086/0087/0088; the journal keeps our original `when` values, which still sort after upstream's), schema/workflow-definitions.ts declares it, and workflow-definition-store.ts adds loadFrozenWireProjection (exported from src/index.ts) to read it back beside the approved wire hash. CL-6452: workflow_definition gains an origin column (migration 0090_workflow_definition_origin.sql; declared in schema/workflow-definitions.ts) marking the per-run record a folded run's deploy mints for itself, so launch and refresh resolve the hub-authored definition instead of the newest same-named row. Migration 0091_workflow_definition_version_tolerate_body_failure.sql rewrites the retired `onBodyFailure: "continue"` literal to upstream's `"tolerate"` inside stored wire projections. CL-4455: workflow_definition gains schedule_claimed_minute (migration 0092_workflow_definition_schedule_claimed_minute.sql; declared in schema/workflow-definitions.ts) so the hub's native ScheduleTrigger poller can CAS each UTC minute. Workbench applies these files by sorted filename through scripts/db-setup.ts (replay-from-scratch with a filename ledger); the drizzle journal exists for drizzle-kit only. CL-7495 re-pin to c1b5bb73: upstream's 0089_tense_selene.sql (principal_key table) is renumbered to 0093_principal_keys.sql behind our four delta migrations, with journal idx 93 and a 0093_snapshot.json built from upstream's 0089 snapshot reconciled with the wire_projection/origin/schedule_claimed_minute columns; upstream 447ebb08's snapshot-chain repair (relinked 0087 prevId, workflow_run.credential_refs restored) is applied to 0087/0088. Our fork migrations 0089-0092 ship no drizzle snapshots (hand-written SQL, journal only), so there was no fork snapshot chain to repair. diff --git a/vendor/intx/hub-api/VENDORED-FROM b/vendor/intx/hub-api/VENDORED-FROM index d1e03661a8..2f66e0ef8a 100644 --- a/vendor/intx/hub-api/VENDORED-FROM +++ b/vendor/intx/hub-api/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/hub-api) -Commit: 692c31068cbb636ebb19c6d179ec6fe126a2d4e6 (origin/main, 2026-09-03) +Commit: c1b5bb73ff4815414af44e4662214e054e9e70e2 (origin/main, 2026-09-06) License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. Upstream test files are not vendored (they import the unvendored @intx/inference-testing and @intx/test-harness test packages); only the tests covering a workbench delta are kept. CL-6345: resolveApproval's ResolveApprovalRequest accepts principalId: null for policy-resolved (grant-allowance) decisions, which skip the per-principal approval:/resolve gate the HTTP routes still enforce; readDurableWorkflowRunLifecycles is re-exported from the barrel for the hub's grant-allowance gate. CL-workflow-deploy-bearer: middleware/workflow-run-deploy-auth.ts (+ test) is a bearer-authenticated mirror onto the session-cookie POST/GET .../workflows/deployments route; MountHubRoutesDeps and CreateAppOpts gain an optional workflowRunAuthenticator that mounts it ahead of resolveTenant, and src/index.ts exports the middleware and its types. diff --git a/vendor/intx/hub-common/VENDORED-FROM b/vendor/intx/hub-common/VENDORED-FROM new file mode 100644 index 0000000000..b8b7b90bc1 --- /dev/null +++ b/vendor/intx/hub-common/VENDORED-FROM @@ -0,0 +1,4 @@ +Source: https://github.com/faremeter/interchange (packages/hub-common) +Commit: c1b5bb73ff4815414af44e4662214e054e9e70e2 (origin/main, 2026-09-06) +License: LGPL-2.1-only (see vendor/intx/LICENSE) +Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. First vendored in CL-7495: the hub's signing-key series needs generateId's pky_ principal-key prefix (npm 0.3.0 predates it), and @intx/db's principal-key store depends on this package. Only src/index.ts, ids.ts, action-mapping.ts, and glob.ts are consumed; the package depends on @intx/types via workspace: so the vendored types source joins the composite project-reference graph. diff --git a/vendor/intx/types/VENDORED-FROM b/vendor/intx/types/VENDORED-FROM index 7a473a387f..43124c5319 100644 --- a/vendor/intx/types/VENDORED-FROM +++ b/vendor/intx/types/VENDORED-FROM @@ -1,4 +1,4 @@ Source: https://github.com/faremeter/interchange (packages/types) -Commit: 692c31068cbb636ebb19c6d179ec6fe126a2d4e6 (origin/main, 2026-09-03) +Commit: c1b5bb73ff4815414af44e4662214e054e9e70e2 (origin/main, 2026-09-06) License: LGPL-2.1-only (see vendor/intx/LICENSE) Local modifications: exports map repointed from the upstream intx-src condition to direct TypeScript source resolution (types/default -> ./src/...); dist references removed. Upstream test files are not vendored (they import the unvendored @intx/inference-testing and @intx/test-harness test packages); only the tests covering a workbench delta are kept. CL-7443 retires CL-7190's local delta ("message_response" added to signals.ts's signalKinds, alongside signalKindToGateType): `ask_user` posts its question and ends the turn instead of parking a signal, so signals.ts is back to upstream's approval-only shape. CL-6396: AgentEventFrame gains an optional childRunId so a sidecar can name the occurrence (turn__) the event belongs to.