@hasna/infinity 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +3 -0
- package/README.md +331 -0
- package/dist/cli.js +2965 -0
- package/dist/http-api.js +6448 -0
- package/dist/index.js +28536 -0
- package/dist/migrate.js +346 -0
- package/dist/self-hosted-migrate.js +2064 -0
- package/dist/self-hosted.js +13180 -0
- package/dist/types/authcapsules/codewith-local.d.ts +109 -0
- package/dist/types/authcapsules/infinity-agent-policy.d.ts +57 -0
- package/dist/types/authcapsules/os-isolated-runner.d.ts +87 -0
- package/dist/types/authority/authority.d.ts +342 -0
- package/dist/types/authority/effect-journal.d.ts +205 -0
- package/dist/types/authority/mutation-journal.d.ts +163 -0
- package/dist/types/authority/mutation-tuple.d.ts +26 -0
- package/dist/types/authority/state.d.ts +75 -0
- package/dist/types/authority/store.d.ts +15 -0
- package/dist/types/brokers/checkpoint/archive.d.ts +21 -0
- package/dist/types/brokers/checkpoint/bundle.d.ts +22 -0
- package/dist/types/brokers/checkpoint/bytes.d.ts +4 -0
- package/dist/types/brokers/checkpoint/clean-verifier-finalization.d.ts +62 -0
- package/dist/types/brokers/checkpoint/clean-verifier.d.ts +145 -0
- package/dist/types/brokers/checkpoint/final-trust-authority.d.ts +55 -0
- package/dist/types/brokers/checkpoint/idempotency.d.ts +25 -0
- package/dist/types/brokers/checkpoint/journaled-store.d.ts +9 -0
- package/dist/types/brokers/checkpoint/manifest-signature.d.ts +135 -0
- package/dist/types/brokers/checkpoint/manifest.d.ts +57 -0
- package/dist/types/brokers/checkpoint/s3-store.d.ts +215 -0
- package/dist/types/brokers/checkpoint/seal-authority.d.ts +86 -0
- package/dist/types/brokers/checkpoint/service.d.ts +198 -0
- package/dist/types/brokers/checkpoint/signing-message.d.ts +22 -0
- package/dist/types/brokers/checkpoint/store.d.ts +76 -0
- package/dist/types/brokers/checkpoint/verifier-cleanup-authority.d.ts +108 -0
- package/dist/types/brokers/checkpoint/verifier-cleanup.d.ts +391 -0
- package/dist/types/brokers/checkpoint/verifier.d.ts +225 -0
- package/dist/types/brokers/composition.d.ts +86 -0
- package/dist/types/brokers/git/github-app.d.ts +216 -0
- package/dist/types/brokers/git/promotion.d.ts +418 -0
- package/dist/types/brokers/persistence/contracts.d.ts +77 -0
- package/dist/types/brokers/persistence/memory.d.ts +5 -0
- package/dist/types/brokers/persistence/migration.d.ts +6 -0
- package/dist/types/brokers/persistence/postgres.d.ts +74 -0
- package/dist/types/cli/main.d.ts +2 -0
- package/dist/types/cli/operation-journal.d.ts +47 -0
- package/dist/types/cli/run.d.ts +15 -0
- package/dist/types/client/operator-client.d.ts +100 -0
- package/dist/types/contracts/dtos.d.ts +198 -0
- package/dist/types/contracts/events.d.ts +45 -0
- package/dist/types/contracts/fence.d.ts +39 -0
- package/dist/types/contracts/operations.d.ts +91 -0
- package/dist/types/contracts/runspec.d.ts +75 -0
- package/dist/types/contracts/validators.d.ts +8 -0
- package/dist/types/core/clock.d.ts +12 -0
- package/dist/types/core/errors.d.ts +18 -0
- package/dist/types/core/ids.d.ts +10 -0
- package/dist/types/core/int64.d.ts +7 -0
- package/dist/types/core/strict-json.d.ts +8 -0
- package/dist/types/core/validation.d.ts +25 -0
- package/dist/types/golden/disposable-task.d.ts +108 -0
- package/dist/types/golden/source-pins.d.ts +77 -0
- package/dist/types/http/api.d.ts +52 -0
- package/dist/types/http/bearer-auth.d.ts +8 -0
- package/dist/types/http/main.d.ts +3 -0
- package/dist/types/http/self-hosted-main.d.ts +17 -0
- package/dist/types/index.d.ts +65 -0
- package/dist/types/mcp/schema.d.ts +7 -0
- package/dist/types/node-agent/agent.d.ts +174 -0
- package/dist/types/node-agent/contracts.d.ts +119 -0
- package/dist/types/node-agent/macos-service.d.ts +33 -0
- package/dist/types/node-agent/service-cli.d.ts +26 -0
- package/dist/types/persistence/composition.d.ts +49 -0
- package/dist/types/persistence/migrate-main.d.ts +2 -0
- package/dist/types/persistence/migrations.d.ts +30 -0
- package/dist/types/persistence/postgres-accounts-operation.d.ts +26 -0
- package/dist/types/persistence/postgres-authority.d.ts +69 -0
- package/dist/types/persistence/postgres-external-mutation-journal.d.ts +22 -0
- package/dist/types/persistence/postgres-json.d.ts +1 -0
- package/dist/types/persistence/postgres-mutation-journal.d.ts +31 -0
- package/dist/types/persistence/postgres-reconciler.d.ts +34 -0
- package/dist/types/persistence/postgres-sandbox-dispatch-authority.d.ts +9 -0
- package/dist/types/persistence/self-hosted-migrate-main.d.ts +2 -0
- package/dist/types/ports/accounts-operation.d.ts +336 -0
- package/dist/types/ports/accounts.d.ts +293 -0
- package/dist/types/ports/sandbox-dispatch-authority.d.ts +154 -0
- package/dist/types/ports/sandboxes.d.ts +40 -0
- package/dist/types/providers/sandbox-dispatch.d.ts +143 -0
- package/dist/types/runtime/config.d.ts +61 -0
- package/dist/types/runtime/postgres.d.ts +18 -0
- package/dist/types/runtime/provider-activation.d.ts +45 -0
- package/dist/types/runtime/server.d.ts +68 -0
- package/dist/types/testing/accounts-fake.d.ts +31 -0
- package/dist/types/testing/fakes.d.ts +2 -0
- package/dist/types/testing/node-control-plane-fake.d.ts +20 -0
- package/dist/types/testing/reference.d.ts +27 -0
- package/dist/types/testing/sandboxes-fake.d.ts +22 -0
- package/evidence/provider-live-daytona-v3.json +1 -0
- package/evidence/provider-live-e2b-v3.json +1 -0
- package/evidence/provider-production-route-daytona-v1.json +1 -0
- package/evidence/provider-production-route-e2b-v1.json +1 -0
- package/migrations/0001_authority.sql +689 -0
- package/migrations/0002_effect_step_global_owner.sql +2 -0
- package/migrations/0003_effect_journal_observed_head.sql +34 -0
- package/migrations/0004_authority_mutation_journal_binding.sql +15 -0
- package/migrations/20260710_checkpoint_broker_v1.sql +163 -0
- package/migrations/20260711_accounts_operation_v1.sql +137 -0
- package/migrations/20260711_checkpoint_trust_v2.sql +17 -0
- package/migrations/20260711_verifier_cleanup_v2.sql +169 -0
- package/migrations/20260711_z_accounts_claim_authority_v2.sql +172 -0
- package/migrations/20260711_zz_sandbox_dispatch_authority_v1.sql +143 -0
- package/migrations/20260712_sandbox_dispatch_authority_v2.sql +58 -0
- package/migrations/external-journal/0001_external_authority_mutation_journal.sql +175 -0
- package/package.json +47 -0
package/dist/migrate.js
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// src/persistence/migrate-main.ts
|
|
5
|
+
var {SQL } = globalThis.Bun;
|
|
6
|
+
|
|
7
|
+
// src/core/errors.ts
|
|
8
|
+
class InfinityError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
status;
|
|
11
|
+
retryable;
|
|
12
|
+
details;
|
|
13
|
+
constructor(code, message, options = {}) {
|
|
14
|
+
super(message, { cause: options.cause });
|
|
15
|
+
this.name = "InfinityError";
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.status = options.status ?? 400;
|
|
18
|
+
this.retryable = options.retryable ?? false;
|
|
19
|
+
this.details = Object.freeze({ ...options.details ?? {} });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/core/validation.ts
|
|
24
|
+
function isPlainObject(value) {
|
|
25
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
26
|
+
return false;
|
|
27
|
+
const prototype = Object.getPrototypeOf(value);
|
|
28
|
+
return prototype === Object.prototype || prototype === null;
|
|
29
|
+
}
|
|
30
|
+
var CREDENTIAL_CARRIER_PATTERN_SOURCE = "^(?:[sS][kK]-[A-Za-z0-9]|[gG][hH][pPoOuUsSrR]_|[gG][iI][tT][hH][uU][bB]_[pP][aA][tT]_|[xX][oO][xX][bBaApPrRsS]-|[aA][kKsS][iI][aA][0-9A-Za-z]|[aA][iI][zZ][aA]|[nN][pP][mM]_|[xX][aA][iI]-|[cC][tT][xX]7[sS][kK]-)";
|
|
31
|
+
var CREDENTIAL_CARRIER_PATTERN = new RegExp(CREDENTIAL_CARRIER_PATTERN_SOURCE);
|
|
32
|
+
function invalid(message) {
|
|
33
|
+
return new InfinityError("invalid_input", message, { status: 2 });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/core/strict-json.ts
|
|
37
|
+
var strictDocuments = new WeakSet;
|
|
38
|
+
var strictDocumentDigests = new WeakMap;
|
|
39
|
+
function canonicalJson(value) {
|
|
40
|
+
if (value === null)
|
|
41
|
+
return "null";
|
|
42
|
+
if (typeof value === "boolean")
|
|
43
|
+
return value ? "true" : "false";
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
if (value.normalize("NFC") !== value)
|
|
46
|
+
throw invalid("canonical JSON strings must use NFC normalization");
|
|
47
|
+
if (hasUnpairedSurrogate(value))
|
|
48
|
+
throw invalid("canonical JSON strings must not contain unpaired surrogates");
|
|
49
|
+
return JSON.stringify(value);
|
|
50
|
+
}
|
|
51
|
+
if (typeof value === "number") {
|
|
52
|
+
if (!Number.isFinite(value) || Object.is(value, -0))
|
|
53
|
+
throw invalid("canonical JSON numbers must be finite and not negative zero");
|
|
54
|
+
if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
|
|
55
|
+
throw invalid("canonical JSON integer Numbers must be safe integers; use bigint for exact wider values");
|
|
56
|
+
}
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
if (typeof value === "bigint")
|
|
60
|
+
return value.toString(10);
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
63
|
+
if (!Object.hasOwn(value, index))
|
|
64
|
+
throw invalid("canonical JSON arrays must not be sparse");
|
|
65
|
+
}
|
|
66
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
|
|
67
|
+
}
|
|
68
|
+
if (!isPlainObject(value))
|
|
69
|
+
throw invalid("canonical JSON accepts only JSON-compatible values");
|
|
70
|
+
const entries = Object.keys(value).sort().map((key) => {
|
|
71
|
+
if (key.normalize("NFC") !== key)
|
|
72
|
+
throw invalid("canonical JSON keys must use NFC normalization");
|
|
73
|
+
if (hasUnpairedSurrogate(key))
|
|
74
|
+
throw invalid("canonical JSON keys must not contain unpaired surrogates");
|
|
75
|
+
const nested = value[key];
|
|
76
|
+
if (nested === undefined)
|
|
77
|
+
throw invalid("canonical JSON object contains an undefined field");
|
|
78
|
+
return `${JSON.stringify(key)}:${canonicalJson(nested)}`;
|
|
79
|
+
});
|
|
80
|
+
return `{${entries.join(",")}}`;
|
|
81
|
+
}
|
|
82
|
+
function hasUnpairedSurrogate(value) {
|
|
83
|
+
for (let index = 0;index < value.length; index += 1) {
|
|
84
|
+
const unit = value.charCodeAt(index);
|
|
85
|
+
if (unit >= 55296 && unit <= 56319) {
|
|
86
|
+
const next = value.charCodeAt(index + 1);
|
|
87
|
+
if (!(next >= 56320 && next <= 57343))
|
|
88
|
+
return true;
|
|
89
|
+
index += 1;
|
|
90
|
+
} else if (unit >= 56320 && unit <= 57343) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// src/persistence/migrations.ts
|
|
98
|
+
import { existsSync, readFileSync } from "fs";
|
|
99
|
+
import { dirname, join, parse } from "path";
|
|
100
|
+
import { fileURLToPath } from "url";
|
|
101
|
+
var DEFAULT_POSTGRES_ROLES = {
|
|
102
|
+
runtimeRole: "infinity_runtime",
|
|
103
|
+
reconcilerRole: "infinity_reconciler",
|
|
104
|
+
journalAckerRole: "infinity_journal_acker"
|
|
105
|
+
};
|
|
106
|
+
var POSTGRES_MIGRATION_MANIFEST = Object.freeze({
|
|
107
|
+
"0001_authority.sql": "sha256:bfcff16873cf8664ee46aeea581f9f4d212bac05cf3bbc1817dc495434f7f1d6",
|
|
108
|
+
"0002_effect_step_global_owner.sql": "sha256:3342801f79c4cf6321bbeaa3928cf7a0d4a3e9a391505274e88931c48eb4632d",
|
|
109
|
+
"0003_effect_journal_observed_head.sql": "sha256:0cf5388c8665f9727df4f1f7430761086bd2fa03d0e1e2aaddecda9ca7790aba",
|
|
110
|
+
"0004_authority_mutation_journal_binding.sql": "sha256:ddeb4d190016536f61ea55e3e0c36a5fa28211af5ed2c9a41c752ce3b1a857d0",
|
|
111
|
+
"20260710_checkpoint_broker_v1.sql": "sha256:3e250bee6358188cf30acb2ab14f31edfdf94c2b73502793b91a7caaf28c3d8f",
|
|
112
|
+
"20260711_checkpoint_trust_v2.sql": "sha256:5165e08ae32b671838a2ee9349b35cf7007afade70ec19694ad27334bdaac065",
|
|
113
|
+
"20260711_verifier_cleanup_v2.sql": "sha256:fe06ed0ec0bde18310321e7cce19c885f127e550ac1ea365b159be4effb71e79",
|
|
114
|
+
"20260711_accounts_operation_v1.sql": "sha256:7d9caa3aa9fbbdab501e81b363ec5258b006a86572211eccb563b000d52d3a62",
|
|
115
|
+
"20260711_z_accounts_claim_authority_v2.sql": "sha256:1da1ace609a4813472f2637d61b435cf372bd9d0f66694b3c502e17da3b9de38",
|
|
116
|
+
"20260711_zz_sandbox_dispatch_authority_v1.sql": "sha256:543bb2c9b392c98c2069c437616e3d7d0fbf1d02cb8d1f6e09f1bad83238e69f",
|
|
117
|
+
"20260712_sandbox_dispatch_authority_v2.sql": "sha256:c93b5b31d4f94549018300c94a568a4b8b5a403f28bf04d6177d14726518b3e3"
|
|
118
|
+
});
|
|
119
|
+
async function applyPostgresMigrations(sql, directory) {
|
|
120
|
+
const migrationDirectory = directory ?? packagedMigrationDirectory(import.meta.url);
|
|
121
|
+
const paths = [];
|
|
122
|
+
for await (const path of new Bun.Glob("*.sql").scan({ cwd: migrationDirectory, absolute: true, onlyFiles: true })) {
|
|
123
|
+
paths.push(path);
|
|
124
|
+
}
|
|
125
|
+
paths.sort((left, right) => left.localeCompare(right));
|
|
126
|
+
if (paths.length === 0)
|
|
127
|
+
throw new InfinityError("invalid_input", "no PostgreSQL migrations were found");
|
|
128
|
+
const names = paths.map((path) => path.slice(path.lastIndexOf("/") + 1));
|
|
129
|
+
if (canonicalStringArray(names) !== canonicalStringArray(Object.keys(POSTGRES_MIGRATION_MANIFEST))) {
|
|
130
|
+
throw new InfinityError("integrity_failure", "PostgreSQL migration files differ from the compiled immutable manifest");
|
|
131
|
+
}
|
|
132
|
+
return sql.begin(async (tx) => {
|
|
133
|
+
await tx.unsafe(`
|
|
134
|
+
CREATE SCHEMA IF NOT EXISTS infinity;
|
|
135
|
+
CREATE TABLE IF NOT EXISTS infinity.schema_migrations (
|
|
136
|
+
migration_name text PRIMARY KEY,
|
|
137
|
+
checksum text NOT NULL CHECK (checksum ~ '^sha256:[0-9a-f]{64}$'),
|
|
138
|
+
applied_at timestamptz NOT NULL
|
|
139
|
+
);
|
|
140
|
+
`).simple();
|
|
141
|
+
await tx`SELECT pg_advisory_xact_lock(hashtextextended('infinity.schema.migrations', 0))`;
|
|
142
|
+
const applied = [];
|
|
143
|
+
const alreadyApplied = [];
|
|
144
|
+
for (const path of paths) {
|
|
145
|
+
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
146
|
+
if (!/^(?:\d{4}|\d{8})_[a-z0-9_]+\.sql$/.test(name)) {
|
|
147
|
+
throw new InfinityError("invalid_input", "migration filename is not canonical");
|
|
148
|
+
}
|
|
149
|
+
const source = await Bun.file(path).text();
|
|
150
|
+
const checksum = rawSha256(source);
|
|
151
|
+
const compiledChecksum = POSTGRES_MIGRATION_MANIFEST[name];
|
|
152
|
+
if (checksum !== compiledChecksum) {
|
|
153
|
+
throw new InfinityError("integrity_failure", `migration bytes differ from compiled manifest: ${name}`);
|
|
154
|
+
}
|
|
155
|
+
const existing = await tx`
|
|
156
|
+
SELECT checksum FROM infinity.schema_migrations WHERE migration_name = ${name}
|
|
157
|
+
`;
|
|
158
|
+
if (existing.length > 0) {
|
|
159
|
+
if (existing[0].checksum !== checksum) {
|
|
160
|
+
throw new InfinityError("integrity_failure", `applied migration checksum differs: ${name}`);
|
|
161
|
+
}
|
|
162
|
+
alreadyApplied.push(name);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
await tx.unsafe(source).simple();
|
|
166
|
+
await tx`
|
|
167
|
+
INSERT INTO infinity.schema_migrations (migration_name, checksum, applied_at)
|
|
168
|
+
VALUES (${name}, ${checksum}, clock_timestamp())
|
|
169
|
+
`;
|
|
170
|
+
applied.push(name);
|
|
171
|
+
}
|
|
172
|
+
return { applied, alreadyApplied };
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function packagedMigrationDirectory(moduleUrl) {
|
|
176
|
+
let candidate = dirname(fileURLToPath(moduleUrl));
|
|
177
|
+
const filesystemRoot = parse(candidate).root;
|
|
178
|
+
while (candidate !== filesystemRoot) {
|
|
179
|
+
const packagePath = join(candidate, "package.json");
|
|
180
|
+
const migrationsPath = join(candidate, "migrations");
|
|
181
|
+
if (existsSync(packagePath) && existsSync(migrationsPath)) {
|
|
182
|
+
try {
|
|
183
|
+
const packageManifest = JSON.parse(readFileSync(packagePath, "utf8"));
|
|
184
|
+
if (packageManifest.name === "@hasna/infinity")
|
|
185
|
+
return migrationsPath;
|
|
186
|
+
} catch {
|
|
187
|
+
throw new InfinityError("integrity_failure", "Infinity package manifest cannot locate PostgreSQL migrations");
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
candidate = dirname(candidate);
|
|
191
|
+
}
|
|
192
|
+
throw new InfinityError("invalid_input", "no PostgreSQL migrations were found");
|
|
193
|
+
}
|
|
194
|
+
async function configurePostgresRoles(sql, roles = DEFAULT_POSTGRES_ROLES) {
|
|
195
|
+
const runtimeRole = sqlIdentifier(roles.runtimeRole, "runtime role");
|
|
196
|
+
const reconcilerRole = sqlIdentifier(roles.reconcilerRole, "reconciler role");
|
|
197
|
+
const journalAckerRole = sqlIdentifier(roles.journalAckerRole, "journal acker role");
|
|
198
|
+
if (new Set([runtimeRole, reconcilerRole, journalAckerRole]).size !== 3) {
|
|
199
|
+
throw new InfinityError("invalid_input", "runtime, reconciler, and journal acker roles must differ");
|
|
200
|
+
}
|
|
201
|
+
const session = await sql`
|
|
202
|
+
SELECT current_user, database.datname AS database_name, owner.rolname AS database_owner
|
|
203
|
+
FROM pg_database database
|
|
204
|
+
JOIN pg_roles owner ON owner.oid = database.datdba
|
|
205
|
+
WHERE database.datname = current_database()
|
|
206
|
+
`;
|
|
207
|
+
const database = session[0];
|
|
208
|
+
if ([runtimeRole, reconcilerRole, journalAckerRole].includes(database?.current_user ?? "")) {
|
|
209
|
+
throw new InfinityError("capability_denied", "migration session must be distinct from authority service roles", { status: 403 });
|
|
210
|
+
}
|
|
211
|
+
if (!database || database.database_owner !== database.current_user) {
|
|
212
|
+
throw new InfinityError("capability_denied", "Infinity migration role must be the dedicated owner of the current database", { status: 503 });
|
|
213
|
+
}
|
|
214
|
+
for (const role of [runtimeRole, reconcilerRole, journalAckerRole]) {
|
|
215
|
+
const found = await sql`SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = ${role}) AS present`;
|
|
216
|
+
if (!found[0]?.present)
|
|
217
|
+
await sql.unsafe(`CREATE ROLE ${quoteIdentifier(role)} NOLOGIN`);
|
|
218
|
+
await sql.unsafe(`ALTER ROLE ${quoteIdentifier(role)} NOSUPERUSER NOCREATEDB NOCREATEROLE NOLOGIN NOREPLICATION NOBYPASSRLS`);
|
|
219
|
+
const parentMemberships = await sql`
|
|
220
|
+
SELECT parent.rolname AS role_name
|
|
221
|
+
FROM pg_auth_members membership
|
|
222
|
+
JOIN pg_roles child ON child.oid = membership.member
|
|
223
|
+
JOIN pg_roles parent ON parent.oid = membership.roleid
|
|
224
|
+
WHERE child.rolname = ${role}
|
|
225
|
+
`;
|
|
226
|
+
if (parentMemberships.length > 0) {
|
|
227
|
+
throw new InfinityError("capability_denied", `Infinity authority role ${role} has a privilege-escalating parent membership`, { status: 503 });
|
|
228
|
+
}
|
|
229
|
+
const databaseOwnerMembership = await sql`
|
|
230
|
+
SELECT pg_has_role(${role}, 'pg_database_owner', 'MEMBER') AS member,
|
|
231
|
+
pg_has_role(${role}, 'pg_database_owner', 'SET') AS settable
|
|
232
|
+
`;
|
|
233
|
+
if (databaseOwnerMembership[0]?.member || databaseOwnerMembership[0]?.settable) {
|
|
234
|
+
throw new InfinityError("capability_denied", `Infinity authority role ${role} must not inherit or SET pg_database_owner`, { status: 503 });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const runtime = quoteIdentifier(runtimeRole);
|
|
238
|
+
const reconciler = quoteIdentifier(reconcilerRole);
|
|
239
|
+
const journalAcker = quoteIdentifier(journalAckerRole);
|
|
240
|
+
await sql.begin(async (tx) => {
|
|
241
|
+
const databaseIdentifier = quoteIdentifier(database.database_name);
|
|
242
|
+
await tx.unsafe(`REVOKE CREATE, TEMPORARY ON DATABASE ${databaseIdentifier} FROM PUBLIC`);
|
|
243
|
+
await tx.unsafe(`REVOKE ALL PRIVILEGES ON DATABASE ${databaseIdentifier} FROM ${runtime}, ${reconciler}, ${journalAcker}`);
|
|
244
|
+
await tx.unsafe(`GRANT CONNECT ON DATABASE ${databaseIdentifier} TO ${runtime}, ${reconciler}, ${journalAcker}`);
|
|
245
|
+
await tx.unsafe("REVOKE ALL ON SCHEMA infinity FROM PUBLIC");
|
|
246
|
+
await tx.unsafe(`REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA infinity FROM ${runtime}, ${reconciler}, ${journalAcker}`);
|
|
247
|
+
await tx.unsafe(`REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA infinity FROM ${runtime}, ${reconciler}, ${journalAcker}`);
|
|
248
|
+
await tx.unsafe(`GRANT USAGE ON SCHEMA infinity TO ${runtime}, ${reconciler}, ${journalAcker}`);
|
|
249
|
+
await tx.unsafe(`GRANT SELECT ON ALL TABLES IN SCHEMA infinity TO ${runtime}, ${reconciler}`);
|
|
250
|
+
await tx.unsafe(`GRANT SELECT ON infinity.authority_runtime_meta, infinity.authority_mutation_envelopes,
|
|
251
|
+
infinity.authority_mutation_acknowledgements, infinity.authority_mutation_journal_binding,
|
|
252
|
+
infinity.outbox TO ${journalAcker}`);
|
|
253
|
+
await tx.unsafe(`GRANT INSERT ON infinity.run_specs, infinity.runs, infinity.attempts,
|
|
254
|
+
infinity.attempt_lease_grants, infinity.attempt_lease_events, infinity.queue_entries,
|
|
255
|
+
infinity.admission_keys, infinity.idempotency_records, infinity.events, infinity.budget_reservations,
|
|
256
|
+
infinity.event_stream_heads, infinity.outbox, infinity.external_operations,
|
|
257
|
+
infinity.authority_mutation_envelopes, infinity.accounts_resource_authority,
|
|
258
|
+
infinity.accounts_operation_signer_pin, infinity.accounts_serialization_domains,
|
|
259
|
+
infinity.accounts_serialization_frontier_heads, infinity.accounts_model_call_operations,
|
|
260
|
+
infinity.accounts_model_effect_journal, infinity.sandbox_dispatch_accounts_consume_evidence,
|
|
261
|
+
infinity.sandbox_dispatch_authorizations TO ${runtime}`);
|
|
262
|
+
await tx.unsafe(`GRANT UPDATE ON infinity.runs, infinity.attempts, infinity.queue_entries,
|
|
263
|
+
infinity.event_stream_heads, infinity.external_operations TO ${runtime}`);
|
|
264
|
+
await tx.unsafe(`GRANT UPDATE
|
|
265
|
+
(head_sequence, head_digest, pin_digest, pin_record, updated_at)
|
|
266
|
+
ON infinity.accounts_operation_signer_pin TO ${runtime}`);
|
|
267
|
+
await tx.unsafe(`GRANT UPDATE
|
|
268
|
+
(state, updated_at)
|
|
269
|
+
ON infinity.accounts_resource_authority TO ${runtime}`);
|
|
270
|
+
await tx.unsafe(`GRANT UPDATE
|
|
271
|
+
(active_model_operation_id, revision, state_digest, state_record, updated_at)
|
|
272
|
+
ON infinity.accounts_serialization_domains TO ${runtime}`);
|
|
273
|
+
await tx.unsafe(`GRANT UPDATE
|
|
274
|
+
(state, consume_receipt_digest, use_id, projection_digest, projection_record, updated_at)
|
|
275
|
+
ON infinity.accounts_model_call_operations TO ${runtime}`);
|
|
276
|
+
await tx.unsafe(`GRANT UPDATE
|
|
277
|
+
(state, consume_operation_id, consume_idempotency_key, consume_request_digest,
|
|
278
|
+
effect_claim_digest, canonical_intent_digest, dispatch_use_id, consumption_receipt_digest,
|
|
279
|
+
consumption_receipt_record, consumed_at, provider_contact_expires_at, updated_at)
|
|
280
|
+
ON infinity.sandbox_dispatch_authorizations TO ${runtime}`);
|
|
281
|
+
await tx.unsafe(`GRANT UPDATE
|
|
282
|
+
(next_queue_sequence, next_commit_sequence, budget_reserved_units, budget_charged_units, budget_released_units)
|
|
283
|
+
ON infinity.authority_runtime_meta TO ${runtime}`);
|
|
284
|
+
await tx.unsafe(`GRANT INSERT ON infinity.effect_operation_steps, infinity.effect_journal_records,
|
|
285
|
+
infinity.effect_journal_observed_head TO ${reconciler}`);
|
|
286
|
+
await tx.unsafe(`GRANT UPDATE ON infinity.effect_journal_frontier,
|
|
287
|
+
infinity.effect_journal_observed_head TO ${reconciler}`);
|
|
288
|
+
await tx.unsafe(`GRANT UPDATE (state, lease_owner, lease_expires_at) ON infinity.outbox TO ${reconciler}`);
|
|
289
|
+
await tx.unsafe(`GRANT INSERT ON infinity.authority_mutation_acknowledgements TO ${journalAcker}`);
|
|
290
|
+
await tx.unsafe(`GRANT INSERT ON infinity.authority_mutation_journal_binding TO ${journalAcker}`);
|
|
291
|
+
await tx.unsafe(`GRANT UPDATE (journal_acknowledged_through)
|
|
292
|
+
ON infinity.authority_runtime_meta TO ${journalAcker}`);
|
|
293
|
+
await tx.unsafe(`GRANT UPDATE (state) ON infinity.outbox TO ${journalAcker}`);
|
|
294
|
+
await tx.unsafe(`REVOKE CREATE ON SCHEMA infinity FROM ${runtime}, ${reconciler}, ${journalAcker}`);
|
|
295
|
+
await tx.unsafe(`REVOKE DELETE, TRUNCATE, REFERENCES, TRIGGER ON ALL TABLES IN SCHEMA infinity FROM ${runtime}, ${reconciler}, ${journalAcker}`);
|
|
296
|
+
await tx.unsafe(`REVOKE ALL ON FUNCTION infinity.capture_authority_mutation_change(),
|
|
297
|
+
infinity.validate_authority_mutation_envelope(), infinity.validate_authority_mutation_acknowledgement(),
|
|
298
|
+
infinity.validate_authority_ack_frontier_update(), infinity.validate_outbox_write(),
|
|
299
|
+
infinity.validate_effect_journal_observed_head_update(),
|
|
300
|
+
infinity.validate_accounts_operation_signer_pin_update(),
|
|
301
|
+
infinity.validate_accounts_resource_authority_update(),
|
|
302
|
+
infinity.validate_sandbox_dispatch_authorization_update()
|
|
303
|
+
FROM PUBLIC, ${runtime}, ${reconciler}, ${journalAcker}`);
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function rawSha256(source) {
|
|
307
|
+
const value = new Bun.CryptoHasher("sha256").update(source).digest("hex");
|
|
308
|
+
return `sha256:${value}`;
|
|
309
|
+
}
|
|
310
|
+
function sqlIdentifier(value, label) {
|
|
311
|
+
if (!/^[a-z][a-z0-9_]{0,62}$/.test(value))
|
|
312
|
+
throw new InfinityError("invalid_input", `${label} is not a canonical PostgreSQL identifier`);
|
|
313
|
+
return value;
|
|
314
|
+
}
|
|
315
|
+
function quoteIdentifier(value) {
|
|
316
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
317
|
+
}
|
|
318
|
+
function canonicalStringArray(values) {
|
|
319
|
+
return JSON.stringify([...values].sort((left, right) => left.localeCompare(right)));
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/persistence/migrate-main.ts
|
|
323
|
+
var url = process.env.INFINITY_MIGRATOR_DATABASE_URL;
|
|
324
|
+
if (!url) {
|
|
325
|
+
process.stderr.write(`INFINITY_MIGRATOR_DATABASE_URL is required
|
|
326
|
+
`);
|
|
327
|
+
process.exitCode = 2;
|
|
328
|
+
} else {
|
|
329
|
+
const sql = new SQL(url, { max: 1 });
|
|
330
|
+
try {
|
|
331
|
+
const result = await applyPostgresMigrations(sql);
|
|
332
|
+
await configurePostgresRoles(sql, {
|
|
333
|
+
runtimeRole: process.env.INFINITY_RUNTIME_ROLE ?? "infinity_runtime",
|
|
334
|
+
reconcilerRole: process.env.INFINITY_RECONCILER_ROLE ?? "infinity_reconciler",
|
|
335
|
+
journalAckerRole: process.env.INFINITY_JOURNAL_ACKER_ROLE ?? "infinity_journal_acker"
|
|
336
|
+
});
|
|
337
|
+
process.stdout.write(`${canonicalJson({ ok: true, ...result })}
|
|
338
|
+
`);
|
|
339
|
+
} catch (error) {
|
|
340
|
+
process.stderr.write(`${error instanceof Error ? error.message : "migration failed"}
|
|
341
|
+
`);
|
|
342
|
+
process.exitCode = 1;
|
|
343
|
+
} finally {
|
|
344
|
+
await sql.close({ timeout: 1 });
|
|
345
|
+
}
|
|
346
|
+
}
|