@creatornader/agent-bridge 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -1
- package/README.md +3 -0
- package/SECURITY.md +7 -0
- package/dist/gateway-main.js +66 -2
- package/dist/gateway-main.js.map +1 -1
- package/docs/deployment.md +17 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.5.1] - 2026-07-20
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Let a production gateway receive its PostgreSQL CA bundle through the separate
|
|
14
|
+
`AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64` secret. The gateway decodes the bundle in
|
|
15
|
+
memory, forces certificate and hostname verification, and removes file-based TLS
|
|
16
|
+
CA parameters from the runtime URL. It rejects the ambiguous `ssl` parameter and
|
|
17
|
+
client-certificate file parameters instead of discarding them. The Fly preflight now
|
|
18
|
+
requires both runtime secrets.
|
|
19
|
+
|
|
9
20
|
## [0.5.0] - 2026-07-20
|
|
10
21
|
|
|
11
22
|
### Added
|
|
@@ -355,7 +366,8 @@ First tagged release. Marks the point where agent-bridge has shipped its initial
|
|
|
355
366
|
- Narrative-leak detection in CI + on commit via `creatornader/textleaks@v0.2.0` (renamed from leakguard).
|
|
356
367
|
|
|
357
368
|
[0.1.0]: https://github.com/creatornader/agent-bridge/releases/tag/v0.1.0
|
|
358
|
-
[Unreleased]: https://github.com/creatornader/agent-bridge/compare/v0.5.
|
|
369
|
+
[Unreleased]: https://github.com/creatornader/agent-bridge/compare/v0.5.1...HEAD
|
|
370
|
+
[0.5.1]: https://github.com/creatornader/agent-bridge/compare/v0.5.0...v0.5.1
|
|
359
371
|
[0.5.0]: https://github.com/creatornader/agent-bridge/compare/v0.4.0...v0.5.0
|
|
360
372
|
[0.4.0]: https://github.com/creatornader/agent-bridge/compare/v0.3.1...v0.4.0
|
|
361
373
|
[0.3.1]: https://github.com/creatornader/agent-bridge/compare/v0.3.0...v0.3.1
|
package/README.md
CHANGED
|
@@ -97,6 +97,9 @@ After an operator supplies an existing app, `npm run preflight:fly -- --app <app
|
|
|
97
97
|
The command does not deploy, migrate, scale, restart, or print environment-variable
|
|
98
98
|
values. The [Fly production procedure](docs/deployment.md#flyio-reference-hosting)
|
|
99
99
|
keeps database migration and runtime-role bootstrap outside the gateway process.
|
|
100
|
+
The Fly gateway expects two secrets: the restricted runtime database URL and a
|
|
101
|
+
base64-encoded PostgreSQL CA bundle. It decodes the CA in memory and verifies both the
|
|
102
|
+
certificate chain and database hostname. It never needs a CA file inside the image.
|
|
100
103
|
|
|
101
104
|
The manual `gateway production proof` workflow tests an existing gateway after its
|
|
102
105
|
deployment gate has passed. GitHub must run it from `main` in the protected
|
package/SECURITY.md
CHANGED
|
@@ -74,6 +74,13 @@ Keep the schema-owner, runtime, operator, archive, backup, and restore database
|
|
|
74
74
|
authorities separate. Do not copy one client's token or backend file into another
|
|
75
75
|
client's configuration.
|
|
76
76
|
|
|
77
|
+
When a gateway receives `AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64`, it decodes the PEM
|
|
78
|
+
bundle in memory and forces PostgreSQL certificate and hostname verification. The
|
|
79
|
+
gateway removes `sslmode` and `sslrootcert` before it opens the pool. It rejects URL
|
|
80
|
+
parameters for `ssl`, a client certificate, client key, or key password. Keep the
|
|
81
|
+
restricted database URL in a separate secret. Do not put either value in the image,
|
|
82
|
+
source tree, command arguments, or deployment logs.
|
|
83
|
+
|
|
77
84
|
Managed PostgreSQL may expose a schema-owner administrator with `CREATEROLE` instead
|
|
78
85
|
of true superuser authority. That capability is sufficient for migrations. Native backup
|
|
79
86
|
requires the schema owner to be a true superuser or hold `BYPASSRLS`, because the source
|
package/dist/gateway-main.js
CHANGED
|
@@ -224,6 +224,66 @@ var PostgresRequestAuthority = class {
|
|
|
224
224
|
}
|
|
225
225
|
};
|
|
226
226
|
|
|
227
|
+
// src/postgres-runtime-connection.ts
|
|
228
|
+
var MAX_CA_PEM_BYTES = 256 * 1024;
|
|
229
|
+
var BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u;
|
|
230
|
+
var CERTIFICATE_PATTERN = /-----BEGIN CERTIFICATE-----[\s\S]+-----END CERTIFICATE-----/u;
|
|
231
|
+
var CLIENT_CERTIFICATE_PARAMETERS = [
|
|
232
|
+
"sslcert",
|
|
233
|
+
"sslkey",
|
|
234
|
+
"sslpassword"
|
|
235
|
+
];
|
|
236
|
+
function decodeCaPem(encoded) {
|
|
237
|
+
const value = encoded.trim();
|
|
238
|
+
if (!value) throw new Error("AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 cannot be empty");
|
|
239
|
+
if (!BASE64_PATTERN.test(value)) {
|
|
240
|
+
throw new Error("AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 must be canonical base64");
|
|
241
|
+
}
|
|
242
|
+
const bytes = Buffer.from(value, "base64");
|
|
243
|
+
if (bytes.toString("base64") !== value) {
|
|
244
|
+
throw new Error("AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 must be canonical base64");
|
|
245
|
+
}
|
|
246
|
+
if (bytes.byteLength > MAX_CA_PEM_BYTES) {
|
|
247
|
+
throw new Error("AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 exceeds 256 KiB after decoding");
|
|
248
|
+
}
|
|
249
|
+
const pem = bytes.toString("utf8").trim();
|
|
250
|
+
if (pem.includes("\0") || !CERTIFICATE_PATTERN.test(pem)) {
|
|
251
|
+
throw new Error("AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 must decode to a PEM certificate bundle");
|
|
252
|
+
}
|
|
253
|
+
return pem;
|
|
254
|
+
}
|
|
255
|
+
function runtimePostgresConnectionConfig(connectionString, caBase64) {
|
|
256
|
+
if (caBase64 === void 0) return { connectionString };
|
|
257
|
+
let parsed;
|
|
258
|
+
try {
|
|
259
|
+
parsed = new URL(connectionString);
|
|
260
|
+
} catch {
|
|
261
|
+
throw new Error("AGENT_BRIDGE_RUNTIME_DATABASE_URL must be a valid PostgreSQL URL");
|
|
262
|
+
}
|
|
263
|
+
if (parsed.protocol !== "postgresql:" && parsed.protocol !== "postgres:") {
|
|
264
|
+
throw new Error("AGENT_BRIDGE_RUNTIME_DATABASE_URL must use postgresql:// or postgres://");
|
|
265
|
+
}
|
|
266
|
+
if (CLIENT_CERTIFICATE_PARAMETERS.some((parameter) => parsed.searchParams.has(parameter))) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
"AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 cannot be combined with client certificate URL parameters"
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
if (parsed.searchParams.has("ssl")) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
"AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 cannot be combined with the ssl URL parameter"
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
parsed.searchParams.delete("sslmode");
|
|
277
|
+
parsed.searchParams.delete("sslrootcert");
|
|
278
|
+
return {
|
|
279
|
+
connectionString: parsed.toString(),
|
|
280
|
+
ssl: {
|
|
281
|
+
ca: decodeCaPem(caBase64),
|
|
282
|
+
rejectUnauthorized: true
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
227
287
|
// src/gateway-main.ts
|
|
228
288
|
function integer(value, fallback, name) {
|
|
229
289
|
if (value === void 0) return fallback;
|
|
@@ -271,8 +331,12 @@ async function main() {
|
|
|
271
331
|
const port = integer(process.env.AGENT_BRIDGE_PORT, 8787, "gateway port");
|
|
272
332
|
const allowedOrigins = process.env.AGENT_BRIDGE_ALLOWED_ORIGINS?.split(",").map((origin) => origin.trim()).filter(Boolean);
|
|
273
333
|
const migrationPlan = await loadMigrationPlan(migrationsDirectory);
|
|
334
|
+
const runtimeConnection = runtimePostgresConnectionConfig(
|
|
335
|
+
databaseUrl,
|
|
336
|
+
process.env.AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64
|
|
337
|
+
);
|
|
274
338
|
const pool = new pg.Pool({
|
|
275
|
-
|
|
339
|
+
...runtimeConnection,
|
|
276
340
|
max: poolSize,
|
|
277
341
|
connectionTimeoutMillis: databaseTimeout,
|
|
278
342
|
query_timeout: databaseTimeout,
|
|
@@ -280,7 +344,7 @@ async function main() {
|
|
|
280
344
|
application_name: "agent-bridge-gateway"
|
|
281
345
|
});
|
|
282
346
|
const readinessPool = new pg.Pool({
|
|
283
|
-
|
|
347
|
+
...runtimeConnection,
|
|
284
348
|
max: 1,
|
|
285
349
|
connectionTimeoutMillis: databaseTimeout,
|
|
286
350
|
query_timeout: databaseTimeout,
|
package/dist/gateway-main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/gateway-main.ts","../src/gateway-security.ts","../src/postgres-request-authority.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport pg from \"pg\";\nimport { PostgresCredentialResolver } from \"./gateway-auth.js\";\nimport { PostgresGatewaySecurity } from \"./gateway-security.js\";\nimport { createGateway } from \"./gateway.js\";\nimport {\n loadMigrationPlan,\n migrationsReady,\n runMigrations,\n runtimeSchemaReady,\n} from \"./migrations.js\";\nimport { PostgresBridgeStore } from \"./postgres-bridge-store.js\";\nimport { PostgresRequestAuthority } from \"./postgres-request-authority.js\";\n\nfunction integer(value: string | undefined, fallback: number, name: string): number {\n if (value === undefined) return fallback;\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 1) throw new Error(`${name} must be a positive integer`);\n return parsed;\n}\n\nasync function main(): Promise<void> {\n const migrationsDirectory = fileURLToPath(new URL(\"../sql/migrations\", import.meta.url));\n if (process.argv.includes(\"--migrate-only\")) {\n const databaseUrl = process.env.AGENT_BRIDGE_DATABASE_URL;\n if (!databaseUrl) throw new Error(\"AGENT_BRIDGE_DATABASE_URL is required for migrations\");\n const migrationPool = new pg.Pool({\n connectionString: databaseUrl,\n max: 1,\n application_name: \"agent-bridge-migrator\",\n });\n try {\n await runMigrations(migrationPool, migrationsDirectory);\n } finally {\n await migrationPool.end();\n }\n return;\n }\n if (process.env.AGENT_BRIDGE_MIGRATE === \"1\") {\n throw new Error(\"AGENT_BRIDGE_MIGRATE is not supported; run agent-bridge migrate separately\");\n }\n const databaseUrl = process.env.AGENT_BRIDGE_RUNTIME_DATABASE_URL;\n if (!databaseUrl) {\n throw new Error(\"AGENT_BRIDGE_RUNTIME_DATABASE_URL is required for the gateway\");\n }\n\n const deadline = integer(process.env.AGENT_BRIDGE_REQUEST_DEADLINE_MS, 10_000, \"request deadline\");\n const databaseTimeout = integer(\n process.env.AGENT_BRIDGE_DATABASE_TIMEOUT_MS,\n Math.max(1, Math.floor(deadline / 3)),\n \"database timeout\",\n );\n const poolSize = integer(process.env.AGENT_BRIDGE_DATABASE_POOL_SIZE, 10, \"database pool size\");\n const bodyLimitBytes = integer(\n process.env.AGENT_BRIDGE_BODY_LIMIT_BYTES,\n 128 * 1024,\n \"body limit\",\n );\n const host = process.env.AGENT_BRIDGE_HOST ?? \"127.0.0.1\";\n const port = integer(process.env.AGENT_BRIDGE_PORT, 8787, \"gateway port\");\n const allowedOrigins = process.env.AGENT_BRIDGE_ALLOWED_ORIGINS\n ?.split(\",\")\n .map((origin) => origin.trim())\n .filter(Boolean);\n const migrationPlan = await loadMigrationPlan(migrationsDirectory);\n const pool = new pg.Pool({\n connectionString: databaseUrl,\n max: poolSize,\n connectionTimeoutMillis: databaseTimeout,\n query_timeout: databaseTimeout,\n statement_timeout: databaseTimeout,\n application_name: \"agent-bridge-gateway\",\n });\n const readinessPool = new pg.Pool({\n connectionString: databaseUrl,\n max: 1,\n connectionTimeoutMillis: databaseTimeout,\n query_timeout: databaseTimeout,\n statement_timeout: databaseTimeout,\n application_name: \"agent-bridge-gateway-readiness\",\n });\n\n const store = new PostgresBridgeStore(pool);\n let readinessCheckedAt = 0;\n let readinessValue = false;\n let readinessInFlight: Promise<boolean> | undefined;\n const probeSchema = async () =>\n await migrationsReady(readinessPool, migrationPlan) && await runtimeSchemaReady(readinessPool);\n const schemaReady = (force = false): Promise<boolean> => {\n if (!force && Date.now() - readinessCheckedAt < 1_000) {\n return Promise.resolve(readinessValue);\n }\n if (readinessInFlight) return readinessInFlight;\n let current: Promise<boolean>;\n current = probeSchema()\n .then((ready) => {\n readinessValue = ready;\n readinessCheckedAt = Date.now();\n return ready;\n })\n .finally(() => {\n if (readinessInFlight === current) readinessInFlight = undefined;\n });\n readinessInFlight = current;\n return current;\n };\n try {\n if (!await schemaReady(true)) {\n throw new Error(\"runtime database is not ready for enforced row isolation\");\n }\n } catch (error) {\n await Promise.allSettled([pool.end(), readinessPool.end()]);\n throw error;\n }\n const gateway = createGateway({\n store,\n credentials: new PostgresCredentialResolver(pool),\n security: new PostgresGatewaySecurity(pool),\n requestAuthority: new PostgresRequestAuthority(pool),\n allowedOrigins,\n bodyLimitBytes,\n requestDeadlineMs: deadline,\n ready: schemaReady,\n rowIsolationReady: schemaReady,\n });\n\n gateway.listen(port, host, () => {\n process.stderr.write(`Agent Bridge gateway listening on ${host}:${port}\\n`);\n });\n\n let stopping = false;\n async function shutdown(): Promise<void> {\n if (stopping) return;\n stopping = true;\n await new Promise<void>((resolve, reject) => {\n gateway.close((error) => error ? reject(error) : resolve());\n });\n await Promise.all([pool.end(), readinessPool.end()]);\n }\n\n process.once(\"SIGINT\", () => void shutdown());\n process.once(\"SIGTERM\", () => void shutdown());\n}\n\nvoid main().catch((error) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n});\n","import type { PgQueryable } from \"./postgres-bridge-store.js\";\nimport type { OperationId } from \"./contracts/registry.js\";\n\nexport interface RateLimitDecision {\n allowed: boolean;\n policyId: string | null;\n limit: number;\n remaining: number;\n retryAfterSeconds: number;\n}\n\nexport interface GatewaySecurity {\n recordScopeDenial(\n credentialId: string,\n operationId: OperationId,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<void>;\n consume(\n credentialId: string,\n operationId: OperationId,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<RateLimitDecision>;\n}\n\nexport class PostgresGatewaySecurity implements GatewaySecurity {\n constructor(private readonly db: PgQueryable) {}\n\n private functionName(bound: string, unbound: string): string {\n return this.db.inTransaction ? bound : unbound;\n }\n\n async recordScopeDenial(\n credentialId: string,\n operationId: OperationId,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<void> {\n if (signal?.aborted) throw signal.reason;\n await this.db.query(\n `SELECT agent_bridge.${this.functionName(\"record_scope_denial\", \"record_scope_denial_unbound_011\")}($1::uuid, $2::text, $3::uuid)`,\n [credentialId, operationId, requestId],\n );\n if (signal?.aborted) throw signal.reason;\n }\n\n async consume(\n credentialId: string,\n operationId: OperationId,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<RateLimitDecision> {\n if (signal?.aborted) throw signal.reason;\n const result = await this.db.query<{\n allowed: boolean;\n limit_value: string | number;\n remaining_value: string | number;\n retry_after_seconds: string | number;\n denied_policy_id: string | null;\n }>(\n `SELECT allowed, limit_value, remaining_value, retry_after_seconds, denied_policy_id\n FROM agent_bridge.${this.functionName(\"consume_rate_limit\", \"consume_rate_limit_unbound_011\")}($1::uuid, $2::text, $3::uuid)`,\n [credentialId, operationId, requestId],\n );\n if (signal?.aborted) throw signal.reason;\n const row = result.rows[0];\n if (!row) throw new Error(\"rate limit policy is missing\");\n return {\n allowed: row.allowed,\n policyId: row.denied_policy_id,\n limit: Math.max(0, Number(row.limit_value)),\n remaining: Math.max(0, Number(row.remaining_value)),\n retryAfterSeconds: Math.max(0, Number(row.retry_after_seconds)),\n };\n }\n}\n","import type pg from \"pg\";\nimport type { BridgePrincipal } from \"./bridge-domain.js\";\nimport type { AuthorizationScope } from \"./contracts/registry.js\";\nimport { PostgresBridgeStore, type PgQueryable } from \"./postgres-bridge-store.js\";\nimport { PostgresGatewaySecurity } from \"./gateway-security.js\";\n\nexport interface RequestAuthorityContext {\n gatewayAuthorityId: string;\n credential: { id: string; principal: BridgePrincipal; scopes: readonly AuthorizationScope[] };\n store: PostgresBridgeStore;\n security: PostgresGatewaySecurity;\n /** Establishes the savepoint separating durable security accounting from domain work. */\n beginDomainWork(): Promise<void>;\n endpointMigrationChallenges?: {\n issue(input: { challenge: string; expectedGatewayAuthorityId: string; verifierCredentialId: string }): Promise<{ gatewayAuthorityId: string; issuerCredentialId: string; verifierCredentialId: string; expiresAt: string }>;\n consume(input: { challenge: string; expectedGatewayAuthorityId: string; issuerCredentialId: string }): Promise<{ gatewayAuthorityId: string; issuerCredentialId: string; verifierCredentialId: string; expiresAt: string | null; consumed: boolean }>;\n };\n}\n\nexport interface RequestAuthority {\n run<T>(credentialId: string, credentialHash: string, requestId: string, signal: AbortSignal, work: (context: RequestAuthorityContext) => Promise<T>): Promise<T>;\n}\n\ntype AuthorityRow = {\n gateway_authority_id: string;\n credential_id: string;\n workspace_id: string;\n principal: string;\n scopes: AuthorizationScope[];\n};\n\nfunction timestamp(value: unknown): string {\n if (value instanceof Date) return value.toISOString();\n return String(value);\n}\n\nfunction endpointMigrationFailure(error: unknown): never {\n const sqlstate = typeof error === \"object\" && error !== null && \"code\" in error\n ? String((error as { code?: unknown }).code ?? \"\")\n : \"\";\n if (sqlstate === \"28000\") {\n throw Object.assign(new Error(\"endpoint migration binding was rejected\"), {\n status: 409,\n code: \"endpoint_migration_binding_rejected\",\n });\n }\n if (sqlstate === \"23505\") {\n throw Object.assign(new Error(\"endpoint migration challenge conflicts with existing state\"), {\n status: 409,\n code: \"endpoint_migration_challenge_conflict\",\n });\n }\n throw error;\n}\n\nexport class MutationOutcomeUnknownError extends Error {\n readonly status = 503;\n readonly code = \"mutation_outcome_unknown\";\n constructor() { super(\"The database commit outcome is unknown\"); }\n}\n\nexport class PostgresRequestAuthority implements RequestAuthority {\n constructor(private readonly pool: Pick<pg.Pool, \"connect\">) {}\n\n async run<T>(credentialId: string, credentialHash: string, requestId: string, signal: AbortSignal, work: (context: RequestAuthorityContext) => Promise<T>): Promise<T> {\n if (signal.aborted) throw signal.reason;\n const client = await this.pool.connect();\n const db: PgQueryable = { inTransaction: true, query: (sql, values) => client.query(sql, values) };\n let transactionOpen = false;\n let domainSavepoint = false;\n let commitDispatched = false;\n let commitCompleted = false;\n let discard: Error | undefined;\n try {\n if (signal.aborted) throw signal.reason;\n await client.query(\"BEGIN\");\n transactionOpen = true;\n const opened = await client.query<AuthorityRow>(\n `SELECT gateway_authority_id,credential_id,workspace_id,principal,scopes\n FROM agent_bridge.open_request_authority_bound($1::uuid,$2::text,$3::uuid)`,\n [credentialId, credentialHash, requestId],\n );\n const row = opened.rows[0];\n if (!row) {\n await client.query(\"ROLLBACK\");\n transactionOpen = false;\n throw Object.assign(new Error(\"Credential is not active\"), { status: 401, code: \"unauthorized\" });\n }\n if (signal.aborted) throw signal.reason;\n const context: RequestAuthorityContext = {\n gatewayAuthorityId: row.gateway_authority_id,\n credential: { id: row.credential_id, principal: { workspace: row.workspace_id, agent: row.principal }, scopes: row.scopes },\n store: new PostgresBridgeStore(db),\n security: new PostgresGatewaySecurity(db),\n beginDomainWork: async () => {\n if (domainSavepoint) return;\n if (signal.aborted) throw signal.reason;\n await client.query(\"SAVEPOINT agent_bridge_domain_work\");\n if (signal.aborted) throw signal.reason;\n domainSavepoint = true;\n },\n endpointMigrationChallenges: {\n issue: async (input) => {\n let result;\n try {\n result = await client.query<{\n gateway_authority_id: string;\n issuer_credential_id: string;\n verifier_credential_id: string;\n expires_at: string | Date;\n }>(\n `SELECT gateway_authority_id,issuer_credential_id,verifier_credential_id,expires_at\n FROM agent_bridge.issue_endpoint_migration_challenge($1::uuid,$2::uuid,$3::text)`,\n [input.expectedGatewayAuthorityId, input.verifierCredentialId, input.challenge],\n );\n } catch (error) {\n endpointMigrationFailure(error);\n }\n const row = result.rows[0];\n if (!row) throw new Error(\"endpoint migration challenge could not be issued\");\n return {\n gatewayAuthorityId: row.gateway_authority_id,\n issuerCredentialId: row.issuer_credential_id,\n verifierCredentialId: row.verifier_credential_id,\n expiresAt: timestamp(row.expires_at),\n };\n },\n consume: async (input) => {\n let result;\n try {\n result = await client.query<{\n gateway_authority_id: string;\n issuer_credential_id: string;\n verifier_credential_id: string;\n expires_at: string | Date | null;\n consumed: boolean;\n }>(\n `SELECT gateway_authority_id,issuer_credential_id,verifier_credential_id,expires_at,consumed\n FROM agent_bridge.consume_endpoint_migration_challenge($1::uuid,$2::uuid,$3::text)`,\n [input.expectedGatewayAuthorityId, input.issuerCredentialId, input.challenge],\n );\n } catch (error) {\n endpointMigrationFailure(error);\n }\n const row = result.rows[0];\n if (!row) throw new Error(\"endpoint migration challenge could not be consumed\");\n return {\n gatewayAuthorityId: row.gateway_authority_id,\n issuerCredentialId: row.issuer_credential_id,\n verifierCredentialId: row.verifier_credential_id,\n expiresAt: row.expires_at === null ? null : timestamp(row.expires_at),\n consumed: row.consumed,\n };\n },\n },\n };\n try {\n const result = await work(context);\n if (signal.aborted) throw signal.reason;\n await client.query(\"SELECT agent_bridge.close_request_authority()\");\n if (signal.aborted) throw signal.reason;\n commitDispatched = true;\n await client.query(\"COMMIT\");\n commitCompleted = true;\n transactionOpen = false;\n return result;\n } catch (error) {\n if (commitDispatched) throw new MutationOutcomeUnknownError();\n if (signal.aborted) throw error;\n try {\n if (domainSavepoint) {\n await client.query(\"ROLLBACK TO SAVEPOINT agent_bridge_domain_work\");\n }\n await client.query(\"SELECT agent_bridge.close_request_authority()\");\n } catch {\n try {\n await client.query(\"ROLLBACK\");\n } catch (rollbackError) {\n discard = rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError));\n }\n transactionOpen = false;\n throw error;\n }\n if (signal.aborted) throw signal.reason;\n commitDispatched = true;\n await client.query(\"COMMIT\");\n commitCompleted = true;\n transactionOpen = false;\n throw error;\n }\n } catch (error) {\n if (commitDispatched && !commitCompleted) {\n discard = error instanceof Error ? error : new Error(String(error));\n throw error instanceof MutationOutcomeUnknownError ? error : new MutationOutcomeUnknownError();\n }\n if (transactionOpen) {\n try { await client.query(\"ROLLBACK\"); transactionOpen = false; }\n catch (rollbackError) { discard = rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError)); }\n }\n throw error;\n } finally {\n client.release(discard);\n }\n }\n\n}\n"],"mappings":"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","names":["row","databaseUrl"]}
|
|
1
|
+
{"version":3,"sources":["../src/gateway-main.ts","../src/gateway-security.ts","../src/postgres-request-authority.ts","../src/postgres-runtime-connection.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport pg from \"pg\";\nimport { PostgresCredentialResolver } from \"./gateway-auth.js\";\nimport { PostgresGatewaySecurity } from \"./gateway-security.js\";\nimport { createGateway } from \"./gateway.js\";\nimport {\n loadMigrationPlan,\n migrationsReady,\n runMigrations,\n runtimeSchemaReady,\n} from \"./migrations.js\";\nimport { PostgresBridgeStore } from \"./postgres-bridge-store.js\";\nimport { PostgresRequestAuthority } from \"./postgres-request-authority.js\";\nimport { runtimePostgresConnectionConfig } from \"./postgres-runtime-connection.js\";\n\nfunction integer(value: string | undefined, fallback: number, name: string): number {\n if (value === undefined) return fallback;\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed < 1) throw new Error(`${name} must be a positive integer`);\n return parsed;\n}\n\nasync function main(): Promise<void> {\n const migrationsDirectory = fileURLToPath(new URL(\"../sql/migrations\", import.meta.url));\n if (process.argv.includes(\"--migrate-only\")) {\n const databaseUrl = process.env.AGENT_BRIDGE_DATABASE_URL;\n if (!databaseUrl) throw new Error(\"AGENT_BRIDGE_DATABASE_URL is required for migrations\");\n const migrationPool = new pg.Pool({\n connectionString: databaseUrl,\n max: 1,\n application_name: \"agent-bridge-migrator\",\n });\n try {\n await runMigrations(migrationPool, migrationsDirectory);\n } finally {\n await migrationPool.end();\n }\n return;\n }\n if (process.env.AGENT_BRIDGE_MIGRATE === \"1\") {\n throw new Error(\"AGENT_BRIDGE_MIGRATE is not supported; run agent-bridge migrate separately\");\n }\n const databaseUrl = process.env.AGENT_BRIDGE_RUNTIME_DATABASE_URL;\n if (!databaseUrl) {\n throw new Error(\"AGENT_BRIDGE_RUNTIME_DATABASE_URL is required for the gateway\");\n }\n\n const deadline = integer(process.env.AGENT_BRIDGE_REQUEST_DEADLINE_MS, 10_000, \"request deadline\");\n const databaseTimeout = integer(\n process.env.AGENT_BRIDGE_DATABASE_TIMEOUT_MS,\n Math.max(1, Math.floor(deadline / 3)),\n \"database timeout\",\n );\n const poolSize = integer(process.env.AGENT_BRIDGE_DATABASE_POOL_SIZE, 10, \"database pool size\");\n const bodyLimitBytes = integer(\n process.env.AGENT_BRIDGE_BODY_LIMIT_BYTES,\n 128 * 1024,\n \"body limit\",\n );\n const host = process.env.AGENT_BRIDGE_HOST ?? \"127.0.0.1\";\n const port = integer(process.env.AGENT_BRIDGE_PORT, 8787, \"gateway port\");\n const allowedOrigins = process.env.AGENT_BRIDGE_ALLOWED_ORIGINS\n ?.split(\",\")\n .map((origin) => origin.trim())\n .filter(Boolean);\n const migrationPlan = await loadMigrationPlan(migrationsDirectory);\n const runtimeConnection = runtimePostgresConnectionConfig(\n databaseUrl,\n process.env.AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64,\n );\n const pool = new pg.Pool({\n ...runtimeConnection,\n max: poolSize,\n connectionTimeoutMillis: databaseTimeout,\n query_timeout: databaseTimeout,\n statement_timeout: databaseTimeout,\n application_name: \"agent-bridge-gateway\",\n });\n const readinessPool = new pg.Pool({\n ...runtimeConnection,\n max: 1,\n connectionTimeoutMillis: databaseTimeout,\n query_timeout: databaseTimeout,\n statement_timeout: databaseTimeout,\n application_name: \"agent-bridge-gateway-readiness\",\n });\n\n const store = new PostgresBridgeStore(pool);\n let readinessCheckedAt = 0;\n let readinessValue = false;\n let readinessInFlight: Promise<boolean> | undefined;\n const probeSchema = async () =>\n await migrationsReady(readinessPool, migrationPlan) && await runtimeSchemaReady(readinessPool);\n const schemaReady = (force = false): Promise<boolean> => {\n if (!force && Date.now() - readinessCheckedAt < 1_000) {\n return Promise.resolve(readinessValue);\n }\n if (readinessInFlight) return readinessInFlight;\n let current: Promise<boolean>;\n current = probeSchema()\n .then((ready) => {\n readinessValue = ready;\n readinessCheckedAt = Date.now();\n return ready;\n })\n .finally(() => {\n if (readinessInFlight === current) readinessInFlight = undefined;\n });\n readinessInFlight = current;\n return current;\n };\n try {\n if (!await schemaReady(true)) {\n throw new Error(\"runtime database is not ready for enforced row isolation\");\n }\n } catch (error) {\n await Promise.allSettled([pool.end(), readinessPool.end()]);\n throw error;\n }\n const gateway = createGateway({\n store,\n credentials: new PostgresCredentialResolver(pool),\n security: new PostgresGatewaySecurity(pool),\n requestAuthority: new PostgresRequestAuthority(pool),\n allowedOrigins,\n bodyLimitBytes,\n requestDeadlineMs: deadline,\n ready: schemaReady,\n rowIsolationReady: schemaReady,\n });\n\n gateway.listen(port, host, () => {\n process.stderr.write(`Agent Bridge gateway listening on ${host}:${port}\\n`);\n });\n\n let stopping = false;\n async function shutdown(): Promise<void> {\n if (stopping) return;\n stopping = true;\n await new Promise<void>((resolve, reject) => {\n gateway.close((error) => error ? reject(error) : resolve());\n });\n await Promise.all([pool.end(), readinessPool.end()]);\n }\n\n process.once(\"SIGINT\", () => void shutdown());\n process.once(\"SIGTERM\", () => void shutdown());\n}\n\nvoid main().catch((error) => {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`);\n process.exitCode = 1;\n});\n","import type { PgQueryable } from \"./postgres-bridge-store.js\";\nimport type { OperationId } from \"./contracts/registry.js\";\n\nexport interface RateLimitDecision {\n allowed: boolean;\n policyId: string | null;\n limit: number;\n remaining: number;\n retryAfterSeconds: number;\n}\n\nexport interface GatewaySecurity {\n recordScopeDenial(\n credentialId: string,\n operationId: OperationId,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<void>;\n consume(\n credentialId: string,\n operationId: OperationId,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<RateLimitDecision>;\n}\n\nexport class PostgresGatewaySecurity implements GatewaySecurity {\n constructor(private readonly db: PgQueryable) {}\n\n private functionName(bound: string, unbound: string): string {\n return this.db.inTransaction ? bound : unbound;\n }\n\n async recordScopeDenial(\n credentialId: string,\n operationId: OperationId,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<void> {\n if (signal?.aborted) throw signal.reason;\n await this.db.query(\n `SELECT agent_bridge.${this.functionName(\"record_scope_denial\", \"record_scope_denial_unbound_011\")}($1::uuid, $2::text, $3::uuid)`,\n [credentialId, operationId, requestId],\n );\n if (signal?.aborted) throw signal.reason;\n }\n\n async consume(\n credentialId: string,\n operationId: OperationId,\n requestId: string,\n signal?: AbortSignal,\n ): Promise<RateLimitDecision> {\n if (signal?.aborted) throw signal.reason;\n const result = await this.db.query<{\n allowed: boolean;\n limit_value: string | number;\n remaining_value: string | number;\n retry_after_seconds: string | number;\n denied_policy_id: string | null;\n }>(\n `SELECT allowed, limit_value, remaining_value, retry_after_seconds, denied_policy_id\n FROM agent_bridge.${this.functionName(\"consume_rate_limit\", \"consume_rate_limit_unbound_011\")}($1::uuid, $2::text, $3::uuid)`,\n [credentialId, operationId, requestId],\n );\n if (signal?.aborted) throw signal.reason;\n const row = result.rows[0];\n if (!row) throw new Error(\"rate limit policy is missing\");\n return {\n allowed: row.allowed,\n policyId: row.denied_policy_id,\n limit: Math.max(0, Number(row.limit_value)),\n remaining: Math.max(0, Number(row.remaining_value)),\n retryAfterSeconds: Math.max(0, Number(row.retry_after_seconds)),\n };\n }\n}\n","import type pg from \"pg\";\nimport type { BridgePrincipal } from \"./bridge-domain.js\";\nimport type { AuthorizationScope } from \"./contracts/registry.js\";\nimport { PostgresBridgeStore, type PgQueryable } from \"./postgres-bridge-store.js\";\nimport { PostgresGatewaySecurity } from \"./gateway-security.js\";\n\nexport interface RequestAuthorityContext {\n gatewayAuthorityId: string;\n credential: { id: string; principal: BridgePrincipal; scopes: readonly AuthorizationScope[] };\n store: PostgresBridgeStore;\n security: PostgresGatewaySecurity;\n /** Establishes the savepoint separating durable security accounting from domain work. */\n beginDomainWork(): Promise<void>;\n endpointMigrationChallenges?: {\n issue(input: { challenge: string; expectedGatewayAuthorityId: string; verifierCredentialId: string }): Promise<{ gatewayAuthorityId: string; issuerCredentialId: string; verifierCredentialId: string; expiresAt: string }>;\n consume(input: { challenge: string; expectedGatewayAuthorityId: string; issuerCredentialId: string }): Promise<{ gatewayAuthorityId: string; issuerCredentialId: string; verifierCredentialId: string; expiresAt: string | null; consumed: boolean }>;\n };\n}\n\nexport interface RequestAuthority {\n run<T>(credentialId: string, credentialHash: string, requestId: string, signal: AbortSignal, work: (context: RequestAuthorityContext) => Promise<T>): Promise<T>;\n}\n\ntype AuthorityRow = {\n gateway_authority_id: string;\n credential_id: string;\n workspace_id: string;\n principal: string;\n scopes: AuthorizationScope[];\n};\n\nfunction timestamp(value: unknown): string {\n if (value instanceof Date) return value.toISOString();\n return String(value);\n}\n\nfunction endpointMigrationFailure(error: unknown): never {\n const sqlstate = typeof error === \"object\" && error !== null && \"code\" in error\n ? String((error as { code?: unknown }).code ?? \"\")\n : \"\";\n if (sqlstate === \"28000\") {\n throw Object.assign(new Error(\"endpoint migration binding was rejected\"), {\n status: 409,\n code: \"endpoint_migration_binding_rejected\",\n });\n }\n if (sqlstate === \"23505\") {\n throw Object.assign(new Error(\"endpoint migration challenge conflicts with existing state\"), {\n status: 409,\n code: \"endpoint_migration_challenge_conflict\",\n });\n }\n throw error;\n}\n\nexport class MutationOutcomeUnknownError extends Error {\n readonly status = 503;\n readonly code = \"mutation_outcome_unknown\";\n constructor() { super(\"The database commit outcome is unknown\"); }\n}\n\nexport class PostgresRequestAuthority implements RequestAuthority {\n constructor(private readonly pool: Pick<pg.Pool, \"connect\">) {}\n\n async run<T>(credentialId: string, credentialHash: string, requestId: string, signal: AbortSignal, work: (context: RequestAuthorityContext) => Promise<T>): Promise<T> {\n if (signal.aborted) throw signal.reason;\n const client = await this.pool.connect();\n const db: PgQueryable = { inTransaction: true, query: (sql, values) => client.query(sql, values) };\n let transactionOpen = false;\n let domainSavepoint = false;\n let commitDispatched = false;\n let commitCompleted = false;\n let discard: Error | undefined;\n try {\n if (signal.aborted) throw signal.reason;\n await client.query(\"BEGIN\");\n transactionOpen = true;\n const opened = await client.query<AuthorityRow>(\n `SELECT gateway_authority_id,credential_id,workspace_id,principal,scopes\n FROM agent_bridge.open_request_authority_bound($1::uuid,$2::text,$3::uuid)`,\n [credentialId, credentialHash, requestId],\n );\n const row = opened.rows[0];\n if (!row) {\n await client.query(\"ROLLBACK\");\n transactionOpen = false;\n throw Object.assign(new Error(\"Credential is not active\"), { status: 401, code: \"unauthorized\" });\n }\n if (signal.aborted) throw signal.reason;\n const context: RequestAuthorityContext = {\n gatewayAuthorityId: row.gateway_authority_id,\n credential: { id: row.credential_id, principal: { workspace: row.workspace_id, agent: row.principal }, scopes: row.scopes },\n store: new PostgresBridgeStore(db),\n security: new PostgresGatewaySecurity(db),\n beginDomainWork: async () => {\n if (domainSavepoint) return;\n if (signal.aborted) throw signal.reason;\n await client.query(\"SAVEPOINT agent_bridge_domain_work\");\n if (signal.aborted) throw signal.reason;\n domainSavepoint = true;\n },\n endpointMigrationChallenges: {\n issue: async (input) => {\n let result;\n try {\n result = await client.query<{\n gateway_authority_id: string;\n issuer_credential_id: string;\n verifier_credential_id: string;\n expires_at: string | Date;\n }>(\n `SELECT gateway_authority_id,issuer_credential_id,verifier_credential_id,expires_at\n FROM agent_bridge.issue_endpoint_migration_challenge($1::uuid,$2::uuid,$3::text)`,\n [input.expectedGatewayAuthorityId, input.verifierCredentialId, input.challenge],\n );\n } catch (error) {\n endpointMigrationFailure(error);\n }\n const row = result.rows[0];\n if (!row) throw new Error(\"endpoint migration challenge could not be issued\");\n return {\n gatewayAuthorityId: row.gateway_authority_id,\n issuerCredentialId: row.issuer_credential_id,\n verifierCredentialId: row.verifier_credential_id,\n expiresAt: timestamp(row.expires_at),\n };\n },\n consume: async (input) => {\n let result;\n try {\n result = await client.query<{\n gateway_authority_id: string;\n issuer_credential_id: string;\n verifier_credential_id: string;\n expires_at: string | Date | null;\n consumed: boolean;\n }>(\n `SELECT gateway_authority_id,issuer_credential_id,verifier_credential_id,expires_at,consumed\n FROM agent_bridge.consume_endpoint_migration_challenge($1::uuid,$2::uuid,$3::text)`,\n [input.expectedGatewayAuthorityId, input.issuerCredentialId, input.challenge],\n );\n } catch (error) {\n endpointMigrationFailure(error);\n }\n const row = result.rows[0];\n if (!row) throw new Error(\"endpoint migration challenge could not be consumed\");\n return {\n gatewayAuthorityId: row.gateway_authority_id,\n issuerCredentialId: row.issuer_credential_id,\n verifierCredentialId: row.verifier_credential_id,\n expiresAt: row.expires_at === null ? null : timestamp(row.expires_at),\n consumed: row.consumed,\n };\n },\n },\n };\n try {\n const result = await work(context);\n if (signal.aborted) throw signal.reason;\n await client.query(\"SELECT agent_bridge.close_request_authority()\");\n if (signal.aborted) throw signal.reason;\n commitDispatched = true;\n await client.query(\"COMMIT\");\n commitCompleted = true;\n transactionOpen = false;\n return result;\n } catch (error) {\n if (commitDispatched) throw new MutationOutcomeUnknownError();\n if (signal.aborted) throw error;\n try {\n if (domainSavepoint) {\n await client.query(\"ROLLBACK TO SAVEPOINT agent_bridge_domain_work\");\n }\n await client.query(\"SELECT agent_bridge.close_request_authority()\");\n } catch {\n try {\n await client.query(\"ROLLBACK\");\n } catch (rollbackError) {\n discard = rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError));\n }\n transactionOpen = false;\n throw error;\n }\n if (signal.aborted) throw signal.reason;\n commitDispatched = true;\n await client.query(\"COMMIT\");\n commitCompleted = true;\n transactionOpen = false;\n throw error;\n }\n } catch (error) {\n if (commitDispatched && !commitCompleted) {\n discard = error instanceof Error ? error : new Error(String(error));\n throw error instanceof MutationOutcomeUnknownError ? error : new MutationOutcomeUnknownError();\n }\n if (transactionOpen) {\n try { await client.query(\"ROLLBACK\"); transactionOpen = false; }\n catch (rollbackError) { discard = rollbackError instanceof Error ? rollbackError : new Error(String(rollbackError)); }\n }\n throw error;\n } finally {\n client.release(discard);\n }\n }\n\n}\n","import type { PoolConfig } from \"pg\";\n\nconst MAX_CA_PEM_BYTES = 256 * 1024;\nconst BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u;\nconst CERTIFICATE_PATTERN = /-----BEGIN CERTIFICATE-----[\\s\\S]+-----END CERTIFICATE-----/u;\nconst CLIENT_CERTIFICATE_PARAMETERS = [\n \"sslcert\",\n \"sslkey\",\n \"sslpassword\",\n] as const;\n\nfunction decodeCaPem(encoded: string): string {\n const value = encoded.trim();\n if (!value) throw new Error(\"AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 cannot be empty\");\n if (!BASE64_PATTERN.test(value)) {\n throw new Error(\"AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 must be canonical base64\");\n }\n const bytes = Buffer.from(value, \"base64\");\n if (bytes.toString(\"base64\") !== value) {\n throw new Error(\"AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 must be canonical base64\");\n }\n if (bytes.byteLength > MAX_CA_PEM_BYTES) {\n throw new Error(\"AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 exceeds 256 KiB after decoding\");\n }\n const pem = bytes.toString(\"utf8\").trim();\n if (pem.includes(\"\\0\") || !CERTIFICATE_PATTERN.test(pem)) {\n throw new Error(\"AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 must decode to a PEM certificate bundle\");\n }\n return pem;\n}\n\nexport function runtimePostgresConnectionConfig(\n connectionString: string,\n caBase64: string | undefined,\n): Pick<PoolConfig, \"connectionString\" | \"ssl\"> {\n if (caBase64 === undefined) return { connectionString };\n\n let parsed: URL;\n try {\n parsed = new URL(connectionString);\n } catch {\n throw new Error(\"AGENT_BRIDGE_RUNTIME_DATABASE_URL must be a valid PostgreSQL URL\");\n }\n if (parsed.protocol !== \"postgresql:\" && parsed.protocol !== \"postgres:\") {\n throw new Error(\"AGENT_BRIDGE_RUNTIME_DATABASE_URL must use postgresql:// or postgres://\");\n }\n\n if (CLIENT_CERTIFICATE_PARAMETERS.some((parameter) => parsed.searchParams.has(parameter))) {\n throw new Error(\n \"AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 cannot be combined with client certificate URL parameters\",\n );\n }\n if (parsed.searchParams.has(\"ssl\")) {\n throw new Error(\n \"AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64 cannot be combined with the ssl URL parameter\",\n );\n }\n parsed.searchParams.delete(\"sslmode\");\n parsed.searchParams.delete(\"sslrootcert\");\n return {\n connectionString: parsed.toString(),\n ssl: {\n ca: decodeCaPem(caBase64),\n rejectUnauthorized: true,\n },\n };\n}\n"],"mappings":"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","names":["row","databaseUrl"]}
|
package/docs/deployment.md
CHANGED
|
@@ -175,12 +175,26 @@ names and check results but never environment-variable values. A passing local c
|
|
|
175
175
|
does not prove that an external resource exists or that deployment is safe.
|
|
176
176
|
|
|
177
177
|
The long-running Fly machine may receive only the restricted
|
|
178
|
-
`AGENT_BRIDGE_RUNTIME_DATABASE_URL` database authority
|
|
178
|
+
`AGENT_BRIDGE_RUNTIME_DATABASE_URL` database authority and the public CA bundle in
|
|
179
|
+
`AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64`. The gateway decodes the CA in memory,
|
|
180
|
+
removes `sslmode` and `sslrootcert` from the URL, and enables certificate and hostname
|
|
181
|
+
verification. It rejects the `ssl` parameter and client-certificate file parameters.
|
|
182
|
+
Do not give it
|
|
179
183
|
`AGENT_BRIDGE_DATABASE_URL`, `AGENT_BRIDGE_OPERATOR_DATABASE_URL`,
|
|
180
184
|
`AGENT_BRIDGE_RUNTIME_PASSWORD`, a client bearer token, or a Supabase client key. The
|
|
181
185
|
preflight rejects those names. Keep schema-owner, control-operator, archive, and DR
|
|
182
186
|
authority in separate operator jobs.
|
|
183
187
|
|
|
188
|
+
Import both runtime secrets through stdin so neither value appears in command
|
|
189
|
+
arguments or a temporary file:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
{
|
|
193
|
+
printf 'AGENT_BRIDGE_RUNTIME_DATABASE_URL=%s\n' "$(tr -d '\r\n' < runtime-url)"
|
|
194
|
+
printf 'AGENT_BRIDGE_RUNTIME_DATABASE_CA_BASE64=%s\n' "$(base64 < database-ca.pem | tr -d '\r\n')"
|
|
195
|
+
} | fly secrets import --stage --app <app>
|
|
196
|
+
```
|
|
197
|
+
|
|
184
198
|
### Operator order and gates
|
|
185
199
|
|
|
186
200
|
The following actions change external state and require an operator gate. The
|
|
@@ -213,9 +227,8 @@ repository does not perform them:
|
|
|
213
227
|
runtime password to that job only. The bootstrap is transactional and changes an
|
|
214
228
|
existing login only when its attributes, ownership, and membership graph already
|
|
215
229
|
match the recorded runtime contract. It never adopts a restored `NOLOGIN` shell.
|
|
216
|
-
6. Construct the restricted runtime URL
|
|
217
|
-
|
|
218
|
-
preflight.
|
|
230
|
+
6. Construct the restricted runtime URL. Import it and the base64-encoded PostgreSQL CA
|
|
231
|
+
as the two runtime secrets shown above, then run the app-aware read-only preflight.
|
|
219
232
|
7. Deploy the immutable image with `fly deploy --config deploy/fly.toml --app <app>`.
|
|
220
233
|
Require `/readyz` before routing clients.
|
|
221
234
|
8. Run authenticated capability and status probes, then publish, pull, claim, and
|
package/package.json
CHANGED