@tinycloud-ai/tinycloud-cli 0.1.0 → 0.1.2
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/README.md +1 -3
- package/dist/main.js +29 -29
- package/package.json +1 -10
- package/dist/main.js.map +0 -7
package/README.md
CHANGED
|
@@ -22,9 +22,7 @@ tiny plan --cwd ./my-app
|
|
|
22
22
|
tiny deploy --cwd ./my-app
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
Run `tiny --help` for the complete command list.
|
|
26
|
-
documentation is available in the
|
|
27
|
-
[Tinycloud repository](https://github.com/scar-ai/tinycloud).
|
|
25
|
+
Run `tiny --help` for the complete command list.
|
|
28
26
|
|
|
29
27
|
## License
|
|
30
28
|
|
package/dist/main.js
CHANGED
|
@@ -5,6 +5,11 @@ import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync
|
|
|
5
5
|
import { basename, join as join5, resolve } from "node:path";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
|
|
8
|
+
// ../../packages/domain/src/cli.ts
|
|
9
|
+
var CLI_PACKAGE = "@tinycloud-ai/tinycloud-cli";
|
|
10
|
+
var CLI_VERSION = "0.1.2";
|
|
11
|
+
var CLI_SPEC = `${CLI_PACKAGE}@${CLI_VERSION}`;
|
|
12
|
+
|
|
8
13
|
// ../../packages/domain/src/ids.ts
|
|
9
14
|
var lastRandom = new Uint8Array(10);
|
|
10
15
|
|
|
@@ -15,6 +20,7 @@ var ERROR_CODES = {
|
|
|
15
20
|
AUTH_PROVIDER_REQUIRED: 501,
|
|
16
21
|
FORBIDDEN: 403,
|
|
17
22
|
NOT_FOUND: 404,
|
|
23
|
+
METHOD_NOT_ALLOWED: 405,
|
|
18
24
|
CONFLICT: 409,
|
|
19
25
|
PRECONDITION_FAILED: 412,
|
|
20
26
|
VALIDATION_FAILED: 422,
|
|
@@ -28,7 +34,6 @@ var ERROR_CODES = {
|
|
|
28
34
|
MANIFEST_UNSUPPORTED_API_VERSION: 422,
|
|
29
35
|
// Planning / provider
|
|
30
36
|
TARGET_INCOMPATIBLE: 422,
|
|
31
|
-
APPROVAL_REQUIRED: 409,
|
|
32
37
|
PLAN_EXPIRED: 409,
|
|
33
38
|
PLAN_STALE: 409,
|
|
34
39
|
PROVIDER_UNAVAILABLE: 503,
|
|
@@ -89,9 +94,9 @@ var RETRYABLE = /* @__PURE__ */ new Set([
|
|
|
89
94
|
]);
|
|
90
95
|
var DEFAULT_REMEDIATION = {
|
|
91
96
|
UNAUTHENTICATED: "Run `tiny login` and retry.",
|
|
97
|
+
METHOD_NOT_ALLOWED: "Use one of the methods named in the Allow response header.",
|
|
92
98
|
AUTH_PROVIDER_REQUIRED: "Configure a trusted OIDC provider for this gateway.",
|
|
93
99
|
FORBIDDEN: "Ask an organization admin for the required role.",
|
|
94
|
-
APPROVAL_REQUIRED: "Ask an organization admin to approve the plan, then deploy again with the approved plan ID.",
|
|
95
100
|
PLAN_STALE: "Run `tiny plan` again; the manifest or policy changed since this plan was produced.",
|
|
96
101
|
MIGRATION_CHECKSUM_CHANGED: "Restore the original migration file and add a new migration instead.",
|
|
97
102
|
INTERACTION_REQUIRED: "Re-run interactively, or pass the flag named in details.missingInput.",
|
|
@@ -121,8 +126,6 @@ function parseDuration(value) {
|
|
|
121
126
|
var DEFAULT_ORGANIZATION_POLICY = {
|
|
122
127
|
allowPublicApps: false,
|
|
123
128
|
allowRawSecrets: true,
|
|
124
|
-
requireApprovalForCapabilities: true,
|
|
125
|
-
requireApprovalForRawSecrets: true,
|
|
126
129
|
maxAppsPerOrg: 500,
|
|
127
130
|
maxPreviewTtlHours: 168,
|
|
128
131
|
maxMemoryMiB: 2048,
|
|
@@ -1092,6 +1095,9 @@ var STORAGE_LIMITS = {
|
|
|
1092
1095
|
maxConcurrentOperationsPerEnvironment: 16
|
|
1093
1096
|
};
|
|
1094
1097
|
|
|
1098
|
+
// ../../packages/orchestrator/src/lifecycle.ts
|
|
1099
|
+
var MAX_DELETE_GRACE_MS = 90 * 864e5;
|
|
1100
|
+
|
|
1095
1101
|
// ../../packages/db/src/store.ts
|
|
1096
1102
|
import { dirname as dirname3, join as join3 } from "node:path";
|
|
1097
1103
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
@@ -1165,7 +1171,11 @@ var ApiClient = class {
|
|
|
1165
1171
|
return this.request("DELETE", path, query ? { query } : {});
|
|
1166
1172
|
}
|
|
1167
1173
|
uploadDirectory(directory) {
|
|
1168
|
-
return this.
|
|
1174
|
+
return this.uploadArtifact(createArtifactUpload(directory));
|
|
1175
|
+
}
|
|
1176
|
+
/** Upload a source tree the caller has already packed, from wherever it read it. */
|
|
1177
|
+
uploadArtifact(upload) {
|
|
1178
|
+
return this.post("/v1/artifacts", upload);
|
|
1169
1179
|
}
|
|
1170
1180
|
};
|
|
1171
1181
|
|
|
@@ -1277,7 +1287,6 @@ var EXIT_BY_CODE = {
|
|
|
1277
1287
|
MANIFEST_SCHEMA_INVALID: EXIT.validation,
|
|
1278
1288
|
MANIFEST_POLICY_VIOLATION: EXIT.policy,
|
|
1279
1289
|
MANIFEST_UNSUPPORTED_API_VERSION: EXIT.validation,
|
|
1280
|
-
APPROVAL_REQUIRED: EXIT.policy,
|
|
1281
1290
|
PLAN_STALE: EXIT.conflict,
|
|
1282
1291
|
PLAN_EXPIRED: EXIT.conflict,
|
|
1283
1292
|
TARGET_INCOMPATIBLE: EXIT.validation,
|
|
@@ -1388,7 +1397,7 @@ var USAGE = `tiny \u2014 governed runtime for small internal apps
|
|
|
1388
1397
|
tiny init [name] Write a starter tiny.yaml
|
|
1389
1398
|
tiny validate Parse and check tiny.yaml
|
|
1390
1399
|
tiny plan [--env production] Show what a deploy would do (never mutates)
|
|
1391
|
-
tiny deploy [--env production]
|
|
1400
|
+
tiny deploy [--env production] Deploy and watch it finish
|
|
1392
1401
|
[--no-wait] [--wait <seconds>]
|
|
1393
1402
|
tiny status Show apps and their lifecycle status
|
|
1394
1403
|
tiny logs [app] [--since 1h] [--limit 100] Read bounded logs
|
|
@@ -1397,12 +1406,13 @@ var USAGE = `tiny \u2014 governed runtime for small internal apps
|
|
|
1397
1406
|
|
|
1398
1407
|
tiny access list|grant|revoke <subject> [--role user]
|
|
1399
1408
|
tiny secrets set <name>|list
|
|
1400
|
-
tiny capabilities list|
|
|
1409
|
+
tiny capabilities list|grant <operation> --connection <name>
|
|
1401
1410
|
tiny preview create [--ttl 72h]|delete <environment>
|
|
1402
1411
|
|
|
1403
1412
|
tiny archive [app] Snapshot and deactivate
|
|
1404
1413
|
tiny restore [app] Reactivate an archived app
|
|
1405
|
-
tiny delete [app] --confirm <slug>
|
|
1414
|
+
tiny delete [app] --confirm <slug> [--grace 7d]
|
|
1415
|
+
Schedule deletion (--grace 0 deletes now)
|
|
1406
1416
|
tiny doctor Check auth, manifest files, and target reachability
|
|
1407
1417
|
|
|
1408
1418
|
Global flags: --json --api <url> --token <token> --cwd <path> --idempotency-key <key>
|
|
@@ -1628,21 +1638,12 @@ Service account token (shown once): ${created.apiToken}`);
|
|
|
1628
1638
|
environment: typeof args.flags.env === "string" ? args.flags.env : void 0
|
|
1629
1639
|
});
|
|
1630
1640
|
if (args.flags.json !== true) printPlan(output, planned.plan, planned.warnings);
|
|
1631
|
-
if (planned.plan.approvals.length > 0 && args.flags.yes !== true && !isInteractive()) {
|
|
1632
|
-
throw new TinyError("APPROVAL_REQUIRED", "This deployment needs approval and no terminal is attached.", {
|
|
1633
|
-
details: { approvals: planned.plan.approvals, planId: planned.plan.planId, nextActions: [
|
|
1634
|
-
"Ask an organization admin to approve the plan.",
|
|
1635
|
-
"Re-run `tiny deploy --yes` once approved."
|
|
1636
|
-
] }
|
|
1637
|
-
});
|
|
1638
|
-
}
|
|
1639
1641
|
output.progress("Deploying\u2026");
|
|
1640
1642
|
const result = await client.post("/v1/apps:deploy", {
|
|
1641
1643
|
artifactUri: artifact.artifactUri,
|
|
1642
1644
|
manifest: source,
|
|
1643
1645
|
environment: typeof args.flags.env === "string" ? args.flags.env : void 0,
|
|
1644
|
-
planId: planned.plan.planId
|
|
1645
|
-
approve: args.flags.yes === true
|
|
1646
|
+
planId: planned.plan.planId
|
|
1646
1647
|
}, typeof args.flags["idempotency-key"] === "string" ? args.flags["idempotency-key"] : void 0);
|
|
1647
1648
|
const settled = args.flags["no-wait"] === true ? null : await awaitDeployment(
|
|
1648
1649
|
() => client.get(`/v1/deployments/${result.deploymentId}`),
|
|
@@ -1812,10 +1813,10 @@ Service account token (shown once): ${created.apiToken}`);
|
|
|
1812
1813
|
});
|
|
1813
1814
|
return;
|
|
1814
1815
|
}
|
|
1815
|
-
if (args.subcommand === "
|
|
1816
|
+
if (args.subcommand === "grant") {
|
|
1816
1817
|
const operation = args.positional[0];
|
|
1817
1818
|
const connection = typeof args.flags.connection === "string" ? args.flags.connection : null;
|
|
1818
|
-
if (!operation || !connection) throw usageError("tiny capabilities
|
|
1819
|
+
if (!operation || !connection) throw usageError("tiny capabilities grant <operation> --connection <name>");
|
|
1819
1820
|
const appRef = typeof args.flags.app === "string" ? args.flags.app : parseManifest(readManifest(cwd).source).normalized.metadata.name;
|
|
1820
1821
|
const app = await resolveApp(client, appRef);
|
|
1821
1822
|
const grant = await client.post(`/v1/apps/${app.id}/capability-grants`, {
|
|
@@ -1823,12 +1824,11 @@ Service account token (shown once): ${created.apiToken}`);
|
|
|
1823
1824
|
operations: [operation]
|
|
1824
1825
|
});
|
|
1825
1826
|
output.result({ grantId: grant.id, status: grant.status, operation }, () => {
|
|
1826
|
-
output.step(
|
|
1827
|
-
if (grant.status === "pending") output.progress("An organization admin must approve it before the app can call the operation.");
|
|
1827
|
+
output.step(true, `${appRef} can now ${operation} on ${connection}.`);
|
|
1828
1828
|
});
|
|
1829
1829
|
return;
|
|
1830
1830
|
}
|
|
1831
|
-
throw usageError("tiny capabilities list|
|
|
1831
|
+
throw usageError("tiny capabilities list|grant");
|
|
1832
1832
|
}
|
|
1833
1833
|
// --------------------------------------------------------- preview
|
|
1834
1834
|
case "preview": {
|
|
@@ -1884,10 +1884,11 @@ Service account token (shown once): ${created.apiToken}`);
|
|
|
1884
1884
|
details: { missingInput: "--confirm", expected: app.slug }
|
|
1885
1885
|
});
|
|
1886
1886
|
}
|
|
1887
|
-
const
|
|
1887
|
+
const grace = typeof args.flags.grace === "string" ? args.flags.grace : void 0;
|
|
1888
|
+
const scheduled = await client.del(`/v1/apps/${app.id}`, { confirm, grace });
|
|
1888
1889
|
output.result(
|
|
1889
|
-
{ appId: app.id, deleteAfter: scheduled.deleteAfter },
|
|
1890
|
-
() => output.step(true, `${app.slug} will be deleted after ${scheduled.deleteAfter}.`)
|
|
1890
|
+
{ appId: app.id, status: scheduled.status, deleteAfter: scheduled.deleteAfter },
|
|
1891
|
+
() => output.step(true, scheduled.status === "deleted" ? `${app.slug} deleted.` : `${app.slug} will be deleted after ${scheduled.deleteAfter}. Run "tiny restore ${app.slug}" to cancel.`)
|
|
1891
1892
|
);
|
|
1892
1893
|
return;
|
|
1893
1894
|
}
|
|
@@ -1922,7 +1923,7 @@ function printPlan(output, plan, warnings) {
|
|
|
1922
1923
|
output.progress(` risk: ${plan.riskTier}${plan.estimatedMonthlyCents === null ? "" : ` estimate: $${(plan.estimatedMonthlyCents / 100).toFixed(2)}/mo`}`);
|
|
1923
1924
|
for (const warning of [...warnings, ...plan.warnings]) output.warn(warning);
|
|
1924
1925
|
for (const problem of plan.incompatibilities) output.warn(`incompatible: ${problem}`);
|
|
1925
|
-
for (const
|
|
1926
|
+
for (const notice of plan.notices) output.warn(notice.detail);
|
|
1926
1927
|
output.progress("");
|
|
1927
1928
|
}
|
|
1928
1929
|
function usageError(usage) {
|
|
@@ -1940,4 +1941,3 @@ function hoursFrom(value) {
|
|
|
1940
1941
|
return Number(match[1]) * (match[2] === "d" ? 24 : 1);
|
|
1941
1942
|
}
|
|
1942
1943
|
await main();
|
|
1943
|
-
//# sourceMappingURL=main.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinycloud-ai/tinycloud-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Command-line client for deploying and managing governed, isolated internal apps on Tinycloud.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -22,15 +22,6 @@
|
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
|
-
"repository": {
|
|
26
|
-
"type": "git",
|
|
27
|
-
"url": "git+https://github.com/scar-ai/tinycloud.git",
|
|
28
|
-
"directory": "tools/npm-cli"
|
|
29
|
-
},
|
|
30
|
-
"bugs": {
|
|
31
|
-
"url": "https://github.com/scar-ai/tinycloud/issues"
|
|
32
|
-
},
|
|
33
|
-
"homepage": "https://github.com/scar-ai/tinycloud#readme",
|
|
34
25
|
"keywords": [
|
|
35
26
|
"tinycloud",
|
|
36
27
|
"cli",
|
package/dist/main.js.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../cli/src/main.ts", "../../../packages/domain/src/ids.ts", "../../../packages/domain/src/errors.ts", "../../../packages/domain/src/duration.ts", "../../../packages/domain/src/entities.ts", "../../../packages/manifest-schema/src/index.ts", "../../../packages/manifest-schema/src/jsonschema.ts", "../../../packages/manifest-schema/src/types.ts", "../../../packages/manifest-schema/src/normalize.ts", "../../../packages/manifest-schema/src/semantic.ts", "../../../packages/manifest-schema/src/yaml.ts", "../../../packages/orchestrator/src/artifacts.ts", "../../../packages/resource-sqlite/src/index.ts", "../../../packages/runtime-provider/src/contract.ts", "../../../packages/orchestrator/src/planner.ts", "../../../packages/auth/src/tokens.ts", "../../../packages/orchestrator/src/data.ts", "../../../packages/orchestrator/src/storage.ts", "../../../packages/db/src/store.ts", "../../../packages/db/src/sqlite.ts", "../../cli/src/client.ts", "../../cli/src/config.ts", "../../cli/src/deployments.ts", "../../cli/src/output.ts"],
|
|
4
|
-
"sourcesContent": ["#!/usr/bin/env node\n/**\n * The `tiny` CLI (blueprint \u00A719).\n *\n * Design rules this file exists to keep:\n * - `--json` produces one versioned object on stdout, progress on stderr;\n * - nothing prompts when stdin is not a TTY \u2014 it returns INTERACTION_REQUIRED\n * with the flag that would have satisfied it;\n * - `plan` never mutates;\n * - every mutation accepts an idempotency key.\n */\nimport { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { basename, join, resolve } from 'node:path';\nimport { createInterface } from 'node:readline/promises';\nimport { TinyError } from '@tinycloud/domain';\nimport { parseManifest, starterManifest } from '@tinycloud/manifest-schema';\nimport { ApiClient } from './client.ts';\nimport { clearProfile, currentProfile, setProfile } from './config.ts';\nimport {\n DEFAULT_WAIT_MS, awaitDeployment, buildSummary, describeBuild, describeFailure, failureRemediation,\n type DeploymentRecord,\n} from './deployments.ts';\nimport { EXIT, Output, isInteractive } from './output.ts';\n\ninterface Args {\n command: string;\n subcommand: string | null;\n positional: string[];\n flags: Record<string, string | boolean>;\n}\n\nfunction parseArgs(argv: string[]): Args {\n const positional: string[] = [];\n const flags: Record<string, string | boolean> = {};\n for (let index = 0; index < argv.length; index++) {\n const token = argv[index]!;\n if (token.startsWith('--')) {\n const [name, inline] = token.slice(2).split('=');\n if (inline !== undefined) flags[name!] = inline;\n else if (argv[index + 1] && !argv[index + 1]!.startsWith('-')) flags[name!] = argv[++index]!;\n else flags[name!] = true;\n } else {\n positional.push(token);\n }\n }\n const [command = 'help', ...rest] = positional;\n const grouped = new Set(['access', 'secrets', 'capabilities', 'preview', 'org']);\n return {\n command,\n subcommand: grouped.has(command) ? (rest[0] ?? null) : null,\n positional: grouped.has(command) ? rest.slice(1) : rest,\n flags,\n };\n}\n\nconst USAGE = `tiny \u2014 governed runtime for small internal apps\n\n tiny login --api <url> [--token <token>] Store credentials for a control plane\n tiny logout Remove stored credentials\n tiny whoami Show the current identity\n tiny org create --slug <slug> --owner <email>\n Bootstrap an organization (returns an API token)\n\n tiny init [name] Write a starter tiny.yaml\n tiny validate Parse and check tiny.yaml\n tiny plan [--env production] Show what a deploy would do (never mutates)\n tiny deploy [--env production] [--yes] Deploy and watch it finish\n [--no-wait] [--wait <seconds>]\n tiny status Show apps and their lifecycle status\n tiny logs [app] [--since 1h] [--limit 100] Read bounded logs\n tiny open [app] Print the app URL\n tiny rollback <deployment> Repoint traffic at a previous deployment\n\n tiny access list|grant|revoke <subject> [--role user]\n tiny secrets set <name>|list\n tiny capabilities list|request <operation> --connection <name>\n tiny preview create [--ttl 72h]|delete <environment>\n\n tiny archive [app] Snapshot and deactivate\n tiny restore [app] Reactivate an archived app\n tiny delete [app] --confirm <slug> Schedule deletion\n tiny doctor Check auth, manifest files, and target reachability\n\nGlobal flags: --json --api <url> --token <token> --cwd <path> --idempotency-key <key>\n\nExit codes: 0 ok, 2 usage, 3 auth, 4 validation, 5 policy, 6 not found,\n 7 interaction required, 8 conflict, 9 unavailable.\n`;\n\nasync function main(): Promise<void> {\n const args = parseArgs(process.argv.slice(2));\n const output = new Output({ json: args.flags.json === true || args.flags.json === 'true' });\n\n try {\n await dispatch(args, output);\n } catch (error) {\n output.fail(error);\n }\n}\n\nfunction cwdOf(args: Args): string {\n return resolve(typeof args.flags.cwd === 'string' ? args.flags.cwd : process.cwd());\n}\n\nfunction readManifest(cwd: string): { source: string; path: string } {\n const path = join(cwd, 'tiny.yaml');\n if (!existsSync(path)) {\n throw new TinyError('NOT_FOUND', 'No tiny.yaml in this directory.', {\n remediation: 'Run `tiny init` to create one, or pass --cwd with the app directory.',\n details: { expected: path },\n });\n }\n return { source: readFileSync(path, 'utf8'), path };\n}\n\nfunction clientFor(args: Args): ApiClient {\n const profile = currentProfile();\n const apiUrl = (typeof args.flags.api === 'string' ? args.flags.api : undefined)\n ?? process.env.TINY_API_URL\n ?? profile?.apiUrl;\n const token = (typeof args.flags.token === 'string' ? args.flags.token : undefined)\n ?? process.env.TINY_TOKEN\n ?? profile?.token;\n\n if (!apiUrl) {\n throw new TinyError('INTERACTION_REQUIRED', 'No control plane configured.', {\n remediation: 'Run `tiny login --api <url> --token <token>`, or pass --api.',\n details: { missingInput: '--api' },\n });\n }\n if (!token) {\n throw new TinyError('UNAUTHENTICATED', 'No credentials for this control plane.', {\n remediation: 'Run `tiny login --api <url> --token <token>`.',\n details: { missingInput: '--token' },\n });\n }\n return new ApiClient({ apiUrl, token });\n}\n\n/** Prompt only when a human is present; otherwise say what flag to pass. */\nasync function promptSecret(label: string, missingInput: string): Promise<string> {\n if (!isInteractive()) {\n if (!process.stdin.isTTY) {\n const piped = readFileSync(0, 'utf8').trim();\n if (piped) return piped;\n }\n throw new TinyError('INTERACTION_REQUIRED', `${label} is required and stdin is not a terminal.`, {\n remediation: `Pipe the value on stdin, or pass ${missingInput}.`,\n details: { missingInput },\n });\n }\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n try {\n return (await rl.question(`${label}: `)).trim();\n } finally {\n rl.close();\n }\n}\n\ninterface DeployResponse {\n appId: string;\n deploymentId: string;\n status: string;\n url: string;\n planId: string;\n warnings: string[];\n}\n\nfunction waitTimeoutMs(args: Args): number {\n const flag = args.flags.wait;\n if (typeof flag !== 'string') return DEFAULT_WAIT_MS;\n const seconds = Number(flag);\n if (!Number.isFinite(seconds) || seconds <= 0) throw usageError('tiny deploy --wait <seconds>');\n return seconds * 1000;\n}\n\nasync function dispatch(args: Args, output: Output): Promise<void> {\n const cwd = cwdOf(args);\n\n switch (args.command) {\n case 'help':\n case '--help':\n process.stderr.write(USAGE);\n process.exit(args.command === 'help' ? EXIT.ok : EXIT.usage);\n return;\n\n // ------------------------------------------------------------- auth\n case 'login': {\n const apiUrl = typeof args.flags.api === 'string' ? args.flags.api : process.env.TINY_API_URL;\n if (!apiUrl) {\n throw new TinyError('INTERACTION_REQUIRED', 'A control plane URL is required.', {\n remediation: 'Pass --api <url>.', details: { missingInput: '--api' },\n });\n }\n const token = typeof args.flags.token === 'string'\n ? args.flags.token\n : await promptSecret('API token', '--token');\n const profile = typeof args.flags.profile === 'string' ? args.flags.profile : 'default';\n setProfile(profile, { apiUrl, token });\n output.result({ profile, apiUrl }, () => output.step(true, `Credentials stored for ${apiUrl} (profile \"${profile}\").`));\n return;\n }\n\n case 'logout': {\n clearProfile(typeof args.flags.profile === 'string' ? args.flags.profile : undefined);\n output.result({ loggedOut: true }, () => output.step(true, 'Credentials removed.'));\n return;\n }\n\n case 'whoami': {\n const profile = currentProfile();\n if (!profile) {\n throw new TinyError('UNAUTHENTICATED', 'Not logged in.', { remediation: 'Run `tiny login --api <url>`.' });\n }\n const capabilities = await clientFor(args).get<Record<string, unknown>>('/v1/platform/capabilities');\n output.result({ apiUrl: profile.apiUrl, platform: capabilities }, () => {\n output.step(true, `Control plane: ${profile.apiUrl}`);\n });\n return;\n }\n\n case 'org': {\n if (args.subcommand !== 'create') throw usageError('tiny org create --slug <slug> --owner <email>');\n const slug = String(args.flags.slug ?? '');\n const owner = String(args.flags.owner ?? '');\n if (!slug || !owner) throw usageError('tiny org create --slug <slug> --owner <email>');\n const apiUrl = (typeof args.flags.api === 'string' ? args.flags.api : undefined) ?? process.env.TINY_API_URL;\n if (!apiUrl) throw usageError('tiny org create --api <url> --slug <slug> --owner <email>');\n const created = await new ApiClient({ apiUrl }).post<{ apiToken: string; sessionToken: string; organization: { id: string; slug: string } }>(\n '/v1/organizations', { slug, displayName: String(args.flags.name ?? slug), ownerEmail: owner });\n setProfile('default', { apiUrl, token: created.sessionToken, organizationSlug: slug, email: owner });\n output.result(\n { organizationId: created.organization.id, slug: created.organization.slug, apiToken: created.apiToken },\n () => {\n output.step(true, `Organization ${created.organization.slug} created.`);\n output.step(true, 'Credentials stored for this shell.');\n output.progress(`\\nService account token (shown once): ${created.apiToken}`);\n },\n );\n return;\n }\n\n // --------------------------------------------------------- manifest\n case 'init': {\n const name = args.positional[0] ?? basename(cwd);\n const owner = String(args.flags.owner ?? currentProfile()?.email ?? 'you@example.com');\n const path = join(cwd, 'tiny.yaml');\n if (existsSync(path) && args.flags.force !== true) {\n throw new TinyError('CONFLICT', 'tiny.yaml already exists.', {\n remediation: 'Pass --force to overwrite it.', details: { path },\n });\n }\n writeFileSync(path, starterManifest(name, owner));\n output.result({ path, name }, () => output.step(true, `Wrote ${path}`));\n return;\n }\n\n case 'validate': {\n const { source } = readManifest(cwd);\n const parsed = parseManifest(source);\n output.result(\n { name: parsed.normalized.metadata.name, manifestSha256: parsed.sha256, warnings: parsed.warnings },\n () => {\n output.step(true, `tiny.yaml is valid (${parsed.normalized.metadata.name}).`);\n for (const warning of parsed.warnings) output.warn(warning);\n },\n );\n return;\n }\n\n case 'doctor': {\n const checks: Array<{ name: string; ok: boolean; detail: string }> = [];\n const profile = currentProfile();\n checks.push({ name: 'credentials', ok: Boolean(profile), detail: profile ? `stored for ${profile.apiUrl}` : 'run `tiny login`' });\n\n let manifestOk = false;\n let detail = 'no tiny.yaml in this directory';\n try {\n const parsed = parseManifest(readManifest(cwd).source);\n manifestOk = true;\n detail = `${parsed.normalized.metadata.name} (${parsed.normalized.runtime.type}/${parsed.normalized.runtime.language})`;\n const entry = join(cwd, parsed.normalized.runtime.entrypoint);\n checks.push({ name: 'entrypoint', ok: existsSync(entry), detail: parsed.normalized.runtime.entrypoint });\n if (parsed.normalized.resources.database) {\n const migrations = join(cwd, parsed.normalized.resources.database.migrations);\n checks.push({ name: 'migrations', ok: existsSync(migrations), detail: parsed.normalized.resources.database.migrations });\n }\n } catch (error) {\n detail = error instanceof Error ? error.message : String(error);\n }\n checks.unshift({ name: 'manifest', ok: manifestOk, detail });\n\n for (const risky of ['.env', '.env.local', 'credentials.json']) {\n if (existsSync(join(cwd, risky))) {\n checks.push({ name: `ignored:${risky}`, ok: true, detail: 'present locally, excluded from uploads' });\n }\n }\n\n if (profile) {\n try {\n await clientFor(args).get('/v1/platform/capabilities');\n checks.push({ name: 'control plane', ok: true, detail: profile.apiUrl });\n } catch (error) {\n checks.push({ name: 'control plane', ok: false, detail: (error as Error).message });\n }\n }\n\n const ok = checks.every((check) => check.ok);\n output.result({ checks, healthy: ok }, () => {\n for (const check of checks) output.step(check.ok, `${check.name}: ${check.detail}`);\n });\n if (!ok) process.exit(EXIT.validation);\n return;\n }\n\n // ------------------------------------------------------ plan/deploy\n case 'plan': {\n const { source } = readManifest(cwd);\n const client = clientFor(args);\n const artifact = await client.uploadDirectory(cwd);\n const response = await client.post<{ plan: PlanShape; warnings: string[] }>('/v1/apps:plan', {\n artifactUri: artifact.artifactUri,\n manifest: source,\n environment: typeof args.flags.env === 'string' ? args.flags.env : undefined,\n });\n output.result({ plan: response.plan, warnings: response.warnings }, () => printPlan(output, response.plan, response.warnings));\n return;\n }\n\n case 'deploy': {\n const { source } = readManifest(cwd);\n const client = clientFor(args);\n output.progress('Planning\u2026');\n const artifact = await client.uploadDirectory(cwd);\n const planned = await client.post<{ plan: PlanShape; warnings: string[] }>('/v1/apps:plan', {\n artifactUri: artifact.artifactUri, manifest: source,\n environment: typeof args.flags.env === 'string' ? args.flags.env : undefined,\n });\n if (args.flags.json !== true) printPlan(output, planned.plan, planned.warnings);\n\n if (planned.plan.approvals.length > 0 && args.flags.yes !== true && !isInteractive()) {\n throw new TinyError('APPROVAL_REQUIRED', 'This deployment needs approval and no terminal is attached.', {\n details: { approvals: planned.plan.approvals, planId: planned.plan.planId, nextActions: [\n 'Ask an organization admin to approve the plan.',\n 'Re-run `tiny deploy --yes` once approved.',\n ] },\n });\n }\n\n output.progress('Deploying\u2026');\n const result = await client.post<DeployResponse>('/v1/apps:deploy', {\n artifactUri: artifact.artifactUri,\n manifest: source,\n environment: typeof args.flags.env === 'string' ? args.flags.env : undefined,\n planId: planned.plan.planId,\n approve: args.flags.yes === true,\n }, typeof args.flags['idempotency-key'] === 'string' ? args.flags['idempotency-key'] : undefined);\n\n // The API accepted the deployment; the build and rollout happen after.\n // Watch them unless the caller asked not to, so that what the command\n // prints is what actually happened rather than what was requested.\n const settled: DeploymentRecord | null = args.flags['no-wait'] === true\n ? null\n : await awaitDeployment(\n () => client.get<DeploymentRecord>(`/v1/deployments/${result.deploymentId}`),\n { timeoutMs: waitTimeoutMs(args), onStatus: (status) => output.progress(` ${status}\u2026`) },\n );\n const status = settled?.status ?? result.status;\n const build = buildSummary(settled?.providerState);\n\n output.result(\n {\n appId: result.appId, deploymentId: result.deploymentId, status, url: result.url,\n warnings: result.warnings,\n ...(build ? { build } : {}),\n },\n () => {\n output.progress('');\n const built = describeBuild(build);\n if (built) output.step(true, built);\n if (settled === null) {\n // Either we were told not to watch, or we stopped watching. Both\n // leave the deployment running, so neither is a \u2713 or an \u2717.\n output.progress(args.flags['no-wait'] === true\n ? `\u2192 Deployment ${result.deploymentId} is ${status}. Follow it with \\`tiny logs\\`.`\n : `\u2192 Deployment ${result.deploymentId} is still running after ${Math.round(waitTimeoutMs(args) / 1000)}s. Follow it with \\`tiny logs\\`.`);\n } else {\n output.step(status === 'ready', `Deployment ${result.deploymentId} is ${status}`);\n if (settled.errorCode) output.warn(`${settled.errorCode}: ${describeFailure(settled)}`);\n }\n output.step(true, `URL: ${result.url}`);\n for (const warning of result.warnings) output.warn(warning);\n },\n );\n // A deployment that failed must not exit 0, or CI will deploy over it.\n if (status === 'failed') {\n throw new TinyError('PROVIDER_UNAVAILABLE', `Deployment ${result.deploymentId} failed: ${describeFailure(settled)}`, {\n // The deployer recorded why and what to do about it; repeating its\n // own remediation beats a generic pointer at the logs.\n remediation: failureRemediation(settled),\n details: {\n deploymentId: result.deploymentId,\n ...(settled?.errorCode ? { errorCode: settled.errorCode } : {}),\n ...(settled?.errorDetail ? { errorDetail: settled.errorDetail } : {}),\n },\n });\n }\n return;\n }\n\n case 'rollback': {\n const deploymentId = args.positional[0];\n if (!deploymentId) throw usageError('tiny rollback <deployment>');\n const result = await clientFor(args).post<{ deployment: { id: string; status: string }; schemaWarning: string | null }>(\n `/v1/deployments/${deploymentId}/rollback`);\n output.result({ deploymentId: result.deployment.id, status: result.deployment.status, schemaWarning: result.schemaWarning }, () => {\n output.step(true, `Traffic now served by ${result.deployment.id}`);\n if (result.schemaWarning) output.warn(result.schemaWarning);\n });\n return;\n }\n\n // --------------------------------------------------------- inspect\n case 'status': {\n const client = clientFor(args);\n const apps = await client.get<{ items: AppShape[] }>('/v1/organizations/self/apps');\n output.result({ apps: apps.items }, () => {\n if (apps.items.length === 0) { output.progress('No apps yet. Run `tiny deploy`.'); return; }\n for (const app of apps.items) {\n output.progress(`${app.slug.padEnd(28)} ${app.status.padEnd(14)} risk=${app.riskTier} last-used=${app.lastAccessedAt ?? 'never'}`);\n }\n });\n return;\n }\n\n case 'logs': {\n const client = clientFor(args);\n const appRef = args.positional[0] ?? parseManifest(readManifest(cwd).source).normalized.metadata.name;\n const app = await resolveApp(client, appRef);\n const deployments = await client.get<{ items: Array<{ id: string }> }>(`/v1/apps/${app.id}/deployments`, { limit: 1 });\n const deployment = deployments.items[0];\n if (!deployment) throw new TinyError('NOT_FOUND', `App ${appRef} has no deployments yet.`);\n const logs = await client.get<{ items: Array<{ timestamp: string; level: string; message: string }> }>(\n `/v1/deployments/${deployment.id}/logs`, {\n limit: Number(args.flags.limit ?? 100),\n since: typeof args.flags.since === 'string' ? sinceToIso(args.flags.since) : undefined,\n });\n output.result({ appId: app.id, deploymentId: deployment.id, logs: logs.items }, () => {\n for (const event of logs.items) {\n process.stdout.write(`${event.timestamp} ${event.level.padEnd(5)} ${event.message}\\n`);\n }\n });\n return;\n }\n\n case 'open': {\n const client = clientFor(args);\n const appRef = args.positional[0] ?? parseManifest(readManifest(cwd).source).normalized.metadata.name;\n const app = await resolveApp(client, appRef);\n const environments = await client.get<{ items: Array<{ name: string; hostname: string }> }>(`/v1/apps/${app.id}/environments`);\n const environment = environments.items.find((entry) => entry.name === (args.flags.env ?? 'production')) ?? environments.items[0];\n if (!environment) throw new TinyError('NOT_FOUND', `App ${app.slug} has no environments yet.`);\n const url = `https://${environment.hostname}`;\n output.result({ appId: app.id, url, environment: environment.name }, () => process.stdout.write(`${url}\\n`));\n return;\n }\n\n // ---------------------------------------------------------- access\n case 'access': {\n const client = clientFor(args);\n const appRef = typeof args.flags.app === 'string'\n ? args.flags.app\n : parseManifest(readManifest(cwd).source).normalized.metadata.name;\n const app = await resolveApp(client, appRef);\n\n if (args.subcommand === 'list' || args.subcommand === null) {\n const bindings = await client.get<{ items: Array<{ subject: string; role: string; subjectType: string }> }>(`/v1/apps/${app.id}/access`);\n output.result({ appId: app.id, bindings: bindings.items }, () => {\n for (const binding of bindings.items) output.progress(`${binding.subject.padEnd(36)} ${binding.role} (${binding.subjectType})`);\n });\n return;\n }\n if (args.subcommand === 'grant') {\n const subject = args.positional[0];\n if (!subject) throw usageError('tiny access grant <subject> [--role user]');\n const binding = await client.put<{ subject: string; role: string }>(\n `/v1/apps/${app.id}/access/${encodeURIComponent(subject)}`,\n { role: typeof args.flags.role === 'string' ? args.flags.role : 'user' });\n output.result({ appId: app.id, subject: binding.subject, role: binding.role },\n () => output.step(true, `${binding.subject} granted ${binding.role}`));\n return;\n }\n if (args.subcommand === 'revoke') {\n const subject = args.positional[0];\n if (!subject) throw usageError('tiny access revoke <subject>');\n await client.del(`/v1/apps/${app.id}/access/${encodeURIComponent(subject)}`);\n output.result({ appId: app.id, subject, revoked: true }, () => output.step(true, `${subject} revoked`));\n return;\n }\n throw usageError('tiny access list|grant|revoke');\n }\n\n // --------------------------------------------------------- secrets\n case 'secrets': {\n const client = clientFor(args);\n if (args.subcommand === 'list') {\n const secrets = await client.get<{ items: Array<{ name: string; createdAt: string }> }>('/v1/organizations/self/secrets');\n output.result({ secrets: secrets.items }, () => {\n for (const secret of secrets.items) output.progress(`${secret.name.padEnd(32)} ${secret.createdAt}`);\n });\n return;\n }\n if (args.subcommand === 'set') {\n const name = args.positional[0];\n if (!name) throw usageError('tiny secrets set <name>');\n // Never accepted as an argument: process lists are readable (\u00A719.1).\n const value = await promptSecret(`Value for ${name}`, '--stdin');\n const result = await client.post<{ secretId: string; version: number }>('/v1/organizations/self/secrets', { name, value });\n output.result({ name, version: result.version }, () => output.step(true, `Secret ${name} stored as version ${result.version}`));\n return;\n }\n throw usageError('tiny secrets set|list');\n }\n\n // ---------------------------------------------------- capabilities\n case 'capabilities': {\n const client = clientFor(args);\n if (args.subcommand === 'list' || args.subcommand === null) {\n const catalog = await client.get<{ items: Array<{ id: string; risk: string; version: string }> }>('/v1/capabilities');\n output.result({ capabilities: catalog.items }, () => {\n for (const operation of catalog.items) output.progress(`${operation.id.padEnd(30)} ${operation.risk.padEnd(12)} ${operation.version}`);\n });\n return;\n }\n if (args.subcommand === 'request') {\n const operation = args.positional[0];\n const connection = typeof args.flags.connection === 'string' ? args.flags.connection : null;\n if (!operation || !connection) throw usageError('tiny capabilities request <operation> --connection <name>');\n const appRef = typeof args.flags.app === 'string'\n ? args.flags.app\n : parseManifest(readManifest(cwd).source).normalized.metadata.name;\n const app = await resolveApp(client, appRef);\n const grant = await client.post<{ id: string; status: string }>(`/v1/apps/${app.id}/capability-grants`, {\n connection, operations: [operation],\n });\n output.result({ grantId: grant.id, status: grant.status, operation }, () => {\n output.step(grant.status === 'approved', `Grant ${grant.id} is ${grant.status}`);\n if (grant.status === 'pending') output.progress('An organization admin must approve it before the app can call the operation.');\n });\n return;\n }\n throw usageError('tiny capabilities list|request');\n }\n\n // --------------------------------------------------------- preview\n case 'preview': {\n const client = clientFor(args);\n if (args.subcommand === 'create') {\n const { source } = readManifest(cwd);\n const artifact = await client.uploadDirectory(cwd);\n const result = await client.post<{ deploymentId: string; url: string; status: string }>('/v1/apps:preview', {\n artifactUri: artifact.artifactUri, manifest: source,\n name: typeof args.flags.name === 'string' ? args.flags.name : undefined,\n ttlHours: args.flags.ttl ? hoursFrom(String(args.flags.ttl)) : undefined,\n });\n output.result(result, () => output.step(true, `Preview ready at ${result.url}`));\n return;\n }\n if (args.subcommand === 'delete') {\n const environmentId = args.positional[0];\n if (!environmentId) throw usageError('tiny preview delete <environment>');\n await client.del(`/v1/environments/${environmentId}`);\n output.result({ environmentId, deleted: true }, () => output.step(true, `Preview ${environmentId} deleted`));\n return;\n }\n throw usageError('tiny preview create|delete');\n }\n\n // ------------------------------------------------------- lifecycle\n case 'archive':\n case 'restore':\n case 'delete': {\n const client = clientFor(args);\n const appRef = args.positional[0] ?? parseManifest(readManifest(cwd).source).normalized.metadata.name;\n const app = await resolveApp(client, appRef);\n\n if (args.command === 'archive') {\n const result = await client.post<{ archived: boolean; snapshots: number }>(`/v1/apps/${app.id}/archive`);\n output.result({ appId: app.id, ...result },\n () => output.step(true, `${app.slug} archived with ${result.snapshots} snapshot(s).`));\n return;\n }\n if (args.command === 'restore') {\n const restored = await client.post<AppShape>(`/v1/apps/${app.id}/restore`);\n output.result({ appId: app.id, status: restored.status },\n () => output.step(true, `${app.slug} restored. Deploy again to bring it back online.`));\n return;\n }\n\n const confirm = typeof args.flags.confirm === 'string' ? args.flags.confirm : null;\n if (!confirm) {\n throw new TinyError('DELETE_CONFIRMATION_REQUIRED', `Deleting \"${app.slug}\" requires confirmation.`, {\n remediation: `Re-run with --confirm ${app.slug}.`,\n details: { missingInput: '--confirm', expected: app.slug },\n });\n }\n const scheduled = await client.del<AppShape>(`/v1/apps/${app.id}`, { confirm });\n output.result({ appId: app.id, deleteAfter: scheduled.deleteAfter },\n () => output.step(true, `${app.slug} will be deleted after ${scheduled.deleteAfter}.`));\n return;\n }\n\n default:\n process.stderr.write(`Unknown command \"${args.command}\".\\n\\n${USAGE}`);\n process.exit(EXIT.usage);\n }\n}\n\ninterface AppShape {\n id: string;\n slug: string;\n status: string;\n riskTier: string;\n lastAccessedAt: string | null;\n deleteAfter?: string | null;\n}\n\ninterface PlanShape {\n planId: string;\n steps: Array<{ kind: string; resource: string; detail: string }>;\n migrations: { pending: string[]; destructive: string[] };\n capabilities: Array<{ operation: string; status: string }>;\n approvals: Array<{ code: string; detail: string }>;\n riskTier: string;\n estimatedMonthlyCents: number | null;\n warnings: string[];\n incompatibilities: string[];\n}\n\nasync function resolveApp(client: ApiClient, reference: string): Promise<AppShape> {\n if (reference.startsWith('app_')) return client.get<AppShape>(`/v1/apps/${reference}`);\n const apps = await client.get<{ items: AppShape[] }>('/v1/organizations/self/apps');\n const app = apps.items.find((entry) => entry.slug === reference);\n if (!app) {\n throw new TinyError('NOT_FOUND', `No app \"${reference}\" in this organization.`, {\n remediation: 'Run `tiny status` to list apps.',\n });\n }\n return app;\n}\n\nfunction printPlan(output: Output, plan: PlanShape, warnings: string[]): void {\n output.progress('');\n for (const step of plan.steps) output.progress(` ${step.kind.padEnd(7)} ${step.resource.padEnd(34)} ${step.detail}`);\n if (plan.migrations.pending.length > 0) {\n output.progress(` migrations: ${plan.migrations.pending.join(', ')}`);\n for (const destructive of plan.migrations.destructive) output.warn(`${destructive} contains destructive statements.`);\n }\n for (const capability of plan.capabilities) {\n output.progress(` capability ${capability.operation} \u2192 ${capability.status}`);\n }\n output.progress(` risk: ${plan.riskTier}${plan.estimatedMonthlyCents === null ? '' : ` estimate: $${(plan.estimatedMonthlyCents / 100).toFixed(2)}/mo`}`);\n for (const warning of [...warnings, ...plan.warnings]) output.warn(warning);\n for (const problem of plan.incompatibilities) output.warn(`incompatible: ${problem}`);\n for (const approval of plan.approvals) output.warn(`approval required \u2014 ${approval.code}: ${approval.detail}`);\n output.progress('');\n}\n\nfunction usageError(usage: string): TinyError {\n return new TinyError('VALIDATION_FAILED', `Usage: ${usage}`, { remediation: `Run: ${usage}` });\n}\n\nfunction sinceToIso(value: string): string {\n const match = /^(\\d+)([smhd])$/.exec(value);\n if (!match) return value;\n const unit = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 }[match[2]!]!;\n return new Date(Date.now() - Number(match[1]) * unit).toISOString();\n}\n\nfunction hoursFrom(value: string): number {\n const match = /^(\\d+)([hd])$/.exec(value);\n if (!match) return Number(value);\n return Number(match[1]) * (match[2] === 'd' ? 24 : 1);\n}\n\nawait main();\n", "/**\n * Prefixed, lexicographically sortable IDs (blueprint \u00A77).\n *\n * Format: `<prefix>_<26-char Crockford base32 ULID>`. ULIDs sort by creation\n * time, which keeps cursor pagination and log scanning cheap without exposing\n * a sequential counter.\n */\nimport { randomBytes } from 'node:crypto';\n\nexport const ID_PREFIXES = [\n 'org', 'user', 'sa', 'mem', 'app', 'rev', 'dep', 'env', 'res', 'bind',\n 'cap', 'grant', 'conn', 'target', 'inst', 'job', 'step', 'evt', 'sec',\n 'ver', 'plan', 'req', 'mig', 'life', 'usage', 'access', 'hook', 'delivery',\n] as const;\n\nexport type IdPrefix = (typeof ID_PREFIXES)[number];\nexport type Id<P extends IdPrefix = IdPrefix> = `${P}_${string}`;\n\nconst CROCKFORD = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';\n\nlet lastTime = 0;\nlet lastRandom: Uint8Array = new Uint8Array(10);\n\nfunction encodeTime(ms: number): string {\n let out = '';\n for (let i = 9; i >= 0; i--) {\n out = CROCKFORD[ms % 32] + out;\n ms = Math.floor(ms / 32);\n }\n return out;\n}\n\nfunction encodeRandom(bytes: Uint8Array): string {\n // 10 bytes -> 16 base32 characters.\n let bits = 0n;\n for (const b of bytes) bits = (bits << 8n) | BigInt(b);\n let out = '';\n for (let i = 0; i < 16; i++) {\n out = CROCKFORD[Number(bits & 31n)] + out;\n bits >>= 5n;\n }\n return out;\n}\n\nfunction bumpRandom(): Uint8Array {\n // Monotonic within the same millisecond so IDs minted in a tight loop\n // still sort in creation order.\n const next = Uint8Array.from(lastRandom);\n for (let i = next.length - 1; i >= 0; i--) {\n if (next[i]! < 255) {\n next[i]!++;\n return next;\n }\n next[i] = 0;\n }\n return next;\n}\n\nexport function ulid(now = Date.now()): string {\n if (now === lastTime) {\n lastRandom = bumpRandom();\n } else {\n lastTime = now;\n lastRandom = Uint8Array.from(randomBytes(10));\n }\n return encodeTime(now) + encodeRandom(lastRandom);\n}\n\nexport function newId<P extends IdPrefix>(prefix: P, now = Date.now()): Id<P> {\n return `${prefix}_${ulid(now)}` as Id<P>;\n}\n\nexport function isId<P extends IdPrefix>(value: unknown, prefix: P): value is Id<P> {\n return typeof value === 'string' && new RegExp(`^${prefix}_[0-9A-HJKMNP-TV-Z]{26}$`).test(value);\n}\n\nexport function idPrefix(value: string): string | null {\n const i = value.indexOf('_');\n return i === -1 ? null : value.slice(0, i);\n}\n\n/** Time a ULID-based ID was minted, useful for cursor windows and debugging. */\nexport function idCreatedAt(value: string): Date | null {\n const body = value.slice(value.indexOf('_') + 1);\n if (body.length !== 26) return null;\n let ms = 0;\n for (const ch of body.slice(0, 10)) {\n const digit = CROCKFORD.indexOf(ch);\n if (digit === -1) return null;\n ms = ms * 32 + digit;\n }\n return new Date(ms);\n}\n", "/**\n * Every failure surfaces a stable code, a human message, agent remediation,\n * and retryability (blueprint \u00A715.5). Agents branch on `code`; humans read\n * `message`; both act on `remediation`.\n */\nexport const ERROR_CODES = {\n // Request/auth\n UNAUTHENTICATED: 401,\n AUTH_PROVIDER_REQUIRED: 501,\n FORBIDDEN: 403,\n NOT_FOUND: 404,\n CONFLICT: 409,\n PRECONDITION_FAILED: 412,\n VALIDATION_FAILED: 422,\n PAYLOAD_TOO_LARGE: 413,\n RATE_LIMITED: 429,\n INTERNAL: 500,\n // Manifest\n MANIFEST_PARSE_FAILED: 422,\n MANIFEST_SCHEMA_INVALID: 422,\n MANIFEST_POLICY_VIOLATION: 422,\n MANIFEST_UNSUPPORTED_API_VERSION: 422,\n // Planning / provider\n TARGET_INCOMPATIBLE: 422,\n APPROVAL_REQUIRED: 409,\n PLAN_EXPIRED: 409,\n PLAN_STALE: 409,\n PROVIDER_UNAVAILABLE: 503,\n BUILD_FAILED: 422,\n HEALTHCHECK_FAILED: 422,\n // State\n MIGRATION_CHECKSUM_CHANGED: 422,\n MIGRATION_FAILED: 422,\n MIGRATION_LOCKED: 409,\n RESOURCE_LIMIT_EXCEEDED: 422,\n // Lifecycle\n APP_ARCHIVED: 409,\n APP_SUSPENDED: 409,\n DELETE_CONFIRMATION_REQUIRED: 412,\n // Capabilities\n CAPABILITY_NOT_GRANTED: 403,\n CAPABILITY_CONSTRAINT_VIOLATED: 403,\n CAPABILITY_INPUT_INVALID: 422,\n EGRESS_DENIED: 403,\n // CLI/agent\n INTERACTION_REQUIRED: 412,\n} as const;\n\nexport type ErrorCode = keyof typeof ERROR_CODES;\n\nexport interface TinyErrorBody {\n code: ErrorCode;\n message: string;\n remediation: string;\n retryable: boolean;\n requestId?: string;\n details?: Record<string, unknown>;\n}\n\nexport class TinyError extends Error {\n readonly code: ErrorCode;\n readonly remediation: string;\n readonly retryable: boolean;\n readonly details: Record<string, unknown>;\n requestId?: string;\n\n constructor(\n code: ErrorCode,\n message: string,\n options: {\n remediation?: string;\n retryable?: boolean;\n details?: Record<string, unknown>;\n requestId?: string;\n cause?: unknown;\n } = {},\n ) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = 'TinyError';\n this.code = code;\n this.remediation = options.remediation ?? DEFAULT_REMEDIATION[code] ?? 'No automatic remediation is available.';\n this.retryable = options.retryable ?? RETRYABLE.has(code);\n this.details = options.details ?? {};\n if (options.requestId) this.requestId = options.requestId;\n }\n\n get httpStatus(): number {\n return ERROR_CODES[this.code];\n }\n\n toJSON(): { error: TinyErrorBody } {\n const body: TinyErrorBody = {\n code: this.code,\n message: this.message,\n remediation: this.remediation,\n retryable: this.retryable,\n };\n if (this.requestId) body.requestId = this.requestId;\n if (Object.keys(this.details).length > 0) body.details = this.details;\n return { error: body };\n }\n}\n\nconst RETRYABLE = new Set<ErrorCode>([\n 'RATE_LIMITED', 'INTERNAL', 'PROVIDER_UNAVAILABLE', 'MIGRATION_LOCKED',\n]);\n\nconst DEFAULT_REMEDIATION: Partial<Record<ErrorCode, string>> = {\n UNAUTHENTICATED: 'Run `tiny login` and retry.',\n AUTH_PROVIDER_REQUIRED: 'Configure a trusted OIDC provider for this gateway.',\n FORBIDDEN: 'Ask an organization admin for the required role.',\n APPROVAL_REQUIRED: 'Ask an organization admin to approve the plan, then deploy again with the approved plan ID.',\n PLAN_STALE: 'Run `tiny plan` again; the manifest or policy changed since this plan was produced.',\n MIGRATION_CHECKSUM_CHANGED: 'Restore the original migration file and add a new migration instead.',\n INTERACTION_REQUIRED: 'Re-run interactively, or pass the flag named in details.missingInput.',\n MANIFEST_SCHEMA_INVALID: 'Fix the fields listed in details.issues and re-run `tiny validate`.',\n TARGET_INCOMPATIBLE: 'Choose a runtime target that supports the manifest, or lower the requested resources.',\n DELETE_CONFIRMATION_REQUIRED: 'Re-run with the exact app slug as confirmation.',\n};\n\nexport function isTinyError(value: unknown): value is TinyError {\n return value instanceof TinyError;\n}\n\n/** Wrap any thrown value so callers always see a coded error. */\nexport function toTinyError(value: unknown, requestId?: string): TinyError {\n if (isTinyError(value)) {\n if (requestId && !value.requestId) value.requestId = requestId;\n return value;\n }\n const message = value instanceof Error ? value.message : String(value);\n return new TinyError('INTERNAL', message, { requestId, cause: value });\n}\n", "/**\n * Manifest durations are short strings (`60m`, `90d`) so an agent can write\n * them without a units library. Parsing lives here because policy, lifecycle,\n * and the planner all need to compare them.\n */\nimport { TinyError } from './errors.ts';\n\nconst UNIT_MS = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 } as const;\nexport type DurationUnit = keyof typeof UNIT_MS;\n\nconst PATTERN = /^([1-9][0-9]*)(s|m|h|d)$/;\n\nexport function parseDuration(value: string): number {\n const match = PATTERN.exec(value);\n if (!match) {\n throw new TinyError('VALIDATION_FAILED', `Invalid duration \"${value}\".`, {\n remediation: 'Use a positive integer followed by s, m, h, or d (for example 60m or 90d).',\n details: { value },\n });\n }\n return Number(match[1]) * UNIT_MS[match[2] as DurationUnit];\n}\n\nexport function tryParseDuration(value: string): number | null {\n const match = PATTERN.exec(value);\n return match ? Number(match[1]) * UNIT_MS[match[2] as DurationUnit] : null;\n}\n\nexport function formatDuration(ms: number): string {\n for (const unit of ['d', 'h', 'm', 's'] as const) {\n if (ms % UNIT_MS[unit] === 0 && ms >= UNIT_MS[unit]) return `${ms / UNIT_MS[unit]}${unit}`;\n }\n return `${Math.round(ms / 1000)}s`;\n}\n\nexport function addDuration(from: Date, duration: string): Date {\n return new Date(from.getTime() + parseDuration(duration));\n}\n", "/**\n * Control-plane entity shapes (blueprint \u00A77). These mirror the SQL schema in\n * @tinycloud/db; the store is responsible for keeping them in sync.\n */\nimport type { Id } from './ids.ts';\nimport type { AppState, DeploymentState, RuntimeInstanceState } from './states.ts';\n\nexport type OrgRole = 'owner' | 'admin' | 'developer' | 'auditor' | 'member';\nexport type AppRole = 'admin' | 'editor' | 'user' | 'viewer';\nexport type RiskTier = 'low' | 'medium' | 'high';\nexport type ActorType = 'user' | 'service_account' | 'system';\nexport type EnvironmentKind = 'production' | 'preview' | 'named';\n\nexport interface Organization {\n id: Id<'org'>;\n slug: string;\n displayName: string;\n plan: string;\n policy: OrganizationPolicy;\n createdAt: string;\n}\n\n/** Organization-wide guardrails the planner consults before every deploy. */\nexport interface OrganizationPolicy {\n allowPublicApps: boolean;\n allowRawSecrets: boolean;\n requireApprovalForCapabilities: boolean;\n requireApprovalForRawSecrets: boolean;\n maxAppsPerOrg: number;\n maxPreviewTtlHours: number;\n maxMemoryMiB: number;\n maxMonthlyRequests: number;\n /**\n * Per-app share of `maxMonthlyRequests`. `0` means \"no separate ceiling\",\n * which is the upgrade-safe default: an existing organization keeps one\n * pooled budget. Set it once an app is public, so anonymous traffic to that\n * app cannot exhaust the pool the organization's internal apps draw from.\n */\n maxMonthlyRequestsPerApp: number;\n /**\n * Per-app ceiling on requests served without a session. Only anonymous\n * ingress is counted, so this is inert for every app that requires login.\n */\n anonymousRequestsPerMinute: number;\n maxMonthlyCapabilityCalls: number;\n maxMonthlyEstimatedCostCents: number;\n reservedSlugs: string[];\n}\n\nexport const DEFAULT_ORGANIZATION_POLICY: OrganizationPolicy = {\n allowPublicApps: false,\n allowRawSecrets: true,\n requireApprovalForCapabilities: true,\n requireApprovalForRawSecrets: true,\n maxAppsPerOrg: 500,\n maxPreviewTtlHours: 168,\n maxMemoryMiB: 2048,\n maxMonthlyRequests: 100_000,\n maxMonthlyRequestsPerApp: 0,\n anonymousRequestsPerMinute: 600,\n maxMonthlyCapabilityCalls: 10_000,\n maxMonthlyEstimatedCostCents: 5_000,\n reservedSlugs: ['api', 'app', 'apps', 'admin', 'dashboard', 'gateway', 'tiny', 'www'],\n};\n\nexport interface User {\n id: Id<'user'>;\n email: string;\n displayName: string;\n createdAt: string;\n}\n\nexport interface Membership {\n id: Id<'mem'>;\n organizationId: Id<'org'>;\n userId: Id<'user'>;\n role: OrgRole;\n groups: string[];\n createdAt: string;\n}\n\nexport interface ServiceAccount {\n id: Id<'sa'>;\n organizationId: Id<'org'>;\n name: string;\n role: OrgRole;\n /** Only the hash is stored; the token is shown once at creation. */\n tokenHash: string;\n createdAt: string;\n}\n\nexport interface App {\n id: Id<'app'>;\n organizationId: Id<'org'>;\n slug: string;\n displayName: string;\n description: string | null;\n ownerMembershipId: Id<'mem'>;\n status: AppState;\n riskTier: RiskTier;\n currentProductionRevisionId: Id<'rev'> | null;\n lastAccessedAt: string | null;\n archivedAt: string | null;\n deleteAfter: string | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface AppRevision {\n id: Id<'rev'>;\n appId: Id<'app'>;\n revisionNumber: number;\n manifestVersion: string;\n normalizedManifest: Record<string, unknown>;\n manifestSha256: string;\n artifactUri: string;\n artifactSha256: string;\n sourceMetadata: Record<string, unknown>;\n createdByType: ActorType;\n createdById: string;\n createdAt: string;\n}\n\nexport interface Environment {\n id: Id<'env'>;\n appId: Id<'app'>;\n name: string;\n kind: EnvironmentKind;\n hostname: string;\n expiresAt: string | null;\n createdAt: string;\n}\n\nexport interface Deployment {\n id: Id<'dep'>;\n organizationId: Id<'org'>;\n appId: Id<'app'>;\n environmentId: Id<'env'>;\n revisionId: Id<'rev'>;\n runtimeTargetId: Id<'target'>;\n status: DeploymentState;\n idempotencyKey: string;\n plan: Record<string, unknown>;\n providerState: Record<string, unknown>;\n errorCode: string | null;\n errorDetail: Record<string, unknown> | null;\n startedAt: string | null;\n readyAt: string | null;\n finishedAt: string | null;\n createdAt: string;\n}\n\nexport interface RuntimeTarget {\n id: Id<'target'>;\n organizationId: Id<'org'> | null; // null = shared hosted target\n name: string;\n providerKind: string;\n region: string;\n config: Record<string, unknown>;\n healthy: boolean;\n createdAt: string;\n}\n\nexport interface RuntimeInstance {\n id: Id<'inst'>;\n deploymentId: Id<'dep'>;\n runtimeTargetId: Id<'target'>;\n providerRef: string;\n state: RuntimeInstanceState;\n updatedAt: string;\n}\n\nexport type ResourceKind = 'database' | 'storage' | 'secret' | 'domain';\n\nexport interface ResourceClaim {\n id: Id<'res'>;\n environmentId: Id<'env'>;\n kind: ResourceKind;\n name: string;\n spec: Record<string, unknown>;\n}\n\nexport interface ResourceBinding {\n id: Id<'bind'>;\n claimId: Id<'res'>;\n environmentId: Id<'env'>;\n kind: ResourceKind;\n name: string;\n providerKind: string;\n providerRef: string;\n /** Non-secret connection details; secrets stay in the secret store. */\n metadata: Record<string, unknown>;\n createdAt: string;\n}\n\nexport interface AccessBinding {\n id: Id<'access'>;\n appId: Id<'app'>;\n subjectType: 'user' | 'group' | 'service_account';\n subject: string;\n role: AppRole;\n grantedBy: string;\n createdAt: string;\n}\n\nexport interface Connection {\n id: Id<'conn'>;\n organizationId: Id<'org'>;\n name: string;\n provider: string;\n environmentClass: 'production' | 'sandbox';\n secretId: Id<'sec'>;\n createdAt: string;\n}\n\nexport interface CapabilityGrant {\n id: Id<'grant'>;\n appId: Id<'app'>;\n environmentId: Id<'env'> | null;\n connectionId: Id<'conn'>;\n operations: string[];\n constraints: Record<string, unknown>;\n status: 'pending' | 'approved' | 'revoked';\n approvedBy: string | null;\n createdAt: string;\n}\n\nexport interface Secret {\n id: Id<'sec'>;\n organizationId: Id<'org'>;\n name: string;\n createdAt: string;\n}\n\nexport interface SecretVersion {\n id: Id<'ver'>;\n secretId: Id<'sec'>;\n version: number;\n ciphertext: string;\n keyId: string;\n createdAt: string;\n}\n\nexport interface AuditEvent {\n id: Id<'evt'>;\n organizationId: Id<'org'>;\n occurredAt: string;\n actorType: ActorType;\n actorId: string | null;\n action: string;\n targetType: string;\n targetId: string;\n decision: 'allow' | 'deny' | null;\n reason: string | null;\n requestId: string | null;\n sourceIp: string | null;\n metadata: Record<string, unknown>;\n}\n\nexport interface UsageEvent {\n id: Id<'usage'>;\n organizationId: Id<'org'>;\n appId: Id<'app'> | null;\n meter: string;\n quantity: number;\n occurredAt: string;\n idempotencyKey: string;\n metadata: Record<string, unknown>;\n}\n\nexport interface LifecycleAction {\n id: Id<'life'>;\n appId: Id<'app'>;\n action: 'sleep' | 'archive' | 'restore' | 'delete';\n status: 'recommended' | 'scheduled' | 'running' | 'completed' | 'failed' | 'cancelled';\n reason: string;\n scheduledFor: string | null;\n completedAt: string | null;\n createdAt: string;\n}\n\n/** The identity a request acts under, resolved once at the API boundary. */\nexport interface Actor {\n type: ActorType;\n id: string;\n organizationId: Id<'org'>;\n role: OrgRole;\n email?: string;\n groups: string[];\n}\n", "/**\n * The single entry point for turning `tiny.yaml` text into a validated,\n * normalized manifest. The CLI, API, MCP server, and remote runtime all\n * call `parseManifest` so a manifest can never be accepted by one and\n * rejected by another (blueprint \u00A724.1).\n */\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { TinyError } from '@tinycloud/domain';\nimport { SchemaValidator, type Schema, type SchemaIssue } from './jsonschema.ts';\nimport { canonicalize, normalizeManifest } from './normalize.ts';\nimport { manifestWarnings, validateSemantics, type SemanticContext } from './semantic.ts';\nimport { MANIFEST_API_VERSION, type AppManifest, type NormalizedManifest } from './types.ts';\nimport { parseYaml } from './yaml.ts';\n\nexport * from './types.ts';\nexport * from './yaml.ts';\nexport * from './jsonschema.ts';\nexport { canonicalize, normalizeManifest, MANIFEST_DEFAULTS } from './normalize.ts';\nexport { manifestWarnings, validateSemantics, RESERVED_ENV_NAMES } from './semantic.ts';\nexport type { SemanticContext } from './semantic.ts';\n\nconst SCHEMA_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', 'schema', 'tiny.v1alpha1.json');\n\nexport const manifestSchema: Schema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf8')) as Schema;\n\nlet validator: SchemaValidator | null = null;\nfunction schemaValidator(): SchemaValidator {\n validator ??= new SchemaValidator(manifestSchema);\n return validator;\n}\n\nexport interface ParsedManifest {\n /** Exactly what the user wrote, kept for the immutable revision record. */\n source: string;\n manifest: AppManifest;\n normalized: NormalizedManifest;\n /** sha256 over the canonical normalized form. */\n sha256: string;\n warnings: string[];\n}\n\nfunction formatIssues(issues: SchemaIssue[]): string {\n return issues\n .slice(0, 10)\n .map((issue) => `${issue.path === '' ? '(root)' : issue.path} ${issue.message}`)\n .join('; ');\n}\n\nexport function parseManifest(source: string, context: SemanticContext = {}): ParsedManifest {\n const document = parseYaml(source);\n if (typeof document !== 'object' || document === null || Array.isArray(document)) {\n throw new TinyError('MANIFEST_PARSE_FAILED', 'tiny.yaml must contain a mapping at the top level.', {\n remediation: 'Run `tiny init` to generate a starter manifest.',\n });\n }\n\n const apiVersion = (document as Record<string, unknown>).apiVersion;\n if (apiVersion !== MANIFEST_API_VERSION) {\n throw new TinyError('MANIFEST_UNSUPPORTED_API_VERSION', `Unsupported apiVersion ${JSON.stringify(apiVersion)}.`, {\n remediation: `Set apiVersion to \"${MANIFEST_API_VERSION}\", or run \\`tiny manifest upgrade\\`.`,\n details: { supported: [MANIFEST_API_VERSION], found: apiVersion ?? null },\n });\n }\n\n const schemaIssues = schemaValidator().validate(document);\n if (schemaIssues.length > 0) {\n throw new TinyError('MANIFEST_SCHEMA_INVALID', `tiny.yaml failed schema validation: ${formatIssues(schemaIssues)}`, {\n details: { issues: schemaIssues },\n });\n }\n\n const manifest = document as unknown as AppManifest;\n const normalized = normalizeManifest(manifest);\n\n const semanticIssues = validateSemantics(normalized, context);\n if (semanticIssues.length > 0) {\n throw new TinyError('MANIFEST_POLICY_VIOLATION', `tiny.yaml violates platform or organization rules: ${formatIssues(semanticIssues)}`, {\n remediation: 'Fix the fields listed in details.issues, or ask an admin to change organization policy.',\n details: { issues: semanticIssues },\n });\n }\n\n return {\n source,\n manifest,\n normalized,\n sha256: manifestSha256(normalized),\n warnings: manifestWarnings(normalized),\n };\n}\n\nexport function manifestSha256(normalized: NormalizedManifest): string {\n return createHash('sha256').update(canonicalize(normalized)).digest('hex');\n}\n\n/** A starter manifest for `tiny init` (blueprint \u00A78.3). */\nexport function starterManifest(name: string, owner: string): string {\n return `apiVersion: ${MANIFEST_API_VERSION}\nkind: App\n\nmetadata:\n name: ${name}\n owner: ${owner}\n\nruntime:\n type: worker\n language: typescript\n entrypoint: src/index.ts\n\naccess:\n visibility: private\n requireLogin: true\n\nlifecycle:\n sleepAfter: 60m\n archiveAfterUnused: 30d\n`;\n}\n", "/**\n * A small JSON Schema (2020-12 subset) validator.\n *\n * The manifest schema is the authoritative contract published for editor\n * completion, so it is written as real JSON Schema rather than as code. This\n * validator supports exactly the keywords that schema uses; anything else\n * throws at load time rather than silently passing.\n */\nexport interface SchemaIssue {\n /** JSON Pointer into the instance, e.g. `/metadata/name`. */\n path: string;\n message: string;\n keyword: string;\n}\n\nexport type Schema = Record<string, unknown>;\n\nconst SUPPORTED = new Set([\n '$schema', '$id', '$ref', '$defs', 'title', 'description', 'examples', 'default',\n 'type', 'const', 'enum', 'required', 'properties', 'additionalProperties',\n 'items', 'minItems', 'maxItems', 'uniqueItems',\n 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf',\n 'minLength', 'maxLength', 'pattern', 'format',\n 'minProperties', 'maxProperties', 'propertyNames',\n 'oneOf', 'anyOf', 'allOf', 'not',\n]);\n\nconst FORMATS: Record<string, RegExp> = {\n email: /^[^\\s@]+@[^\\s@.]+(\\.[^\\s@.]+)+$/,\n hostname: /^(?=.{1,253}$)([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i,\n date: /^\\d{4}-\\d{2}-\\d{2}$/,\n 'date-time': /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?(Z|[+-]\\d{2}:\\d{2})$/,\n uri: /^[a-z][a-z0-9+.-]*:\\S+$/i,\n};\n\nfunction typeOf(value: unknown): string {\n if (value === null) return 'null';\n if (Array.isArray(value)) return 'array';\n if (Number.isInteger(value)) return 'integer';\n return typeof value;\n}\n\nfunction matchesType(value: unknown, type: string): boolean {\n const actual = typeOf(value);\n if (type === 'number') return actual === 'number' || actual === 'integer';\n if (type === 'integer') return actual === 'integer';\n return actual === type;\n}\n\nfunction deepEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\nfunction escapePointer(token: string): string {\n return token.replace(/~/g, '~0').replace(/\\//g, '~1');\n}\n\nexport class SchemaValidator {\n private readonly root: Schema;\n\n constructor(root: Schema) {\n this.root = root;\n this.assertSupported(root, '#');\n }\n\n private assertSupported(schema: unknown, at: string): void {\n if (typeof schema !== 'object' || schema === null || Array.isArray(schema)) return;\n for (const [key, value] of Object.entries(schema)) {\n if (!SUPPORTED.has(key)) {\n throw new Error(`Unsupported JSON Schema keyword \"${key}\" at ${at}.`);\n }\n if (key === 'properties' || key === '$defs') {\n for (const [name, sub] of Object.entries(value as Schema)) this.assertSupported(sub, `${at}/${key}/${name}`);\n } else if (key === 'oneOf' || key === 'anyOf' || key === 'allOf') {\n (value as unknown[]).forEach((sub, i) => this.assertSupported(sub, `${at}/${key}/${i}`));\n } else if (key === 'items' || key === 'not' || key === 'propertyNames' || key === 'additionalProperties') {\n this.assertSupported(value, `${at}/${key}`);\n }\n }\n }\n\n private resolve(ref: string): Schema {\n if (!ref.startsWith('#/')) throw new Error(`Only local $ref is supported, got \"${ref}\".`);\n let node: unknown = this.root;\n for (const token of ref.slice(2).split('/')) {\n node = (node as Record<string, unknown>)?.[token.replace(/~1/g, '/').replace(/~0/g, '~')];\n if (node === undefined) throw new Error(`Unresolved $ref \"${ref}\".`);\n }\n return node as Schema;\n }\n\n validate(instance: unknown, schema: Schema = this.root, path = ''): SchemaIssue[] {\n const issues: SchemaIssue[] = [];\n this.check(instance, schema, path, issues);\n return issues;\n }\n\n private check(value: unknown, schema: Schema, path: string, issues: SchemaIssue[]): void {\n if (typeof schema.$ref === 'string') {\n this.check(value, this.resolve(schema.$ref), path, issues);\n return;\n }\n\n if (schema.type !== undefined) {\n const types = Array.isArray(schema.type) ? schema.type as string[] : [schema.type as string];\n if (!types.some((t) => matchesType(value, t))) {\n issues.push({ path, keyword: 'type', message: `expected ${types.join(' or ')} but got ${typeOf(value)}` });\n return; // further keywords would produce noise\n }\n }\n\n if (schema.const !== undefined && !deepEqual(value, schema.const)) {\n issues.push({ path, keyword: 'const', message: `must be ${JSON.stringify(schema.const)}` });\n }\n\n if (Array.isArray(schema.enum) && !schema.enum.some((option) => deepEqual(option, value))) {\n issues.push({ path, keyword: 'enum', message: `must be one of ${schema.enum.map((o) => JSON.stringify(o)).join(', ')}` });\n }\n\n if (typeof value === 'string') this.checkString(value, schema, path, issues);\n if (typeof value === 'number') this.checkNumber(value, schema, path, issues);\n if (Array.isArray(value)) this.checkArray(value, schema, path, issues);\n else if (typeof value === 'object' && value !== null) this.checkObject(value as Record<string, unknown>, schema, path, issues);\n\n this.checkCombinators(value, schema, path, issues);\n }\n\n private checkString(value: string, schema: Schema, path: string, issues: SchemaIssue[]): void {\n if (typeof schema.minLength === 'number' && value.length < schema.minLength) {\n issues.push({ path, keyword: 'minLength', message: `must be at least ${schema.minLength} characters` });\n }\n if (typeof schema.maxLength === 'number' && value.length > schema.maxLength) {\n issues.push({ path, keyword: 'maxLength', message: `must be at most ${schema.maxLength} characters` });\n }\n if (typeof schema.pattern === 'string' && !new RegExp(schema.pattern, 'u').test(value)) {\n issues.push({ path, keyword: 'pattern', message: `must match ${schema.pattern}` });\n }\n if (typeof schema.format === 'string') {\n const rule = FORMATS[schema.format];\n if (rule && !rule.test(value)) {\n issues.push({ path, keyword: 'format', message: `must be a valid ${schema.format}` });\n }\n }\n }\n\n private checkNumber(value: number, schema: Schema, path: string, issues: SchemaIssue[]): void {\n if (typeof schema.minimum === 'number' && value < schema.minimum) {\n issues.push({ path, keyword: 'minimum', message: `must be >= ${schema.minimum}` });\n }\n if (typeof schema.maximum === 'number' && value > schema.maximum) {\n issues.push({ path, keyword: 'maximum', message: `must be <= ${schema.maximum}` });\n }\n if (typeof schema.exclusiveMinimum === 'number' && value <= schema.exclusiveMinimum) {\n issues.push({ path, keyword: 'exclusiveMinimum', message: `must be > ${schema.exclusiveMinimum}` });\n }\n if (typeof schema.exclusiveMaximum === 'number' && value >= schema.exclusiveMaximum) {\n issues.push({ path, keyword: 'exclusiveMaximum', message: `must be < ${schema.exclusiveMaximum}` });\n }\n if (typeof schema.multipleOf === 'number' && value % schema.multipleOf !== 0) {\n issues.push({ path, keyword: 'multipleOf', message: `must be a multiple of ${schema.multipleOf}` });\n }\n }\n\n private checkArray(value: unknown[], schema: Schema, path: string, issues: SchemaIssue[]): void {\n if (typeof schema.minItems === 'number' && value.length < schema.minItems) {\n issues.push({ path, keyword: 'minItems', message: `must contain at least ${schema.minItems} items` });\n }\n if (typeof schema.maxItems === 'number' && value.length > schema.maxItems) {\n issues.push({ path, keyword: 'maxItems', message: `must contain at most ${schema.maxItems} items` });\n }\n if (schema.uniqueItems === true) {\n const seen = new Set(value.map((item) => JSON.stringify(item)));\n if (seen.size !== value.length) issues.push({ path, keyword: 'uniqueItems', message: 'must not contain duplicates' });\n }\n if (schema.items) {\n value.forEach((item, i) => this.check(item, schema.items as Schema, `${path}/${i}`, issues));\n }\n }\n\n private checkObject(value: Record<string, unknown>, schema: Schema, path: string, issues: SchemaIssue[]): void {\n const keys = Object.keys(value);\n if (Array.isArray(schema.required)) {\n for (const key of schema.required as string[]) {\n if (!(key in value)) issues.push({ path: `${path}/${escapePointer(key)}`, keyword: 'required', message: 'is required' });\n }\n }\n if (typeof schema.maxProperties === 'number' && keys.length > schema.maxProperties) {\n issues.push({ path, keyword: 'maxProperties', message: `must have at most ${schema.maxProperties} properties` });\n }\n if (typeof schema.minProperties === 'number' && keys.length < schema.minProperties) {\n issues.push({ path, keyword: 'minProperties', message: `must have at least ${schema.minProperties} properties` });\n }\n const properties = (schema.properties ?? {}) as Record<string, Schema>;\n for (const key of keys) {\n const childPath = `${path}/${escapePointer(key)}`;\n if (schema.propertyNames) this.check(key, schema.propertyNames as Schema, childPath, issues);\n const child = properties[key];\n if (child) {\n this.check(value[key], child, childPath, issues);\n } else if (schema.additionalProperties === false) {\n issues.push({ path: childPath, keyword: 'additionalProperties', message: 'is not a recognized field' });\n } else if (typeof schema.additionalProperties === 'object' && schema.additionalProperties !== null) {\n this.check(value[key], schema.additionalProperties as Schema, childPath, issues);\n }\n }\n }\n\n private checkCombinators(value: unknown, schema: Schema, path: string, issues: SchemaIssue[]): void {\n if (Array.isArray(schema.allOf)) {\n for (const sub of schema.allOf as Schema[]) this.check(value, sub, path, issues);\n }\n if (Array.isArray(schema.anyOf)) {\n const branches = (schema.anyOf as Schema[]).map((sub) => this.validate(value, sub, path));\n if (branches.every((b) => b.length > 0)) {\n issues.push({ path, keyword: 'anyOf', message: `did not match any allowed shape: ${branches.flat().map((i) => i.message).join('; ')}` });\n }\n }\n if (Array.isArray(schema.oneOf)) {\n const matches = (schema.oneOf as Schema[]).filter((sub) => this.validate(value, sub, path).length === 0);\n if (matches.length !== 1) {\n issues.push({ path, keyword: 'oneOf', message: `must match exactly one allowed shape (matched ${matches.length})` });\n }\n }\n if (schema.not && this.validate(value, schema.not as Schema, path).length === 0) {\n issues.push({ path, keyword: 'not', message: 'matched a forbidden shape' });\n }\n }\n}\n", "/**\n * Types for the `tiny.yaml` manifest. `AppManifest` is the shape a user may\n * write; `NormalizedManifest` is the shape stored immutably on a revision,\n * with every default made explicit (blueprint \u00A78.1).\n */\nexport const MANIFEST_API_VERSION = 'tinycloud.dev/v1alpha1';\n\nexport type Visibility = 'private' | 'organization' | 'public';\nexport type RuntimeType = 'worker' | 'container';\nexport type Language = 'typescript' | 'javascript' | 'python';\nexport type ManifestAppRole = 'admin' | 'editor' | 'user' | 'viewer';\n\nexport interface ManifestMetadata {\n name: string;\n displayName?: string;\n description?: string;\n owner: string;\n labels?: Record<string, string>;\n}\n\nexport interface ManifestRuntimeResources {\n memory?: string;\n cpuMillis?: number;\n requestTimeout?: string;\n concurrency?: number;\n}\n\nexport interface ManifestRuntime {\n type: RuntimeType;\n language?: Language;\n entrypoint: string;\n compatibilityDate?: string;\n resources?: ManifestRuntimeResources;\n}\n\nexport interface ManifestBuild {\n command?: string;\n output?: string;\n packageManager?: 'npm' | 'pnpm' | 'yarn' | 'bun';\n lockfileRequired?: boolean;\n}\n\nexport interface ManifestAccessSubject {\n user?: string;\n group?: string;\n serviceAccount?: string;\n role?: ManifestAppRole;\n}\n\nexport interface ManifestAccess {\n visibility: Visibility;\n requireLogin?: boolean;\n subjects?: ManifestAccessSubject[];\n}\n\nexport interface ManifestDatabase {\n engine: 'sqlite' | 'postgres';\n name?: string;\n migrations?: string;\n retention?: string;\n}\n\nexport interface ManifestStorage {\n name: string;\n class?: 'object';\n maxSize?: string;\n retention?: string;\n}\n\nexport interface ManifestResources {\n database?: ManifestDatabase;\n storage?: ManifestStorage[];\n}\n\nexport interface ManifestCapability {\n name: string;\n connection: string;\n allow: string[];\n constraints?: Record<string, unknown>;\n}\n\nexport interface ManifestEgressRule {\n host: string;\n ports?: number[];\n}\n\nexport interface ManifestNetwork {\n egress?: { mode?: 'deny' | 'allowlist'; allow?: ManifestEgressRule[] };\n}\n\nexport interface ManifestSecretReference {\n name: string;\n from: string;\n version?: string;\n}\n\nexport interface ManifestLifecycle {\n sleepAfter?: string;\n archiveAfterUnused?: string;\n deleteAfterArchived?: string;\n previewTtl?: string;\n}\n\nexport interface ManifestAlert {\n type: 'errorRate' | 'latency' | 'requests';\n threshold: number;\n window?: string;\n notify?: string;\n}\n\nexport interface ManifestObservability {\n logLevel?: 'debug' | 'info' | 'warn' | 'error';\n retention?: string;\n traces?: { sampleRate?: number };\n alerts?: ManifestAlert[];\n}\n\nexport interface AppManifest {\n apiVersion: typeof MANIFEST_API_VERSION;\n kind: 'App';\n metadata: ManifestMetadata;\n runtime: ManifestRuntime;\n build?: ManifestBuild;\n access: ManifestAccess;\n resources?: ManifestResources;\n capabilities?: ManifestCapability[];\n network?: ManifestNetwork;\n secrets?: ManifestSecretReference[];\n lifecycle: ManifestLifecycle;\n observability?: ManifestObservability;\n}\n\nexport interface NormalizedSubject {\n type: 'user' | 'group' | 'serviceAccount';\n value: string;\n role: ManifestAppRole;\n}\n\n/** Every optional field resolved; this is what the planner and providers read. */\nexport interface NormalizedManifest {\n apiVersion: typeof MANIFEST_API_VERSION;\n kind: 'App';\n metadata: Required<Pick<ManifestMetadata, 'name' | 'owner' | 'displayName'>> & {\n description: string | null;\n labels: Record<string, string>;\n };\n runtime: {\n type: RuntimeType;\n language: Language;\n entrypoint: string;\n compatibilityDate: string | null;\n resources: { memoryMiB: number; cpuMillis: number; requestTimeoutMs: number; concurrency: number };\n };\n build: { command: string | null; output: string | null; packageManager: string; lockfileRequired: boolean };\n access: { visibility: Visibility; requireLogin: boolean; subjects: NormalizedSubject[] };\n resources: {\n database: { engine: 'sqlite' | 'postgres'; name: string; migrations: string; retentionMs: number } | null;\n storage: Array<{ name: string; class: 'object'; maxSizeBytes: number; retentionMs: number }>;\n };\n capabilities: Array<{ name: string; connection: string; allow: string[]; constraints: Record<string, unknown> }>;\n network: { egress: { mode: 'deny' | 'allowlist'; allow: Array<{ host: string; ports: number[] }> } };\n secrets: Array<{ name: string; from: string; version: string }>;\n lifecycle: { sleepAfterMs: number | null; archiveAfterUnusedMs: number | null; deleteAfterArchivedMs: number | null; previewTtlMs: number };\n observability: {\n logLevel: 'debug' | 'info' | 'warn' | 'error';\n retentionMs: number;\n traces: { sampleRate: number };\n alerts: ManifestAlert[];\n };\n}\n", "/**\n * Normalization resolves defaults and converts human units into machine units\n * exactly once, at revision creation. Everything downstream \u2014 planner,\n * providers, gateway, policy \u2014 reads the normalized form, so a default can\n * never drift between two subsystems (blueprint \u00A78.1).\n */\nimport { parseDuration } from '@tinycloud/domain';\nimport type { AppManifest, Language, NormalizedManifest, NormalizedSubject } from './types.ts';\nimport { MANIFEST_API_VERSION } from './types.ts';\n\nexport const MANIFEST_DEFAULTS = {\n // The guest runs a full Node binary from a virtio disk. Below roughly\n // 256Mi its text pages no longer fit in the guest page cache, and the\n // guest kernel spends every scheduling slice re-reading them instead of\n // running the app; 512Mi leaves headroom above that cliff. Firecracker\n // faults guest memory in lazily, so the unused part of the ceiling is free.\n memory: '512Mi',\n cpuMillis: 50,\n requestTimeout: '15s',\n concurrency: 20,\n packageManager: 'npm',\n buildOutput: 'dist',\n databaseName: 'app',\n migrationsDir: 'migrations',\n retention: '30d',\n storageMaxSize: '1Gi',\n previewTtl: '72h',\n logRetention: '7d',\n sampleRate: 0.05,\n storageClass: 'object',\n} as const;\n\nfunction parseSize(value: string): number {\n const match = /^([1-9][0-9]*)(Mi|Gi)$/.exec(value);\n if (!match) return 0;\n return Number(match[1]) * (match[2] === 'Gi' ? 1024 ** 3 : 1024 ** 2);\n}\n\nfunction memoryMiB(value: string): number {\n return Math.round(parseSize(value) / 1024 ** 2);\n}\n\nfunction inferLanguage(manifest: AppManifest): Language {\n if (manifest.runtime.language) return manifest.runtime.language;\n return manifest.runtime.entrypoint.endsWith('.py') ? 'python' : 'typescript';\n}\n\nfunction normalizeSubjects(manifest: AppManifest): NormalizedSubject[] {\n const subjects: NormalizedSubject[] = (manifest.access.subjects ?? []).map((subject) => {\n const type = subject.user ? 'user' : subject.group ? 'group' : 'serviceAccount';\n const value = (subject.user ?? subject.group ?? subject.serviceAccount)!;\n return { type, value: type === 'user' ? value.toLowerCase() : value, role: subject.role ?? 'user' };\n });\n // The owner always has app admin, even if the manifest forgets to say so.\n const owner = manifest.metadata.owner.toLowerCase();\n if (!subjects.some((s) => s.type === 'user' && s.value === owner)) {\n subjects.unshift({ type: 'user', value: owner, role: 'admin' });\n }\n return subjects;\n}\n\nexport function normalizeManifest(manifest: AppManifest): NormalizedManifest {\n const runtimeResources = manifest.runtime.resources ?? {};\n const database = manifest.resources?.database ?? null;\n const egress = manifest.network?.egress ?? {};\n\n return {\n apiVersion: MANIFEST_API_VERSION,\n kind: 'App',\n metadata: {\n name: manifest.metadata.name,\n displayName: manifest.metadata.displayName ?? manifest.metadata.name,\n description: manifest.metadata.description ?? null,\n owner: manifest.metadata.owner.toLowerCase(),\n labels: manifest.metadata.labels ?? {},\n },\n runtime: {\n type: manifest.runtime.type,\n language: inferLanguage(manifest),\n entrypoint: manifest.runtime.entrypoint,\n compatibilityDate: manifest.runtime.compatibilityDate ?? null,\n resources: {\n memoryMiB: memoryMiB(runtimeResources.memory ?? MANIFEST_DEFAULTS.memory),\n cpuMillis: runtimeResources.cpuMillis ?? MANIFEST_DEFAULTS.cpuMillis,\n requestTimeoutMs: parseDuration(runtimeResources.requestTimeout ?? MANIFEST_DEFAULTS.requestTimeout),\n concurrency: runtimeResources.concurrency ?? MANIFEST_DEFAULTS.concurrency,\n },\n },\n build: {\n command: manifest.build?.command ?? null,\n output: manifest.build?.output ?? null,\n packageManager: manifest.build?.packageManager ?? MANIFEST_DEFAULTS.packageManager,\n // Production deploys require a lockfile unless explicitly opted out.\n lockfileRequired: manifest.build?.lockfileRequired ?? true,\n },\n access: {\n visibility: manifest.access.visibility,\n requireLogin: manifest.access.requireLogin ?? true,\n subjects: normalizeSubjects(manifest),\n },\n resources: {\n database: database\n ? {\n engine: database.engine,\n name: database.name ?? MANIFEST_DEFAULTS.databaseName,\n migrations: database.migrations ?? MANIFEST_DEFAULTS.migrationsDir,\n retentionMs: parseDuration(database.retention ?? MANIFEST_DEFAULTS.retention),\n }\n : null,\n storage: (manifest.resources?.storage ?? []).map((store) => ({\n name: store.name,\n class: store.class ?? MANIFEST_DEFAULTS.storageClass,\n maxSizeBytes: parseSize(store.maxSize ?? MANIFEST_DEFAULTS.storageMaxSize),\n retentionMs: parseDuration(store.retention ?? MANIFEST_DEFAULTS.retention),\n })),\n },\n capabilities: (manifest.capabilities ?? []).map((capability) => ({\n name: capability.name,\n connection: capability.connection,\n // Manifests may use concise names (`payments.read`) while the catalog\n // and runtime use globally-qualified IDs (`stripe.payments.read`).\n allow: capability.allow\n .map((operation) => operation.startsWith(`${capability.name}.`)\n ? operation\n : `${capability.name}.${operation}`)\n .sort(),\n constraints: capability.constraints ?? {},\n })),\n network: {\n egress: {\n // Default deny: an app gets no outbound network unless it asks (\u00A710, \u00A727).\n mode: egress.mode ?? 'deny',\n allow: (egress.allow ?? []).map((rule) => ({\n host: rule.host.toLowerCase(),\n ports: rule.ports ?? [443],\n })),\n },\n },\n secrets: (manifest.secrets ?? []).map((secret) => ({\n name: secret.name,\n from: secret.from,\n version: secret.version ?? 'latest',\n })),\n lifecycle: {\n sleepAfterMs: manifest.lifecycle.sleepAfter ? parseDuration(manifest.lifecycle.sleepAfter) : null,\n archiveAfterUnusedMs: manifest.lifecycle.archiveAfterUnused ? parseDuration(manifest.lifecycle.archiveAfterUnused) : null,\n deleteAfterArchivedMs: manifest.lifecycle.deleteAfterArchived ? parseDuration(manifest.lifecycle.deleteAfterArchived) : null,\n previewTtlMs: parseDuration(manifest.lifecycle.previewTtl ?? MANIFEST_DEFAULTS.previewTtl),\n },\n observability: {\n logLevel: manifest.observability?.logLevel ?? 'info',\n retentionMs: parseDuration(manifest.observability?.retention ?? MANIFEST_DEFAULTS.logRetention),\n traces: { sampleRate: manifest.observability?.traces?.sampleRate ?? MANIFEST_DEFAULTS.sampleRate },\n alerts: manifest.observability?.alerts ?? [],\n },\n };\n}\n\n/**\n * Stable stringification so an unchanged manifest always hashes to the same\n * revision digest regardless of key order in the source YAML.\n */\nexport function canonicalize(value: unknown): string {\n if (value === null || typeof value !== 'object') return JSON.stringify(value) ?? 'null';\n if (Array.isArray(value)) return `[${value.map(canonicalize).join(',')}]`;\n const entries = Object.entries(value as Record<string, unknown>)\n .filter(([, v]) => v !== undefined)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));\n return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonicalize(v)}`).join(',')}}`;\n}\n", "/**\n * Rules JSON Schema cannot express (blueprint \u00A78.4). These run after schema\n * validation, against the normalized manifest, and may consult organization\n * policy \u2014 so the same manifest can be valid for one org and rejected by\n * another.\n */\nimport type { OrganizationPolicy } from '@tinycloud/domain';\nimport { DEFAULT_ORGANIZATION_POLICY } from '@tinycloud/domain';\nimport type { SchemaIssue } from './jsonschema.ts';\nimport type { NormalizedManifest } from './types.ts';\n\n/** Names the platform injects into every app; a manifest may not shadow them. */\nexport const RESERVED_ENV_NAMES = new Set([\n 'TINY_APP_ID', 'TINY_APP_SLUG', 'TINY_DEPLOYMENT_ID', 'TINY_ENVIRONMENT',\n 'TINY_ORG_ID', 'TINY_REQUEST_ID', 'TINY_TOKEN', 'TINY_API_URL',\n 'TINY_DATABASE_URL', 'TINY_STORAGE_ROOT', 'TINY_BROKER_URL',\n]);\n\nexport interface SemanticContext {\n policy?: OrganizationPolicy;\n /** Migration/entrypoint files present in the uploaded source, if known. */\n sourceFiles?: string[];\n}\n\nexport function validateSemantics(\n manifest: NormalizedManifest,\n context: SemanticContext = {},\n): SchemaIssue[] {\n const policy = context.policy ?? DEFAULT_ORGANIZATION_POLICY;\n const issues: SchemaIssue[] = [];\n const add = (path: string, message: string, keyword = 'semantic') => issues.push({ path, message, keyword });\n\n // Runtime/language compatibility.\n if (manifest.runtime.type === 'worker' && manifest.runtime.language === 'python') {\n add('/runtime/language', 'worker runtime supports only typescript and javascript');\n }\n if (manifest.runtime.resources.memoryMiB > policy.maxMemoryMiB) {\n add('/runtime/resources/memory', `exceeds the organization limit of ${policy.maxMemoryMiB}Mi`);\n }\n\n // Access.\n if (manifest.access.visibility === 'public' && !policy.allowPublicApps) {\n add('/access/visibility', 'public apps are not permitted by organization policy');\n }\n if (manifest.access.visibility !== 'public' && !manifest.access.requireLogin) {\n add('/access/requireLogin', 'non-public apps must require login');\n }\n const seenSubjects = new Set<string>();\n manifest.access.subjects.forEach((subject, index) => {\n const key = `${subject.type}:${subject.value}`;\n if (seenSubjects.has(key)) add(`/access/subjects/${index}`, `duplicate subject ${key}`);\n seenSubjects.add(key);\n });\n\n // Reserved names.\n if (policy.reservedSlugs.includes(manifest.metadata.name)) {\n add('/metadata/name', `\"${manifest.metadata.name}\" is a reserved name`);\n }\n manifest.secrets.forEach((secret, index) => {\n if (RESERVED_ENV_NAMES.has(secret.name)) {\n add(`/secrets/${index}/name`, `\"${secret.name}\" collides with a reserved platform variable`);\n }\n });\n const secretNames = new Set<string>();\n manifest.secrets.forEach((secret, index) => {\n if (secretNames.has(secret.name)) add(`/secrets/${index}/name`, `duplicate secret name \"${secret.name}\"`);\n secretNames.add(secret.name);\n });\n if (manifest.secrets.length > 0 && !policy.allowRawSecrets) {\n add('/secrets', 'raw secret injection is disabled by organization policy');\n }\n\n // Lifecycle ordering.\n const { sleepAfterMs, archiveAfterUnusedMs, deleteAfterArchivedMs, previewTtlMs } = manifest.lifecycle;\n if (sleepAfterMs !== null && archiveAfterUnusedMs !== null && archiveAfterUnusedMs <= sleepAfterMs) {\n add('/lifecycle/archiveAfterUnused', 'must be longer than lifecycle.sleepAfter');\n }\n if (deleteAfterArchivedMs !== null && archiveAfterUnusedMs === null) {\n add('/lifecycle/deleteAfterArchived', 'requires lifecycle.archiveAfterUnused to be set');\n }\n if (previewTtlMs > policy.maxPreviewTtlHours * 3_600_000) {\n add('/lifecycle/previewTtl', `exceeds the organization maximum of ${policy.maxPreviewTtlHours}h`);\n }\n\n // Storage and database.\n const storageNames = new Set<string>();\n manifest.resources.storage.forEach((store, index) => {\n if (storageNames.has(store.name)) add(`/resources/storage/${index}/name`, `duplicate storage name \"${store.name}\"`);\n storageNames.add(store.name);\n });\n\n // Capabilities.\n const capabilityNames = new Set<string>();\n manifest.capabilities.forEach((capability, index) => {\n if (capabilityNames.has(capability.name)) {\n add(`/capabilities/${index}/name`, `duplicate capability name \"${capability.name}\"`);\n }\n capabilityNames.add(capability.name);\n for (const operation of capability.allow) {\n if (operation.endsWith('.*') || operation.includes('request') && operation.includes('raw')) {\n add(`/capabilities/${index}/allow`, `\"${operation}\" is too broad; grant named operations only`);\n }\n }\n });\n\n // Egress.\n if (manifest.network.egress.mode === 'deny' && manifest.network.egress.allow.length > 0) {\n add('/network/egress/mode', 'set mode to \"allowlist\" to use network.egress.allow');\n }\n if (manifest.network.egress.mode === 'allowlist' && manifest.network.egress.allow.length === 0) {\n add('/network/egress/allow', 'allowlist mode requires at least one allowed host');\n }\n\n // Source layout, when the caller knows which files were uploaded.\n if (context.sourceFiles) {\n // With a build step the entrypoint is resolved inside the build output,\n // which does not exist yet; the builder checks it instead.\n if (!manifest.build.command && !context.sourceFiles.includes(manifest.runtime.entrypoint)) {\n add('/runtime/entrypoint', `\"${manifest.runtime.entrypoint}\" was not found in the uploaded source`);\n }\n const migrations = manifest.resources.database?.migrations;\n if (migrations && !context.sourceFiles.some((file) => file.startsWith(`${migrations}/`))) {\n add('/resources/database/migrations', `no migration files found under \"${migrations}/\"`);\n }\n }\n\n return issues;\n}\n\n/** Non-blocking advice surfaced by `tiny validate` and `tiny doctor`. */\nexport function manifestWarnings(manifest: NormalizedManifest): string[] {\n const warnings: string[] = [];\n if (manifest.secrets.length > 0) {\n warnings.push(`This app receives ${manifest.secrets.length} raw secret(s). Prefer a capability grant where one exists.`);\n }\n if (manifest.lifecycle.archiveAfterUnusedMs === null) {\n warnings.push('No lifecycle.archiveAfterUnused set; this app will never be recommended for archive.');\n }\n if (manifest.access.visibility === 'organization' && manifest.access.subjects.length <= 1) {\n warnings.push('Visibility is \"organization\": every member can open this app.');\n }\n if (manifest.build.command && !manifest.build.output) {\n warnings.push('build.command is set without build.output; the whole source tree, including node_modules, will be deployed.');\n }\n if (manifest.build.command && !manifest.build.lockfileRequired) {\n warnings.push('build.lockfileRequired is false; two deploys of the same source may install different dependencies.');\n }\n if (manifest.network.egress.mode === 'allowlist') {\n warnings.push(\n `Direct egress to ${manifest.network.egress.allow.map((rule) => rule.host).join(', ')} bypasses the capability broker; `\n + 'the app holds any credential it needs for those hosts itself.',\n );\n }\n return warnings;\n}\n", "/**\n * A strict YAML subset parser for `tiny.yaml`.\n *\n * The manifest is deliberately small (blueprint \u00A78.1), so the platform parses\n * a deliberately small language: block maps, block sequences, flow scalars,\n * flow sequences/maps, and `|`/`>` block scalars. Anchors, aliases, tags,\n * multiple documents, and merge keys are rejected \u2014 they are attack surface\n * and an agent never needs them to write a manifest.\n */\nimport { TinyError } from '@tinycloud/domain';\n\nexport type YamlValue = string | number | boolean | null | YamlValue[] | { [key: string]: YamlValue };\n\ninterface Line {\n indent: number;\n text: string;\n number: number;\n}\n\nfunction fail(message: string, line: number, remediation = 'Fix the manifest and re-run `tiny validate`.'): never {\n throw new TinyError('MANIFEST_PARSE_FAILED', `${message} (line ${line})`, {\n remediation,\n details: { line },\n });\n}\n\n/** Strip an unquoted trailing `#` comment. */\nfunction stripComment(text: string): string {\n let quote: string | null = null;\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!;\n if (quote) {\n if (ch === '\\\\' && quote === '\"') i++;\n else if (ch === quote) quote = null;\n } else if (ch === '\"' || ch === \"'\") {\n quote = ch;\n } else if (ch === '#' && (i === 0 || /\\s/.test(text[i - 1]!))) {\n return text.slice(0, i);\n }\n }\n return text;\n}\n\nfunction scan(source: string): Line[] {\n const out: Line[] = [];\n const raw = source.split(/\\r?\\n/);\n for (let i = 0; i < raw.length; i++) {\n const original = raw[i]!;\n if (original.includes('\\t')) {\n fail('Tabs are not allowed for indentation', i + 1, 'Indent with spaces only.');\n }\n const text = stripComment(original).trimEnd();\n if (text.trim() === '') continue;\n if (text.trim() === '---') continue;\n if (text.trim() === '...') break;\n out.push({ indent: original.length - original.trimStart().length, text: text.trim(), number: i + 1 });\n }\n return out;\n}\n\nfunction parseFlowScalar(token: string, line: number): YamlValue {\n const text = token.trim();\n if (text === '') return null;\n if (text.startsWith('\"')) {\n if (!text.endsWith('\"') || text.length < 2) fail('Unterminated double-quoted string', line);\n return JSON.parse(text) as string;\n }\n if (text.startsWith(\"'\")) {\n if (!text.endsWith(\"'\") || text.length < 2) fail('Unterminated single-quoted string', line);\n return text.slice(1, -1).replaceAll(\"''\", \"'\");\n }\n if (text.startsWith('[') || text.startsWith('{')) return parseFlowCollection(text, line);\n if (text === 'null' || text === '~') return null;\n if (text === 'true') return true;\n if (text === 'false') return false;\n if (/^-?(0|[1-9][0-9]*)(\\.[0-9]+)?([eE][+-]?[0-9]+)?$/.test(text)) return Number(text);\n if (text.startsWith('*') || text.startsWith('&') || text.startsWith('!')) {\n fail('Anchors, aliases, and tags are not supported in tiny.yaml', line, 'Write the value inline.');\n }\n return text;\n}\n\n/** Split a flow collection body on top-level commas. */\nfunction splitFlow(body: string, line: number): string[] {\n const parts: string[] = [];\n let depth = 0;\n let quote: string | null = null;\n let current = '';\n for (let i = 0; i < body.length; i++) {\n const ch = body[i]!;\n if (quote) {\n current += ch;\n if (ch === '\\\\' && quote === '\"') { current += body[++i] ?? ''; continue; }\n if (ch === quote) quote = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\") { quote = ch; current += ch; continue; }\n if (ch === '[' || ch === '{') depth++;\n if (ch === ']' || ch === '}') depth--;\n if (ch === ',' && depth === 0) { parts.push(current); current = ''; continue; }\n current += ch;\n }\n if (quote) fail('Unterminated string in flow collection', line);\n if (depth !== 0) fail('Unbalanced brackets in flow collection', line);\n if (current.trim() !== '') parts.push(current);\n return parts;\n}\n\nfunction parseFlowCollection(text: string, line: number): YamlValue {\n if (text.startsWith('[')) {\n if (!text.endsWith(']')) fail('Unterminated flow sequence', line);\n return splitFlow(text.slice(1, -1), line).map((part) => parseFlowScalar(part, line));\n }\n if (!text.endsWith('}')) fail('Unterminated flow mapping', line);\n const out: Record<string, YamlValue> = {};\n for (const part of splitFlow(text.slice(1, -1), line)) {\n const idx = part.indexOf(':');\n if (idx === -1) fail('Flow mapping entry is missing \":\"', line);\n const key = String(parseFlowScalar(part.slice(0, idx), line));\n if (key in out) fail(`Duplicate key \"${key}\"`, line);\n out[key] = parseFlowScalar(part.slice(idx + 1), line);\n }\n return out;\n}\n\n/** Find the index of the `:` that separates a block mapping key from its value. */\nfunction keySeparator(text: string): number {\n let quote: string | null = null;\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]!;\n if (quote) {\n if (ch === '\\\\' && quote === '\"') i++;\n else if (ch === quote) quote = null;\n continue;\n }\n if (ch === '\"' || ch === \"'\") { quote = ch; continue; }\n if (ch === '[' || ch === '{') return -1; // flow value, not a key\n if (ch === ':' && (i === text.length - 1 || text[i + 1] === ' ')) return i;\n }\n return -1;\n}\n\nclass Parser {\n private index = 0;\n private readonly lines: Line[];\n\n constructor(lines: Line[]) {\n this.lines = lines;\n }\n\n private peek(): Line | undefined {\n return this.lines[this.index];\n }\n\n parseValue(indent: number): YamlValue {\n const line = this.peek();\n if (!line || line.indent < indent) return null;\n if (line.text.startsWith('- ') || line.text === '-') return this.parseSequence(line.indent);\n return this.parseMapping(line.indent);\n }\n\n private parseSequence(indent: number): YamlValue[] {\n const items: YamlValue[] = [];\n for (;;) {\n const line = this.peek();\n if (!line || line.indent !== indent || !(line.text === '-' || line.text.startsWith('- '))) break;\n const rest = line.text === '-' ? '' : line.text.slice(2).trim();\n this.index++;\n if (rest === '') {\n items.push(this.parseValue(indent + 1));\n continue;\n }\n const sep = keySeparator(rest);\n if (sep !== -1) {\n // `- key: value` starts a mapping whose first key is inline with the dash.\n items.push(this.parseInlineMapping(line, rest, sep, indent));\n } else {\n items.push(this.parseScalarOrBlock(rest, line));\n }\n }\n return items;\n }\n\n private parseInlineMapping(line: Line, rest: string, sep: number, indent: number): YamlValue {\n const map: Record<string, YamlValue> = {};\n const key = String(parseFlowScalar(rest.slice(0, sep), line.number));\n const inlineValue = rest.slice(sep + 1).trim();\n const childIndent = indent + 2;\n if (inlineValue === '') {\n map[key] = this.hasChildAt(childIndent) ? this.parseValue(childIndent) : null;\n } else {\n map[key] = this.parseScalarOrBlock(inlineValue, line);\n }\n const tail = this.parseMappingEntries(childIndent);\n for (const [k, v] of Object.entries(tail)) {\n if (k in map) fail(`Duplicate key \"${k}\"`, line.number);\n map[k] = v;\n }\n return map;\n }\n\n private hasChildAt(indent: number): boolean {\n const next = this.peek();\n return next !== undefined && next.indent >= indent;\n }\n\n private parseMapping(indent: number): Record<string, YamlValue> {\n return this.parseMappingEntries(indent);\n }\n\n private parseMappingEntries(indent: number): Record<string, YamlValue> {\n const map: Record<string, YamlValue> = {};\n for (;;) {\n const line = this.peek();\n if (!line || line.indent < indent) break;\n if (line.indent > indent) fail('Unexpected indentation', line.number, 'Align keys of the same mapping to the same column.');\n if (line.text.startsWith('- ')) break;\n const sep = keySeparator(line.text);\n if (sep === -1) fail(`Expected \"key: value\" but found \"${line.text}\"`, line.number);\n const key = String(parseFlowScalar(line.text.slice(0, sep), line.number));\n if (key in map) fail(`Duplicate key \"${key}\"`, line.number, 'Remove the repeated key.');\n const inline = line.text.slice(sep + 1).trim();\n this.index++;\n if (inline === '') {\n const next = this.peek();\n map[key] = next && next.indent > indent ? this.parseValue(next.indent)\n : next && next.indent === indent && next.text.startsWith('- ') ? this.parseSequence(indent)\n : null;\n } else {\n map[key] = this.parseScalarOrBlock(inline, line);\n }\n }\n return map;\n }\n\n /** Handle `|`/`>` block scalars; everything else is a flow scalar. */\n private parseScalarOrBlock(text: string, line: Line): YamlValue {\n if (text !== '|' && text !== '>' && text !== '|-' && text !== '>-') {\n return parseFlowScalar(text, line.number);\n }\n const folded = text.startsWith('>');\n const chomp = text.endsWith('-');\n const parts: string[] = [];\n const baseIndent = this.peek()?.indent ?? 0;\n while (this.peek() && this.peek()!.indent >= baseIndent && baseIndent > line.indent) {\n parts.push(this.lines[this.index]!.text);\n this.index++;\n }\n const body = folded ? parts.join(' ') : parts.join('\\n');\n return chomp ? body : body + (folded ? '' : '\\n');\n }\n\n atEnd(): boolean {\n return this.index >= this.lines.length;\n }\n\n currentLine(): number {\n return this.peek()?.number ?? 0;\n }\n}\n\nexport function parseYaml(source: string): YamlValue {\n const lines = scan(source);\n if (lines.length === 0) {\n throw new TinyError('MANIFEST_PARSE_FAILED', 'The manifest is empty.', {\n remediation: 'Run `tiny init` to generate a starter tiny.yaml.',\n });\n }\n const parser = new Parser(lines);\n const value = parser.parseValue(lines[0]!.indent);\n if (!parser.atEnd()) fail('Trailing content after the document', parser.currentLine());\n return value;\n}\n", "/**\n * Content-addressed artifact store (blueprint \u00A715.2, \u00A715.3).\n *\n * Artifacts are immutable and addressed by the digest of their contents, so\n * deploying the same source twice reuses one artifact and a rollback always\n * has something byte-identical to return to. Uploads exclude `.git`, local\n * state, caches, and anything named in `.tinyignore`, because agents routinely\n * leave `.env` files lying next to the code they generate.\n */\nimport { createHash, randomUUID } from 'node:crypto';\nimport { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';\nimport { dirname, join, normalize, relative, sep } from 'node:path';\nimport { TinyError } from '@tinycloud/domain';\n\nexport const DEFAULT_IGNORES = [\n '.git', 'node_modules', '.tiny', '.output', 'dist/.cache', '.DS_Store',\n '.env', '.env.local', '.env.production', '*.pem', '*.key', 'id_rsa',\n '.venv', '__pycache__', '.pytest_cache', '.turbo', 'coverage',\n];\n\n/** Files that look like credentials and should never be uploaded (\u00A719.1). */\nconst SECRET_LOOKING = /(^|\\/)(\\.env(\\..+)?|.*\\.pem|.*\\.key|id_rsa|credentials\\.json|service-account.*\\.json)$/i;\n\nexport interface PackedSource {\n files: string[];\n sizeBytes: number;\n sha256: string;\n /** Paths that were skipped because they look like secrets. */\n skippedSecrets: string[];\n}\n\nexport interface ArtifactLimits {\n maxFiles: number;\n maxTotalBytes: number;\n maxFileBytes: number;\n}\n\nexport const DEFAULT_LIMITS: ArtifactLimits = {\n maxFiles: 5000,\n maxTotalBytes: 50 * 1024 * 1024,\n maxFileBytes: 10 * 1024 * 1024,\n};\n\nfunction loadIgnores(root: string): string[] {\n const patterns = [...DEFAULT_IGNORES];\n const file = join(root, '.tinyignore');\n if (existsSync(file)) {\n for (const line of readFileSync(file, 'utf8').split('\\n')) {\n const trimmed = line.trim();\n if (trimmed && !trimmed.startsWith('#')) patterns.push(trimmed);\n }\n }\n return patterns;\n}\n\nfunction matches(pattern: string, path: string): boolean {\n if (pattern.includes('*')) {\n const rule = new RegExp(`^${pattern.split('*').map(escapeRegExp).join('[^/]*')}$`);\n return path.split('/').some((segment) => rule.test(segment)) || rule.test(path);\n }\n return path === pattern || path.startsWith(`${pattern}/`) || path.split('/').includes(pattern);\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/** Walk a source directory, applying ignore rules and upload limits. */\nexport function packSource(root: string, limits: ArtifactLimits = DEFAULT_LIMITS): PackedSource {\n const ignores = loadIgnores(root);\n const files: string[] = [];\n const skippedSecrets: string[] = [];\n let sizeBytes = 0;\n\n const walk = (directory: string): void => {\n for (const entry of readdirSync(directory, { withFileTypes: true }).sort((a, b) => (a.name < b.name ? -1 : 1))) {\n const absolute = join(directory, entry.name);\n const path = relative(root, absolute).split(sep).join('/');\n if (SECRET_LOOKING.test(path)) {\n skippedSecrets.push(path);\n continue;\n }\n if (ignores.some((pattern) => matches(pattern, path))) {\n continue;\n }\n if (entry.isSymbolicLink()) continue; // never follow links out of the tree\n if (entry.isDirectory()) { walk(absolute); continue; }\n const size = statSync(absolute).size;\n if (size > limits.maxFileBytes) {\n throw new TinyError('RESOURCE_LIMIT_EXCEEDED', `File ${path} is larger than the ${limits.maxFileBytes} byte limit.`, {\n remediation: 'Add the file to .tinyignore, or store large assets in object storage instead.',\n details: { path, size },\n });\n }\n files.push(path);\n sizeBytes += size;\n if (files.length > limits.maxFiles || sizeBytes > limits.maxTotalBytes) {\n throw new TinyError('RESOURCE_LIMIT_EXCEEDED', 'Source upload exceeds the size limits for an app.', {\n remediation: 'Add build output and vendored dependencies to .tinyignore.',\n details: { files: files.length, sizeBytes, limits },\n });\n }\n }\n };\n walk(root);\n\n // The digest covers paths and contents, so a rename changes the artifact.\n const hash = createHash('sha256');\n for (const path of files) {\n hash.update(path);\n hash.update(readFileSync(join(root, path)));\n }\n\n return { files, sizeBytes, sha256: hash.digest('hex'), skippedSecrets };\n}\n\nexport interface StoredArtifact {\n uri: string;\n sha256: string;\n path: string;\n sizeBytes: number;\n files: number;\n}\n\nexport interface ArtifactUploadFile {\n /** Portable, relative POSIX path. */\n path: string;\n /** File contents encoded as base64. */\n content: string;\n}\n\nexport interface ArtifactUpload {\n files: ArtifactUploadFile[];\n sha256: string;\n}\n\nfunction safeUploadPath(path: string): string {\n const portable = path.replaceAll('\\\\', '/');\n const normalized = normalize(portable).replaceAll('\\\\', '/');\n if (!portable || portable.startsWith('/') || normalized === '..' || normalized.startsWith('../')\n || normalized !== portable || portable.includes('\\0')) {\n throw new TinyError('VALIDATION_FAILED', `Artifact path \"${path}\" is not a safe relative path.`, {\n remediation: 'Upload only normalized relative paths from the application directory.',\n });\n }\n if (SECRET_LOOKING.test(portable)) {\n throw new TinyError('VALIDATION_FAILED', `Artifact contains secret-looking file \"${path}\".`, {\n remediation: 'Remove credentials from the artifact and use Tinycloud secret references.',\n });\n }\n return portable;\n}\n\n/** Build the transport-safe representation used by the remote API. */\nexport function createArtifactUpload(sourceRoot: string, packed = packSource(sourceRoot)): ArtifactUpload {\n return {\n sha256: packed.sha256,\n files: packed.files.map((path) => ({ path, content: readFileSync(join(sourceRoot, path)).toString('base64') })),\n };\n}\n\nexport class ArtifactStore {\n private readonly root: string;\n\n constructor(root: string) {\n this.root = root;\n mkdirSync(this.root, { recursive: true });\n }\n\n /**\n * Store a source tree. Storing the same contents twice is a no-op that\n * returns the existing artifact \u2014 the property rollback depends on.\n */\n store(sourceRoot: string, packed: PackedSource): StoredArtifact {\n const path = join(this.root, packed.sha256);\n if (!existsSync(path)) {\n const staging = `${path}.${randomUUID()}.staging`;\n for (const file of packed.files) {\n const destination = join(staging, file);\n mkdirSync(join(destination, '..'), { recursive: true });\n cpSync(join(sourceRoot, file), destination);\n }\n writeFileSync(join(staging, '.tiny-manifest.json'), JSON.stringify({\n sha256: packed.sha256, files: packed.files, sizeBytes: packed.sizeBytes,\n storedAt: new Date().toISOString(),\n }, null, 2));\n // Rename last so a crash mid-copy never leaves a half artifact addressed\n // by a digest that promises complete contents.\n try { renameSync(staging, path); }\n catch (error) {\n rmSync(staging, { recursive: true, force: true });\n if (!existsSync(path)) throw error;\n }\n }\n return {\n uri: `artifact://${packed.sha256}`,\n sha256: packed.sha256,\n path,\n sizeBytes: packed.sizeBytes,\n files: packed.files.length,\n };\n }\n\n /**\n * Persist a remote upload without ever accepting a server-side source path.\n * The server recomputes the digest over decoded bytes and rejects duplicates,\n * traversal, secret-looking names, malformed base64, and oversized payloads.\n */\n storeUpload(upload: ArtifactUpload, limits: ArtifactLimits = DEFAULT_LIMITS): StoredArtifact {\n if (!/^[a-f0-9]{64}$/.test(upload.sha256)) {\n throw new TinyError('VALIDATION_FAILED', 'Artifact sha256 must be a lowercase 64-character digest.');\n }\n if (!Array.isArray(upload.files) || upload.files.length === 0 || upload.files.length > limits.maxFiles) {\n throw new TinyError('RESOURCE_LIMIT_EXCEEDED', `Artifact must contain between 1 and ${limits.maxFiles} files.`);\n }\n\n const decoded = new Map<string, Buffer>();\n let sizeBytes = 0;\n for (const entry of upload.files) {\n const path = safeUploadPath(String(entry.path ?? ''));\n if (decoded.has(path)) throw new TinyError('VALIDATION_FAILED', `Artifact contains duplicate path \"${path}\".`);\n if (typeof entry.content !== 'string' || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(entry.content)) {\n throw new TinyError('VALIDATION_FAILED', `Artifact file \"${path}\" is not valid canonical base64.`);\n }\n const bytes = Buffer.from(entry.content, 'base64');\n if (bytes.byteLength > limits.maxFileBytes) {\n throw new TinyError('RESOURCE_LIMIT_EXCEEDED', `File ${path} exceeds the per-file limit.`, {\n details: { path, size: bytes.byteLength, limit: limits.maxFileBytes },\n });\n }\n sizeBytes += bytes.byteLength;\n if (sizeBytes > limits.maxTotalBytes) {\n throw new TinyError('RESOURCE_LIMIT_EXCEEDED', 'Artifact exceeds the total upload limit.', {\n details: { sizeBytes, limit: limits.maxTotalBytes },\n });\n }\n decoded.set(path, bytes);\n }\n\n const files = [...decoded.keys()].sort();\n const hash = createHash('sha256');\n for (const path of files) {\n hash.update(path);\n hash.update(decoded.get(path)!);\n }\n const actual = hash.digest('hex');\n if (actual !== upload.sha256) {\n throw new TinyError('CONFLICT', 'Artifact digest does not match its decoded contents.', {\n remediation: 'Re-pack the source and retry the upload.', details: { expected: upload.sha256, actual },\n });\n }\n\n const path = join(this.root, actual);\n if (!existsSync(path)) {\n const staging = `${path}.${randomUUID()}.staging`;\n mkdirSync(staging, { recursive: true });\n for (const file of files) {\n const destination = join(staging, file);\n mkdirSync(dirname(destination), { recursive: true });\n writeFileSync(destination, decoded.get(file)!, { mode: 0o600 });\n }\n writeFileSync(join(staging, '.tiny-manifest.json'), JSON.stringify({\n sha256: actual, files, sizeBytes, storedAt: new Date().toISOString(),\n }, null, 2), { mode: 0o600 });\n try { renameSync(staging, path); }\n catch (error) {\n rmSync(staging, { recursive: true, force: true });\n if (!existsSync(path)) throw error;\n }\n }\n return { uri: `artifact://${actual}`, sha256: actual, path, sizeBytes, files: files.length };\n }\n\n resolve(uri: string): string {\n const match = /^artifact:\\/\\/([a-f0-9]{64})$/.exec(uri);\n if (!match) throw new TinyError('VALIDATION_FAILED', 'Artifact URI is malformed.');\n const digest = match[1]!;\n const path = join(this.root, digest);\n if (!existsSync(path)) {\n throw new TinyError('NOT_FOUND', `Artifact ${uri} is missing from the artifact store.`, {\n remediation: 'Deploy the revision again to re-upload its artifact.',\n details: { uri },\n });\n }\n return path;\n }\n\n describe(uri: string): StoredArtifact & { fileList: string[] } {\n const path = this.resolve(uri);\n const listing = JSON.parse(readFileSync(join(path, '.tiny-manifest.json'), 'utf8')) as {\n files: string[]; sha256: string; sizeBytes: number;\n };\n return {\n uri, path, sha256: listing.sha256, sizeBytes: listing.sizeBytes,\n files: listing.files.length, fileList: listing.files,\n };\n }\n\n has(sha256: string): boolean {\n return existsSync(join(this.root, sha256));\n }\n\n /** Verify stored bytes still match the digest before running them (\u00A715.3). */\n verify(uri: string): boolean {\n const path = this.resolve(uri);\n const listing = JSON.parse(readFileSync(join(path, '.tiny-manifest.json'), 'utf8')) as { files: string[]; sha256: string };\n const hash = createHash('sha256');\n for (const file of listing.files) {\n hash.update(file);\n hash.update(readFileSync(join(path, file)));\n }\n return hash.digest('hex') === listing.sha256;\n }\n\n remove(uri: string): void {\n rmSync(join(this.root, uri.replace('artifact://', '')), { recursive: true, force: true });\n }\n}\n", "/**\n * SQLite database resource provider (blueprint \u00A712.1).\n *\n * The database contract is what apps depend on: one database per app and\n * environment, reachable only through the platform binding, with migrations\n * applied by the control plane rather than by app code at boot.\n */\nimport { createHash } from 'node:crypto';\nimport { copyFileSync, mkdirSync, rmSync, statSync, existsSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { DatabaseSync } from 'node:sqlite';\nimport { TinyError } from '@tinycloud/domain';\nimport type {\n DestroyReceipt, ResourceBindingOutput, ResourceClaimInput, ResourcePlan,\n ResourceProvider, SnapshotRef,\n} from '@tinycloud/runtime-provider';\nimport { loadMigrations, planMigrations, type AppliedMigration, type MigrationPlan } from './migrations.ts';\n\nexport * from './migrations.ts';\n\nexport interface SqliteResourceOptions {\n /** Root directory that holds one database file per environment. */\n root: string;\n}\n\nexport class SqliteResourceProvider implements ResourceProvider {\n readonly kind = 'sqlite';\n readonly handles = 'database' as const;\n private readonly root: string;\n\n constructor(options: SqliteResourceOptions) {\n // Bindings are handed to runtimes with a different cwd, so paths the\n // provider hands out must be absolute.\n this.root = resolve(options.root);\n mkdirSync(this.root, { recursive: true });\n }\n\n private pathFor(claim: ResourceClaimInput): string {\n return join(this.root, claim.environmentId, `${claim.name}.sqlite`);\n }\n\n async plan(claims: ResourceClaimInput[]): Promise<ResourcePlan> {\n const steps = claims.map((claim) => ({\n kind: existsSync(this.pathFor(claim)) ? ('noop' as const) : ('create' as const),\n resource: `database/${claim.name}`,\n detail: existsSync(this.pathFor(claim))\n ? `Reuse existing ${claim.spec.engine ?? 'sqlite'} database \"${claim.name}\".`\n : `Provision a ${claim.spec.engine ?? 'sqlite'} database \"${claim.name}\".`,\n }));\n return { steps, warnings: [] };\n }\n\n async reconcile(claims: ResourceClaimInput[]): Promise<ResourceBindingOutput[]> {\n return claims.map((claim) => {\n const path = this.pathFor(claim);\n mkdirSync(dirname(path), { recursive: true });\n // Opening creates the file; closing immediately keeps no handle open in\n // the control plane, which owns provisioning but never app data access.\n new DatabaseSync(path).close();\n return {\n claimId: claim.claimId,\n environmentId: claim.environmentId,\n kind: 'database' as const,\n name: claim.name,\n providerKind: this.kind,\n providerRef: path,\n metadata: { engine: 'sqlite', path, url: `file:${path}` },\n };\n });\n }\n\n async snapshot(binding: ResourceBindingOutput): Promise<SnapshotRef> {\n const source = binding.providerRef;\n const takenAt = new Date().toISOString();\n const target = `${source}.${takenAt.replace(/[:.]/g, '-')}.snapshot`;\n // `VACUUM INTO` produces a consistent copy without stopping writers.\n const db = new DatabaseSync(source);\n try {\n db.exec(`vacuum into '${target.replace(/'/g, \"''\")}'`);\n } finally {\n db.close();\n }\n return { claimId: binding.claimId, providerRef: target, takenAt, sizeBytes: statSync(target).size };\n }\n\n async restore(snapshot: SnapshotRef): Promise<ResourceBindingOutput> {\n const target = snapshot.providerRef.replace(/\\.[^.]+\\.snapshot$/, '');\n if (!existsSync(snapshot.providerRef)) {\n throw new TinyError('NOT_FOUND', `Snapshot ${snapshot.providerRef} is missing.`, {\n remediation: 'List available snapshots for this environment and restore an existing one.',\n });\n }\n copyFileSync(snapshot.providerRef, target);\n return {\n claimId: snapshot.claimId,\n environmentId: '',\n kind: 'database',\n name: target.split('/').at(-1)!.replace('.sqlite', ''),\n providerKind: this.kind,\n providerRef: target,\n metadata: { engine: 'sqlite', path: target, restoredFrom: snapshot.providerRef },\n };\n }\n\n async destroy(binding: ResourceBindingOutput): Promise<DestroyReceipt> {\n rmSync(binding.providerRef, { force: true });\n rmSync(`${binding.providerRef}-wal`, { force: true });\n rmSync(`${binding.providerRef}-shm`, { force: true });\n const residual = existsSync(binding.providerRef) ? [binding.providerRef] : [];\n return { providerRef: binding.providerRef, destroyedAt: new Date().toISOString(), residual };\n }\n\n /** Plan the migrations an environment still needs, without applying them. */\n planMigrations(directory: string, applied: AppliedMigration[]): MigrationPlan {\n return planMigrations(loadMigrations(directory), applied);\n }\n\n /**\n * Apply pending migrations inside one transaction per migration, holding an\n * advisory lock for the environment so two deploys cannot interleave (\u00A712.3).\n */\n applyMigrations(\n binding: ResourceBindingOutput,\n plan: MigrationPlan,\n ): Array<{ name: string; checksum: string; durationMs: number }> {\n if (plan.pending.length === 0) return [];\n const db = new DatabaseSync(binding.providerRef);\n const results: Array<{ name: string; checksum: string; durationMs: number }> = [];\n try {\n db.exec('create table if not exists _tiny_migration_lock (id integer primary key check (id = 1), holder text, acquired_at text)');\n db.exec('create table if not exists _tiny_migrations (name text primary key, checksum text not null, applied_at text not null)');\n\n const holder = `${process.pid}:${Date.now()}`;\n const acquired = db.prepare(\n 'insert into _tiny_migration_lock (id, holder, acquired_at) values (1, ?, ?) on conflict(id) do nothing',\n ).run(holder, new Date().toISOString());\n if (Number(acquired.changes) === 0) {\n const current = db.prepare('select holder, acquired_at from _tiny_migration_lock where id = 1').get() as\n { holder: string; acquired_at: string } | undefined;\n throw new TinyError('MIGRATION_LOCKED', 'Another migration run holds the lock for this environment.', {\n remediation: 'Wait for the in-flight deployment to finish and retry.',\n retryable: true,\n details: { holder: current?.holder ?? null, since: current?.acquired_at ?? null },\n });\n }\n\n try {\n for (const migration of plan.pending) {\n const started = Date.now();\n db.exec('begin immediate');\n try {\n db.exec(migration.sql);\n db.prepare('insert into _tiny_migrations (name, checksum, applied_at) values (?,?,?)')\n .run(migration.name, migration.checksum, new Date().toISOString());\n db.exec('commit');\n } catch (error) {\n db.exec('rollback');\n throw new TinyError('MIGRATION_FAILED', `Migration ${migration.name} failed: ${(error as Error).message}`, {\n remediation: 'Fix the migration SQL and deploy again. Earlier migrations remain applied.',\n details: { migration: migration.name, appliedBefore: results.map((r) => r.name) },\n cause: error,\n });\n }\n results.push({ name: migration.name, checksum: migration.checksum, durationMs: Date.now() - started });\n }\n } finally {\n db.exec('delete from _tiny_migration_lock where id = 1');\n }\n } finally {\n db.close();\n }\n return results;\n }\n\n /** Bytes on disk, reported as a usage meter (\u00A726.1). */\n sizeBytes(binding: ResourceBindingOutput): number {\n try {\n return statSync(binding.providerRef).size;\n } catch {\n return 0;\n }\n }\n}\n\nexport function checksum(sql: string): string {\n return createHash('sha256').update(sql).digest('hex');\n}\n", "/**\n * The provider contract suite (blueprint \u00A730.1).\n *\n * Any implementation of `RuntimeProvider` must pass these tests unchanged.\n * They encode the properties the orchestrator relies on \u2014 idempotency,\n * adoption after a lost response, typed destroy receipts \u2014 rather than the\n * behaviour of any one cloud.\n */\nimport assert from 'node:assert/strict';\nimport test from 'node:test';\nimport type { DeploymentDesiredState, ReconcileRequest, RuntimeProvider } from './types.ts';\n\nexport interface ProviderContractHarness {\n /** Fresh provider per test run. */\n createProvider(): Promise<RuntimeProvider> | RuntimeProvider;\n /** A desired state the provider is expected to be able to realize. */\n createDesiredState(): Promise<DeploymentDesiredState> | DeploymentDesiredState;\n /** Called after each test so the harness can remove temporary state. */\n cleanup?(): Promise<void> | void;\n /** Skip liveness assertions for providers that cannot serve traffic locally. */\n servesTraffic?: boolean;\n}\n\nexport function runProviderContractTests(name: string, harness: ProviderContractHarness): void {\n const request = (desired: DeploymentDesiredState, operationKey: string): ReconcileRequest =>\n ({ ...desired, operationKey });\n\n test(`${name}: reports capabilities`, async () => {\n const provider = await harness.createProvider();\n const capabilities = await provider.capabilities();\n assert.ok(capabilities.runtimeTypes.length > 0, 'must support at least one runtime type');\n assert.ok(capabilities.maxMemoryMiB > 0);\n await harness.cleanup?.();\n });\n\n test(`${name}: plan is non-mutating and describes steps`, async () => {\n const provider = await harness.createProvider();\n const desired = await harness.createDesiredState();\n const plan = await provider.plan(desired);\n assert.equal(plan.providerKind, provider.kind);\n assert.ok(plan.steps.length > 0, 'plan must describe at least one step');\n const observed = await provider.inspect(desired.ref);\n assert.ok(['destroyed', 'stopped'].includes(observed.state), 'plan must not create anything');\n await harness.cleanup?.();\n });\n\n test(`${name}: reconcile is idempotent under a stable operation key`, async () => {\n const provider = await harness.createProvider();\n const desired = await harness.createDesiredState();\n const first = await provider.reconcile(request(desired, 'op-1'));\n const second = await provider.reconcile(request(desired, 'op-1'));\n assert.equal(second.providerRef, first.providerRef, 'retry must adopt the existing instance');\n assert.equal(second.state, 'running');\n assert.ok(second.healthy);\n await provider.destroy(desired.ref);\n await harness.cleanup?.();\n });\n\n test(`${name}: inspect reflects reconciled state`, async () => {\n const provider = await harness.createProvider();\n const desired = await harness.createDesiredState();\n const reconciled = await provider.reconcile(request(desired, 'op-1'));\n const observed = await provider.inspect(desired.ref);\n assert.equal(observed.providerRef, reconciled.providerRef);\n assert.equal(observed.state, 'running');\n await provider.destroy(desired.ref);\n await harness.cleanup?.();\n });\n\n test(`${name}: sleep and wake preserve identity`, async () => {\n const provider = await harness.createProvider();\n const capabilities = await provider.capabilities();\n const desired = await harness.createDesiredState();\n const reconciled = await provider.reconcile(request(desired, 'op-1'));\n if (!capabilities.supportsSleep) {\n await provider.destroy(desired.ref);\n await harness.cleanup?.();\n return;\n }\n await provider.sleep(desired.ref);\n assert.equal((await provider.inspect(desired.ref)).state, 'sleeping');\n await provider.wake(desired.ref);\n const awake = await provider.inspect(desired.ref);\n assert.equal(awake.state, 'running');\n assert.equal(awake.ref.deploymentId, reconciled.ref.deploymentId);\n await provider.destroy(desired.ref);\n await harness.cleanup?.();\n });\n\n test(`${name}: destroy is idempotent and returns a receipt`, async () => {\n const provider = await harness.createProvider();\n const desired = await harness.createDesiredState();\n await provider.reconcile(request(desired, 'op-1'));\n const receipt = await provider.destroy(desired.ref);\n assert.ok(receipt.destroyedAt);\n assert.deepEqual(receipt.residual, [], 'a clean destroy must leave nothing behind');\n const again = await provider.destroy(desired.ref);\n assert.ok(again.destroyedAt, 'destroying an absent deployment must not throw');\n assert.equal((await provider.inspect(desired.ref)).state, 'destroyed');\n await harness.cleanup?.();\n });\n\n test(`${name}: route switch is atomic and reports the active deployment`, async () => {\n const provider = await harness.createProvider();\n const desired = await harness.createDesiredState();\n await provider.reconcile(request(desired, 'op-1'));\n const route = await provider.route({ hostname: desired.hostname, target: desired.ref });\n assert.equal(route.activeDeploymentId, desired.ref.deploymentId);\n assert.equal(route.hostname, desired.hostname);\n await provider.destroy(desired.ref);\n await harness.cleanup?.();\n });\n\n test(`${name}: logs are bounded and usage is reported`, async () => {\n const provider = await harness.createProvider();\n const desired = await harness.createDesiredState();\n await provider.reconcile(request(desired, 'op-1'));\n const collected = [];\n for await (const event of provider.logs({ ref: desired.ref, limit: 5 })) collected.push(event);\n assert.ok(collected.length <= 5, 'logs must honour the requested bound');\n const samples = await provider.usage({\n ref: desired.ref,\n since: new Date(Date.now() - 60_000).toISOString(),\n until: new Date().toISOString(),\n });\n assert.ok(Array.isArray(samples));\n await provider.destroy(desired.ref);\n await harness.cleanup?.();\n });\n}\n", "/**\n * The planner (blueprint \u00A715.2 step 4).\n *\n * A plan is non-mutating and is what both a human and an agent read before\n * anything changes: resource diff, migrations, policy implications, approvals,\n * and cost. `tiny plan` prints it; `tiny deploy` refuses to proceed if the plan\n * it was given no longer matches what the planner computes now.\n */\nimport { createHash } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ControlPlaneStore } from '@tinycloud/db';\nimport { TinyError, newId, type App, type Environment, type Id, type Organization } from '@tinycloud/domain';\nimport type { NormalizedManifest } from '@tinycloud/manifest-schema';\nimport { canonicalize } from '@tinycloud/manifest-schema';\nimport { requiredApprovals, riskTierFor, type ApprovalReason } from '@tinycloud/policy';\nimport { loadMigrations, planMigrations } from '@tinycloud/resource-sqlite';\nimport { checkCompatibility, type PlanStep, type RuntimeProvider } from '@tinycloud/runtime-provider';\n\nexport interface DeploymentPlanResult {\n planId: string;\n /** Digest of the inputs; a stale plan is rejected at deploy time. */\n planDigest: string;\n appId: Id<'app'>;\n environmentId: Id<'env'>;\n environmentName: string;\n hostname: string;\n revisionSha256: string;\n artifactSha256: string;\n steps: PlanStep[];\n migrations: { pending: string[]; destructive: string[]; alreadyApplied: number };\n capabilities: Array<{ operation: string; connection: string; status: 'granted' | 'requires_grant' | 'unknown_connection' }>;\n approvals: ApprovalReason[];\n riskTier: 'low' | 'medium' | 'high';\n estimatedMonthlyCents: number | null;\n warnings: string[];\n incompatibilities: string[];\n createdAt: string;\n expiresAt: string;\n}\n\n/** Lockfile names, mirrored from the builder so a plan can warn about them. */\nconst LOCKFILES: Record<string, string> = {\n npm: 'package-lock.json',\n pnpm: 'pnpm-lock.yaml',\n yarn: 'yarn.lock',\n bun: 'bun.lockb',\n};\n\n/** A plan older than this must be recomputed; policy may have changed. */\nconst PLAN_TTL_MS = 15 * 60 * 1000;\n\nexport interface PlanInput {\n store: ControlPlaneStore;\n provider: RuntimeProvider;\n organization: Organization;\n app: App;\n environment: Environment;\n manifest: NormalizedManifest;\n previousManifest: NormalizedManifest | null;\n /** Source directory, used to read migration files. */\n sourcePath: string;\n revisionSha256: string;\n artifactSha256: string;\n}\n\nexport async function planDeployment(input: PlanInput): Promise<DeploymentPlanResult> {\n const { store, provider, organization, app, environment, manifest } = input;\n\n const capabilities = await provider.capabilities();\n const incompatibilities = checkCompatibility(capabilities, manifest);\n\n const providerPlan = await provider.plan({\n ref: { deploymentId: 'dep_plan_preview', appId: app.id, environmentId: environment.id },\n organizationId: organization.id,\n appSlug: app.slug,\n environmentName: environment.name,\n hostname: environment.hostname,\n manifest,\n artifactPath: input.sourcePath,\n artifactSha256: input.artifactSha256,\n bindings: {},\n secrets: {},\n });\n\n const steps: PlanStep[] = [...providerPlan.steps];\n const warnings: string[] = [...providerPlan.warnings];\n\n // ---------------------------------------------------------------- build\n if (manifest.build.command) {\n steps.push({\n kind: 'create',\n resource: 'build',\n detail: `Run \"${manifest.build.command}\" with ${manifest.build.packageManager} in an isolated build VM`\n + `${manifest.build.output ? `, keeping ${manifest.build.output}` : ''}.`,\n });\n const lockfile = LOCKFILES[manifest.build.packageManager];\n if (lockfile && !existsSync(join(input.sourcePath, lockfile))) {\n warnings.push(\n `No ${lockfile} was uploaded, so the build will resolve dependencies fresh and two deploys of the same source `\n + 'may install different code. Commit the lockfile for a reproducible build.',\n );\n }\n if (!existsSync(join(input.sourcePath, 'package.json'))) {\n warnings.push('build.command is declared but the source has no package.json; the build will fail.');\n }\n }\n\n // ------------------------------------------------------------ resources\n const existingBindings = store.listResourceBindings(environment.id);\n const bound = new Set(existingBindings.map((binding) => `${binding.kind}/${binding.name}`));\n\n if (manifest.resources.database) {\n const key = `database/${manifest.resources.database.name}`;\n steps.push({\n kind: bound.has(key) ? 'noop' : 'create',\n resource: key,\n detail: bound.has(key)\n ? `Reuse the existing ${manifest.resources.database.engine} database.`\n : `Provision a ${manifest.resources.database.engine} database for ${environment.name}.`,\n });\n }\n for (const store_ of manifest.resources.storage) {\n const key = `storage/${store_.name}`;\n steps.push({\n kind: bound.has(key) ? 'noop' : 'create',\n resource: key,\n detail: `Object namespace \"${store_.name}\" capped at ${Math.round(store_.maxSizeBytes / 1024 ** 2)}Mi.`,\n });\n }\n // Resources present in the environment but no longer declared are reported,\n // never silently deleted \u2014 deletion is an explicit lifecycle action (\u00A717.3).\n for (const binding of existingBindings) {\n const stillDeclared = binding.kind === 'database'\n ? manifest.resources.database?.name === binding.name\n : manifest.resources.storage.some((entry) => entry.name === binding.name);\n if (!stillDeclared) {\n warnings.push(`${binding.kind} \"${binding.name}\" is no longer declared in tiny.yaml but still exists; use \\`tiny resources prune\\` to remove it.`);\n }\n }\n\n // ----------------------------------------------------------- migrations\n let pending: string[] = [];\n let destructive: string[] = [];\n const applied = store.listMigrationRuns(environment.id);\n if (manifest.resources.database) {\n const files = loadMigrations(join(input.sourcePath, manifest.resources.database.migrations));\n const migrationPlan = planMigrations(files, applied.map((entry) => ({ migrationName: entry.migrationName, checksum: entry.checksum })));\n pending = migrationPlan.pending.map((file) => file.name);\n destructive = migrationPlan.destructive;\n for (const name of pending) {\n steps.push({\n kind: 'update',\n resource: `migration/${name}`,\n detail: destructive.includes(name) ? `Apply ${name} (contains destructive statements).` : `Apply ${name}.`,\n });\n }\n }\n\n // --------------------------------------------------------- capabilities\n const grants = store.listCapabilityGrants(app.id);\n const capabilityStatuses = manifest.capabilities.flatMap((capability) => {\n const connection = store.findConnectionByName(organization.id, capability.connection);\n return capability.allow.map((operation) => {\n if (!connection) {\n return { operation, connection: capability.connection, status: 'unknown_connection' as const };\n }\n const grant = grants.find((entry) =>\n entry.connectionId === connection.id\n && entry.status === 'approved'\n && entry.operations.includes(operation)\n && (entry.environmentId === null || entry.environmentId === environment.id));\n return {\n operation,\n connection: capability.connection,\n status: grant ? ('granted' as const) : ('requires_grant' as const),\n };\n });\n });\n\n for (const capability of capabilityStatuses) {\n if (capability.status === 'unknown_connection') {\n warnings.push(`Connection \"${capability.connection}\" does not exist in this organization; create it before deploying.`);\n }\n }\n\n // ------------------------------------------------------------ approvals\n const approvals = requiredApprovals({\n environmentKind: environment.kind,\n policy: organization.policy,\n next: manifest,\n previous: input.previousManifest,\n destructiveMigrations: destructive,\n });\n\n // ------------------------------------------------------------ estimate\n const estimatedMonthlyCents = estimateCost(manifest, providerPlan.estimatedMonthlyCents);\n\n const createdAt = new Date();\n const result: Omit<DeploymentPlanResult, 'planDigest'> = {\n planId: newId('plan'),\n appId: app.id,\n environmentId: environment.id,\n environmentName: environment.name,\n hostname: environment.hostname,\n revisionSha256: input.revisionSha256,\n artifactSha256: input.artifactSha256,\n steps,\n migrations: { pending, destructive, alreadyApplied: applied.length },\n capabilities: capabilityStatuses,\n approvals,\n riskTier: riskTierFor(manifest),\n estimatedMonthlyCents,\n warnings,\n incompatibilities,\n createdAt: createdAt.toISOString(),\n expiresAt: new Date(createdAt.getTime() + PLAN_TTL_MS).toISOString(),\n };\n\n return { ...result, planDigest: digestPlan(result) };\n}\n\n/**\n * The digest covers what the deploy would actually do. It deliberately omits\n * the plan ID and timestamps so recomputing an unchanged plan produces the\n * same digest.\n */\nexport function digestPlan(plan: Omit<DeploymentPlanResult, 'planDigest'>): string {\n return createHash('sha256').update(canonicalize({\n revisionSha256: plan.revisionSha256,\n artifactSha256: plan.artifactSha256,\n environmentName: plan.environmentName,\n hostname: plan.hostname,\n steps: plan.steps,\n migrations: plan.migrations,\n capabilities: plan.capabilities,\n approvals: plan.approvals.map((approval) => approval.code + (approval.subject ?? '')),\n })).digest('hex');\n}\n\nexport function assertPlanUsable(plan: DeploymentPlanResult, now = new Date()): void {\n if (plan.incompatibilities.length > 0) {\n throw new TinyError('TARGET_INCOMPATIBLE', `The runtime target cannot run this app: ${plan.incompatibilities.join('; ')}`, {\n details: { incompatibilities: plan.incompatibilities },\n });\n }\n if (new Date(plan.expiresAt) < now) {\n throw new TinyError('PLAN_EXPIRED', 'This deployment plan has expired.', {\n remediation: 'Run `tiny plan` again and deploy with the new plan ID.',\n details: { planId: plan.planId, expiresAt: plan.expiresAt },\n });\n }\n}\n\n/**\n * A deliberately transparent estimate. Getting it wrong is expected; hiding it\n * is not \u2014 an agent deploying fifty apps should see the aggregate before a\n * finance team does (\u00A726.3).\n */\nfunction estimateCost(manifest: NormalizedManifest, providerCents: number | null): number | null {\n if (providerCents === null) return null;\n const databaseCents = manifest.resources.database ? 50 : 0;\n const storageCents = manifest.resources.storage.reduce(\n (total, store) => total + Math.ceil(store.maxSizeBytes / 1024 ** 3) * 2, 0);\n const capabilityCents = manifest.capabilities.length * 10;\n return providerCents + databaseCents + storageCents + capabilityCents;\n}\n", "/**\n * Platform tokens (blueprint \u00A79).\n *\n * Four identities stay separate \u2014 human, agent, app workload, runtime \u2014 and\n * each token carries an explicit `aud` so a token minted for one boundary is\n * rejected at another. Tokens are compact `v2.<base64url(payload)>.<sig>`\n * strings signed with Ed25519. Runtime guests receive only the public verifier,\n * so compromise of one workload cannot mint platform tokens for another.\n */\nimport {\n createHash, createHmac, createPrivateKey, createPublicKey, randomBytes,\n scryptSync, sign as cryptoSign, timingSafeEqual, verify as cryptoVerify, type KeyObject,\n} from 'node:crypto';\nimport { TinyError } from '@tinycloud/domain';\n\nexport type TokenAudience = 'control-plane' | 'app-user' | 'app-workload' | 'runtime-agent' | 'capability-broker';\n\nexport interface TokenClaims {\n /** Subject: user ID, service account ID, or app deployment ID. */\n sub: string;\n aud: TokenAudience;\n org: string;\n /** Seconds since epoch. */\n iat: number;\n exp: number;\n jti: string;\n app?: string;\n env?: string;\n deployment?: string;\n roles?: string[];\n groups?: string[];\n email?: string;\n sid?: string;\n /** Authentication assurance level, surfaced to apps that care. */\n aal?: 'password' | 'mfa' | 'sso';\n}\n\nconst PREFIX = 'v2';\n\nfunction b64url(input: Buffer | string): string {\n return Buffer.from(input).toString('base64url');\n}\n\nfunction privateKeyFromSecret(secret: string): KeyObject {\n // RFC 8410 PKCS#8 prefix followed by a deterministic 32-byte Ed25519 seed.\n const seed = createHash('sha256').update('tinycloud/token-signing/v2\\0').update(secret).digest();\n return createPrivateKey({\n key: Buffer.concat([Buffer.from('302e020100300506032b657004220420', 'hex'), seed]),\n format: 'der', type: 'pkcs8',\n });\n}\n\nfunction verifyClaims(token: string, publicKey: KeyObject, expected: { aud: TokenAudience; org?: string }): TokenClaims {\n const parts = token.split('.');\n if (parts.length !== 3 || parts[0] !== PREFIX) {\n throw new TinyError('UNAUTHENTICATED', 'Malformed token.', { details: { reason: 'format' } });\n }\n const body = `${parts[0]}.${parts[1]}`;\n if (!cryptoVerify(null, Buffer.from(body), publicKey, Buffer.from(parts[2]!, 'base64url'))) {\n throw new TinyError('UNAUTHENTICATED', 'Token signature is invalid.', { details: { reason: 'signature' } });\n }\n let claims: TokenClaims;\n try {\n claims = JSON.parse(Buffer.from(parts[1]!, 'base64url').toString('utf8')) as TokenClaims;\n } catch {\n throw new TinyError('UNAUTHENTICATED', 'Token payload is not valid JSON.', { details: { reason: 'payload' } });\n }\n const now = Math.floor(Date.now() / 1000);\n if (claims.exp <= now) {\n throw new TinyError('UNAUTHENTICATED', 'Token has expired.', {\n remediation: 'Obtain a fresh token; run `tiny login` if this is a CLI session.',\n details: { reason: 'expired', expiredAt: claims.exp },\n });\n }\n if (claims.aud !== expected.aud) {\n throw new TinyError('UNAUTHENTICATED', `Token audience ${claims.aud} is not valid here.`, {\n details: { reason: 'audience', expected: expected.aud, found: claims.aud },\n });\n }\n if (expected.org && claims.org !== expected.org) {\n throw new TinyError('FORBIDDEN', 'Token belongs to a different organization.', { details: { reason: 'organization' } });\n }\n return claims;\n}\n\nexport class TokenIssuer {\n private readonly privateKey: KeyObject;\n private readonly publicKey: KeyObject;\n\n constructor(secret: string) {\n if (secret.length < 32) {\n throw new TinyError('INTERNAL', 'Token signing secret must be at least 32 characters.', {\n remediation: 'Set TINY_TOKEN_SECRET to a random 32+ character value.',\n });\n }\n this.privateKey = privateKeyFromSecret(secret);\n this.publicKey = createPublicKey(this.privateKey);\n }\n\n issue(claims: Omit<TokenClaims, 'iat' | 'exp' | 'jti'>, ttlSeconds: number): string {\n const now = Math.floor(Date.now() / 1000);\n const full: TokenClaims = {\n ...claims,\n iat: now,\n exp: now + ttlSeconds,\n jti: randomBytes(12).toString('base64url'),\n };\n const payload = b64url(JSON.stringify(full));\n const body = `${PREFIX}.${payload}`;\n return `${body}.${cryptoSign(null, Buffer.from(body), this.privateKey).toString('base64url')}`;\n }\n\n verify(token: string, expected: { aud: TokenAudience; org?: string }): TokenClaims {\n return verifyClaims(token, this.publicKey, expected);\n }\n\n /** SPKI DER, base64url encoded; safe to distribute to untrusted runtimes. */\n verificationKey(): string {\n return this.publicKey.export({ format: 'der', type: 'spki' }).toString('base64url');\n }\n}\n\nexport class TokenVerifier {\n private readonly publicKey: KeyObject;\n\n constructor(verificationKey: string) {\n try {\n this.publicKey = createPublicKey({ key: Buffer.from(verificationKey, 'base64url'), format: 'der', type: 'spki' });\n } catch (cause) {\n throw new TinyError('INTERNAL', 'Token verification key is invalid.', { cause });\n }\n }\n\n verify(token: string, expected: { aud: TokenAudience; org?: string }): TokenClaims {\n return verifyClaims(token, this.publicKey, expected);\n }\n}\n\n/** TTLs are deliberately short; the gateway re-mints on every request (\u00A79.3). */\nexport const TOKEN_TTL = {\n session: 60 * 60 * 12,\n appUser: 60 * 4,\n appWorkload: 60 * 10,\n runtimeAgent: 60 * 15,\n cli: 60 * 60 * 24 * 30,\n} as const;\n\n/** Service-account/CLI tokens are opaque; only their hash is stored. */\nexport function generateApiToken(): { token: string; hash: string } {\n const token = `tiny_sk_${randomBytes(24).toString('base64url')}`;\n return { token, hash: hashApiToken(token) };\n}\n\nexport function hashApiToken(token: string): string {\n return createHmac('sha256', 'tinycloud/api-token').update(token).digest('hex');\n}\n\n/** Password credentials use an independent random salt and only persist the derived key. */\nexport function hashPassword(password: string): { salt: string; hash: string } {\n const salt = randomBytes(16).toString('base64url');\n return { salt, hash: scryptSync(password, salt, 32).toString('base64url') };\n}\n\nexport function verifyPassword(password: string, salt: string, expected: string): boolean {\n const actual = scryptSync(password, salt, 32);\n const stored = Buffer.from(expected, 'base64url');\n return stored.length === actual.length && timingSafeEqual(stored, actual);\n}\n", "/**\n * Data service (blueprint \u00A712.4).\n *\n * Durable data cannot live inside a replaceable microVM, and the guest cannot\n * be trusted with a database credential \u2014 a generated app that can print its\n * own connection string is one prompt away from leaking it. So the guest gets\n * no credential at all. It sends SQL to the control plane over the same\n * private TAP link it already uses for the capability broker, authenticated by\n * the workload token the deployer minted for that one deployment.\n *\n * The service resolves the token to exactly one environment's binding, so an\n * app cannot address another tenant's database even by guessing. Everything\n * beyond that is bounds: statement size, row count, result size, transaction\n * lifetime, and concurrent sessions.\n */\nimport { randomUUID } from 'node:crypto';\nimport { DatabaseSync } from 'node:sqlite';\nimport type { TokenIssuer } from '@tinycloud/auth';\nimport type { ControlPlaneStore } from '@tinycloud/db';\nimport { TinyError, type Id } from '@tinycloud/domain';\nimport type { ResourceBindingOutput } from '@tinycloud/runtime-provider';\nimport { METERS, type Telemetry } from '@tinycloud/telemetry';\n\n/** Limits chosen so one app cannot make the control plane everyone's problem. */\nexport const DATA_LIMITS = {\n maxSqlBytes: 100_000,\n maxParameters: 256,\n maxRows: 10_000,\n maxResultBytes: 8 * 1024 * 1024,\n transactionTtlMs: 15_000,\n /**\n * Postgres serves concurrent transactions from its pool. SQLite has exactly\n * one writer per database, and pretending otherwise would just move the\n * failure to a lock timeout in the middle of someone's request.\n */\n maxOpenTransactionsPerEnvironment: 4,\n maxOpenTransactionsPerSqliteEnvironment: 1,\n maxConcurrentStatementsPerEnvironment: 16,\n};\n\nexport interface DataQueryResult {\n rows: Array<Record<string, unknown>>;\n rowCount: number;\n /** Present for reads; `execute` reports `changes` instead. */\n fields?: Array<{ name: string }>;\n}\n\n/** The engine-specific half of the service. */\ninterface DataSession {\n query(sql: string, params: unknown[]): Promise<DataQueryResult>;\n execute(sql: string, params: unknown[]): Promise<{ changes: number }>;\n begin(): Promise<void>;\n commit(): Promise<void>;\n rollback(): Promise<void>;\n release(): void;\n}\n\n/** A Postgres pool, as much of it as the data service needs. */\nexport interface PostgresPoolLike {\n acquire(): Promise<PostgresConnectionLike>;\n release(connection: PostgresConnectionLike): void;\n}\n\nexport interface PostgresConnectionLike {\n query<T = Record<string, unknown>>(sql: string, params?: unknown[]): Promise<{\n rows: T[]; rowCount: number; fields: Array<{ name: string }>; command: string;\n }>;\n simple(sql: string): Promise<unknown>;\n}\n\nexport interface PostgresBackend {\n poolFor(binding: ResourceBindingOutput): PostgresPoolLike;\n}\n\nexport interface DataServiceDependencies {\n store: ControlPlaneStore;\n telemetry: Telemetry;\n issuer: TokenIssuer;\n /** Supplied when the operator configured an external Postgres cluster. */\n postgres?: PostgresBackend;\n}\n\ninterface OpenTransaction {\n id: string;\n environmentId: string;\n session: DataSession;\n expiresAt: number;\n timer: NodeJS.Timeout;\n}\n\nexport interface DataRequest {\n workloadToken: string;\n requestId: string;\n sql?: string;\n params?: unknown[];\n transactionId?: string;\n}\n\nexport class DataService {\n private readonly deps: DataServiceDependencies;\n private readonly sqliteConnections = new Map<string, DatabaseSync>();\n private readonly transactions = new Map<string, OpenTransaction>();\n /** Serializes writes per environment; SQLite has one writer by definition. */\n private readonly locks = new Map<string, Promise<unknown>>();\n private readonly inFlight = new Map<string, number>();\n\n constructor(deps: DataServiceDependencies) {\n this.deps = deps;\n }\n\n // ------------------------------------------------------------ resolution\n\n /**\n * Turn a workload token into the one database binding it may touch. A token\n * from a superseded or failed deployment is refused, so a stale VM that\n * somehow survives a route switch cannot keep writing.\n */\n private resolve(workloadToken: string): {\n binding: ResourceBindingOutput; environmentId: Id<'env'>; appId: Id<'app'>; organizationId: Id<'org'>;\n } {\n const claims = this.deps.issuer.verify(workloadToken, { aud: 'app-workload' });\n const deploymentId = claims.deployment as Id<'dep'> | undefined;\n const environmentId = claims.env as Id<'env'> | undefined;\n const appId = claims.app as Id<'app'> | undefined;\n const organizationId = claims.org as Id<'org'>;\n if (!deploymentId || !environmentId || !appId) {\n throw new TinyError('UNAUTHENTICATED', 'Workload token is missing its app, environment, or deployment binding.');\n }\n const deployment = this.deps.store.getDeployment(deploymentId);\n if (deployment.environmentId !== environmentId || deployment.appId !== appId\n || deployment.organizationId !== organizationId\n || !['ready', 'verifying', 'deploying', 'migrating'].includes(deployment.status)) {\n throw new TinyError('FORBIDDEN', 'Workload token is not bound to a live deployment.', {\n details: { deploymentId, status: deployment.status },\n });\n }\n const record = this.deps.store.listResourceBindings(environmentId).find((entry) => entry.kind === 'database');\n if (!record) {\n throw new TinyError('NOT_FOUND', 'This app has no database.', {\n remediation: 'Add resources.database to tiny.yaml and deploy again.',\n });\n }\n return {\n binding: {\n claimId: record.claimId,\n environmentId,\n kind: 'database',\n name: record.name,\n providerKind: record.providerKind,\n providerRef: record.providerRef,\n metadata: record.metadata,\n },\n environmentId,\n appId,\n organizationId,\n };\n }\n\n /**\n * `dedicated` sessions own their connection. A transaction must never run on\n * the shared connection: SQLite would enrol every concurrent statement for\n * that environment into the open transaction, so an unrelated read could be\n * rolled back by someone else's failure.\n */\n private sessionFor(binding: ResourceBindingOutput, dedicated = false): DataSession {\n if (binding.providerKind === 'postgres') {\n if (!this.deps.postgres) {\n throw new TinyError('TARGET_INCOMPATIBLE', 'This deployment is bound to Postgres but no Postgres backend is configured.', {\n remediation: 'Set TINY_POSTGRES_ADMIN_URL on the control plane and restart it.',\n });\n }\n return new PostgresSession(this.deps.postgres.poolFor(binding));\n }\n return dedicated\n ? new SqliteSession(this.openSqlite(binding.providerRef), true)\n : new SqliteSession(this.sqliteFor(binding), false);\n }\n\n private openSqlite(path: string): DatabaseSync {\n const db = new DatabaseSync(path);\n db.exec('pragma journal_mode = WAL');\n db.exec('pragma foreign_keys = on');\n db.exec('pragma busy_timeout = 5000');\n return db;\n }\n\n private sqliteFor(binding: ResourceBindingOutput): DatabaseSync {\n const existing = this.sqliteConnections.get(binding.providerRef);\n if (existing) return existing;\n const db = this.openSqlite(binding.providerRef);\n this.sqliteConnections.set(binding.providerRef, db);\n return db;\n }\n\n // ---------------------------------------------------------------- guards\n\n private assertStatement(sql: unknown, params: unknown): asserts sql is string {\n if (typeof sql !== 'string' || sql.trim().length === 0) {\n throw new TinyError('VALIDATION_FAILED', 'A SQL statement is required.');\n }\n if (Buffer.byteLength(sql) > DATA_LIMITS.maxSqlBytes) {\n throw new TinyError('RESOURCE_LIMIT_EXCEEDED', `SQL statement exceeds the ${DATA_LIMITS.maxSqlBytes} byte limit.`);\n }\n if (params !== undefined && (!Array.isArray(params) || params.length > DATA_LIMITS.maxParameters)) {\n throw new TinyError('VALIDATION_FAILED', `Parameters must be an array of at most ${DATA_LIMITS.maxParameters} values.`);\n }\n }\n\n private assertResultSize(result: DataQueryResult): DataQueryResult {\n if (result.rows.length > DATA_LIMITS.maxRows) {\n throw new TinyError('RESOURCE_LIMIT_EXCEEDED', `Query returned more than ${DATA_LIMITS.maxRows} rows.`, {\n remediation: 'Add a LIMIT clause and paginate.',\n });\n }\n // Binary is converted here rather than left to a JSON replacer, because a\n // Buffer carries its own `toJSON` and JSON.stringify applies that first \u2014\n // a replacer never sees the bytes. Doing it once, up front, is also what\n // makes the size measured below the size actually sent.\n const rows = result.rows.map(normalizeRow);\n const bytes = Buffer.byteLength(JSON.stringify(rows, jsonSafe));\n if (bytes > DATA_LIMITS.maxResultBytes) {\n throw new TinyError('RESOURCE_LIMIT_EXCEEDED', 'Query result exceeds the result size limit.', {\n remediation: 'Select fewer columns, or page through the result.',\n details: { bytes, limit: DATA_LIMITS.maxResultBytes },\n });\n }\n return { ...result, rows };\n }\n\n /** Serialize statements per environment and cap the queue depth. */\n private async serialize<T>(environmentId: string, fn: () => Promise<T>): Promise<T> {\n const depth = this.inFlight.get(environmentId) ?? 0;\n if (depth >= DATA_LIMITS.maxConcurrentStatementsPerEnvironment) {\n throw new TinyError('RATE_LIMITED', 'Too many concurrent database statements for this environment.', {\n retryable: true, details: { limit: DATA_LIMITS.maxConcurrentStatementsPerEnvironment },\n });\n }\n this.inFlight.set(environmentId, depth + 1);\n const previous = this.locks.get(environmentId) ?? Promise.resolve();\n const run = previous.then(fn, fn);\n this.locks.set(environmentId, run.then(() => undefined, () => undefined));\n try {\n return await run;\n } finally {\n this.inFlight.set(environmentId, (this.inFlight.get(environmentId) ?? 1) - 1);\n }\n }\n\n private meter(organizationId: Id<'org'>, appId: Id<'app'>, requestId: string, index: number): void {\n this.deps.telemetry.meter({\n organizationId, appId, meter: METERS.databaseBytes, quantity: 1,\n idempotencyKey: `data:${requestId}:${index}`,\n metadata: { kind: 'statement' },\n });\n }\n\n // -------------------------------------------------------------- requests\n\n /** Read or write outside a transaction. */\n async query(input: DataRequest & { write?: boolean }): Promise<DataQueryResult | { changes: number }> {\n this.assertStatement(input.sql, input.params);\n const target = this.resolve(input.workloadToken);\n const params = decodeParams(input.params ?? []);\n\n if (input.transactionId) {\n const open = this.requireTransaction(input.transactionId, target.environmentId);\n const result = input.write\n ? await open.session.execute(input.sql, params)\n : this.assertResultSize(await open.session.query(input.sql, params));\n this.meter(target.organizationId, target.appId, input.requestId, 1);\n return result;\n }\n\n return this.serialize(target.environmentId, async () => {\n const session = this.sessionFor(target.binding);\n try {\n const result = input.write\n ? await session.execute(input.sql!, params)\n : this.assertResultSize(await session.query(input.sql!, params));\n this.meter(target.organizationId, target.appId, input.requestId, 0);\n return result;\n } finally {\n session.release();\n }\n });\n }\n\n /**\n * Open a transaction. It is pinned to one backend session and reclaimed on a\n * timer, so a guest that crashes mid-transaction cannot hold a write lock or\n * a pooled Postgres connection forever.\n */\n async begin(input: DataRequest): Promise<{ transactionId: string; expiresAt: string }> {\n const target = this.resolve(input.workloadToken);\n const open = [...this.transactions.values()].filter((entry) => entry.environmentId === target.environmentId);\n const ceiling = target.binding.providerKind === 'postgres'\n ? DATA_LIMITS.maxOpenTransactionsPerEnvironment\n : DATA_LIMITS.maxOpenTransactionsPerSqliteEnvironment;\n if (open.length >= ceiling) {\n throw new TinyError('RATE_LIMITED', `Too many open transactions for this environment (limit ${ceiling}).`, {\n retryable: true,\n remediation: target.binding.providerKind === 'postgres'\n ? 'Commit or roll back before starting another transaction.'\n : 'SQLite allows one writing transaction at a time. Commit sooner, or use engine: postgres.',\n });\n }\n const session = this.sessionFor(target.binding, true);\n await session.begin();\n const id = randomUUID();\n const expiresAt = Date.now() + DATA_LIMITS.transactionTtlMs;\n const timer = setTimeout(() => { void this.reap(id); }, DATA_LIMITS.transactionTtlMs);\n timer.unref();\n this.transactions.set(id, { id, environmentId: target.environmentId, session, expiresAt, timer });\n return { transactionId: id, expiresAt: new Date(expiresAt).toISOString() };\n }\n\n async commit(input: DataRequest): Promise<{ committed: true }> {\n const target = this.resolve(input.workloadToken);\n const open = this.requireTransaction(input.transactionId, target.environmentId);\n try {\n await open.session.commit();\n } finally {\n this.close(open);\n }\n return { committed: true };\n }\n\n async rollback(input: DataRequest): Promise<{ rolledBack: true }> {\n const target = this.resolve(input.workloadToken);\n const open = this.requireTransaction(input.transactionId, target.environmentId);\n try {\n await open.session.rollback();\n } finally {\n this.close(open);\n }\n return { rolledBack: true };\n }\n\n private requireTransaction(id: string | undefined, environmentId: string): OpenTransaction {\n const open = id ? this.transactions.get(id) : undefined;\n // Checking the environment as well as the id means a leaked transaction id\n // is still useless to a different app.\n if (!open || open.environmentId !== environmentId) {\n throw new TinyError('NOT_FOUND', 'No open transaction with that id.', {\n remediation: 'Transactions expire after a few seconds; start a new one and retry.',\n });\n }\n return open;\n }\n\n private close(open: OpenTransaction): void {\n clearTimeout(open.timer);\n open.session.release();\n this.transactions.delete(open.id);\n }\n\n /** Roll back and discard a transaction whose owner never came back. */\n private async reap(id: string): Promise<void> {\n const open = this.transactions.get(id);\n if (!open) return;\n try { await open.session.rollback(); } catch { /* the session is already gone */ }\n this.close(open);\n }\n\n async shutdown(): Promise<void> {\n for (const open of [...this.transactions.values()]) await this.reap(open.id);\n for (const db of this.sqliteConnections.values()) db.close();\n this.sqliteConnections.clear();\n }\n}\n\n// --------------------------------------------------------------- sessions\n\nclass SqliteSession implements DataSession {\n private readonly db: DatabaseSync;\n private readonly owned: boolean;\n\n constructor(db: DatabaseSync, owned: boolean) {\n this.db = db;\n this.owned = owned;\n }\n\n async query(sql: string, params: unknown[]): Promise<DataQueryResult> {\n const rows = this.db.prepare(sql).all(...(params as never[])) as Array<Record<string, unknown>>;\n return { rows, rowCount: rows.length, fields: Object.keys(rows[0] ?? {}).map((name) => ({ name })) };\n }\n\n async execute(sql: string, params: unknown[]): Promise<{ changes: number }> {\n return { changes: Number(this.db.prepare(sql).run(...(params as never[])).changes) };\n }\n\n async begin(): Promise<void> { this.db.exec('begin immediate'); }\n async commit(): Promise<void> { this.db.exec('commit'); }\n async rollback(): Promise<void> { this.db.exec('rollback'); }\n\n /** Shared connections are cached per environment; owned ones are closed. */\n release(): void {\n if (this.owned) this.db.close();\n }\n}\n\nclass PostgresSession implements DataSession {\n private connection: PostgresConnectionLike | null = null;\n private readonly pool: PostgresPoolLike;\n\n constructor(pool: PostgresPoolLike) {\n this.pool = pool;\n }\n\n private async borrow(): Promise<PostgresConnectionLike> {\n this.connection ??= await this.pool.acquire();\n return this.connection;\n }\n\n async query(sql: string, params: unknown[]): Promise<DataQueryResult> {\n const connection = await this.borrow();\n const result = await connection.query(sql, params);\n return { rows: result.rows, rowCount: result.rowCount, fields: result.fields.map(({ name }) => ({ name })) };\n }\n\n async execute(sql: string, params: unknown[]): Promise<{ changes: number }> {\n const connection = await this.borrow();\n return { changes: (await connection.query(sql, params)).rowCount };\n }\n\n async begin(): Promise<void> { await (await this.borrow()).simple('begin'); }\n async commit(): Promise<void> { await (await this.borrow()).simple('commit'); }\n async rollback(): Promise<void> { await (await this.borrow()).simple('rollback'); }\n\n release(): void {\n if (this.connection) {\n this.pool.release(this.connection);\n this.connection = null;\n }\n }\n}\n\n/**\n * Binary leaves as base64, whichever engine produced it. SQLite hands back a\n * plain Uint8Array and Postgres a Buffer, and left alone those serialize two\n * different ways \u2014 so the same column would reach an app differently depending\n * on the engine behind its binding.\n */\nfunction normalizeRow(row: Record<string, unknown>): Record<string, unknown> {\n let copy: Record<string, unknown> | null = null;\n for (const [key, value] of Object.entries(row)) {\n if (!(value instanceof Uint8Array)) continue;\n copy ??= { ...row };\n copy[key] = Buffer.from(value).toString('base64');\n }\n return copy ?? row;\n}\n\n/**\n * Undo the SDK's binary tagging. Only an object that is exactly `{ $binary }`\n * with a string is a candidate, so an app's own JSON value that happens to\n * carry that key keeps its shape unless it is precisely this envelope.\n */\nfunction decodeParams(params: unknown[]): unknown[] {\n return params.map((value) => {\n if (value === null || typeof value !== 'object') return value;\n const keys = Object.keys(value);\n if (keys.length !== 1 || keys[0] !== '$binary') return value;\n const encoded = (value as { $binary: unknown }).$binary;\n return typeof encoded === 'string' ? Buffer.from(encoded, 'base64') : value;\n });\n}\n\n/** BigInt and Buffer both appear in query results and neither is JSON. */\nfunction jsonSafe(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') return value.toString();\n if (value instanceof Uint8Array) return Buffer.from(value).toString('base64');\n return value;\n}\n\nexport { jsonSafe };\n", "/**\n * Object storage service (blueprint \u00A713).\n *\n * The same argument the data service makes for SQL, made for bytes. Durable\n * objects cannot live inside a replaceable microVM, and a host directory\n * cannot be mounted into a tenant VM without handing the guest a piece of the\n * host's filesystem. So the guest is given a namespace *name* and nothing\n * else: no bucket, no path, no credential. Objects travel to the control\n * plane over the same private link the capability broker already uses,\n * authenticated by the workload token the deployer minted for that one\n * deployment, and the control plane decides which namespaces that token may\n * address (\u00A712.4).\n *\n * Everything past that resolution is bounds: key shape, object size, page\n * size, per-environment concurrency, and the quota the manifest declared.\n */\nimport { TinyError, type Id } from '@tinycloud/domain';\nimport type { ControlPlaneStore } from '@tinycloud/db';\nimport type { TokenIssuer } from '@tinycloud/auth';\nimport type { ResourceBindingOutput } from '@tinycloud/runtime-provider';\nimport { METERS, type Telemetry } from '@tinycloud/telemetry';\n\n/** Limits chosen so one app cannot make the control plane everyone's problem. */\nexport const STORAGE_LIMITS = {\n maxObjectBytes: 32 * 1024 * 1024,\n maxKeyBytes: 1024,\n /** One listing page. A namespace may hold more; the reply says so. */\n maxListKeys: 1000,\n maxConcurrentOperationsPerEnvironment: 16,\n};\n\nexport interface StorageObject {\n key: string;\n size: number;\n etag: string;\n lastModified: string;\n}\n\n/**\n * The bytes half of a storage provider, kept separate from `ResourceProvider`\n * because provisioning a namespace and reading an object are different jobs\n * with different lifetimes. Implementations must enforce namespace\n * containment themselves: this service validates key shape, but the store is\n * the last line that stops a key from escaping its own directory or bucket\n * prefix.\n */\nexport interface ObjectStore {\n put(binding: ResourceBindingOutput, key: string, data: Buffer): StorageObject;\n get(binding: ResourceBindingOutput, key: string): Buffer | null;\n /** Metadata for one key without reading its bytes; null when it is absent. */\n stat(binding: ResourceBindingOutput, key: string): StorageObject | null;\n delete(binding: ResourceBindingOutput, key: string): boolean;\n list(binding: ResourceBindingOutput, prefix?: string): StorageObject[];\n usedBytes(binding: ResourceBindingOutput): number;\n}\n\nexport interface StorageServiceDependencies {\n store: ControlPlaneStore;\n telemetry: Telemetry;\n issuer: TokenIssuer;\n objects: ObjectStore;\n}\n\nexport interface StorageRequest {\n workloadToken: string;\n requestId: string;\n /** Namespace name from `resources.storage[].name`; the only one may be omitted. */\n namespace?: string | undefined;\n}\n\nexport interface StorageObjectBody extends StorageObject {\n body: Buffer;\n}\n\nexport interface StorageListing {\n objects: StorageObject[];\n usedBytes: number;\n maxSizeBytes: number | null;\n /** True when the namespace holds more keys than one page can carry. */\n truncated: boolean;\n}\n\ninterface ResolvedNamespace {\n binding: ResourceBindingOutput;\n namespace: string;\n environmentId: Id<'env'>;\n appId: Id<'app'>;\n organizationId: Id<'org'>;\n}\n\nexport class StorageService {\n private readonly deps: StorageServiceDependencies;\n /** Serializes operations per environment; a quota check is read-then-write. */\n private readonly locks = new Map<string, Promise<unknown>>();\n private readonly inFlight = new Map<string, number>();\n\n constructor(deps: StorageServiceDependencies) {\n this.deps = deps;\n }\n\n // ------------------------------------------------------------ resolution\n\n /**\n * Turn a workload token and a namespace name into the one binding they may\n * touch. A token from a superseded or failed deployment is refused, so a\n * stale VM that somehow survives a route switch cannot keep writing.\n */\n private resolve(workloadToken: string, namespace: string | undefined): ResolvedNamespace {\n const claims = this.deps.issuer.verify(workloadToken, { aud: 'app-workload' });\n const deploymentId = claims.deployment as Id<'dep'> | undefined;\n const environmentId = claims.env as Id<'env'> | undefined;\n const appId = claims.app as Id<'app'> | undefined;\n const organizationId = claims.org as Id<'org'>;\n if (!deploymentId || !environmentId || !appId) {\n throw new TinyError('UNAUTHENTICATED', 'Workload token is missing its app, environment, or deployment binding.');\n }\n const deployment = this.deps.store.getDeployment(deploymentId);\n if (deployment.environmentId !== environmentId || deployment.appId !== appId\n || deployment.organizationId !== organizationId\n || !['ready', 'verifying', 'deploying', 'migrating'].includes(deployment.status)) {\n throw new TinyError('FORBIDDEN', 'Workload token is not bound to a live deployment.', {\n details: { deploymentId, status: deployment.status },\n });\n }\n\n const records = this.deps.store.listResourceBindings(environmentId).filter((entry) => entry.kind === 'storage');\n if (records.length === 0) {\n throw new TinyError('NOT_FOUND', 'This app has no object storage.', {\n remediation: 'Add resources.storage to tiny.yaml and deploy again.',\n });\n }\n // An unnamed call is only unambiguous when there is exactly one namespace.\n // Guessing for the app would silently write to the wrong one.\n const record = namespace\n ? records.find((entry) => entry.name === namespace)\n : (records.length === 1 ? records[0] : undefined);\n if (!record) {\n throw new TinyError('NOT_FOUND', namespace\n ? `No storage namespace \"${namespace}\" is bound to this deployment.`\n : 'This app declares more than one storage namespace, so one must be named.', {\n remediation: 'Use one of the names under resources.storage in tiny.yaml.',\n details: { available: records.map((entry) => entry.name) },\n });\n }\n\n return {\n binding: {\n claimId: record.claimId,\n environmentId,\n kind: 'storage',\n name: record.name,\n providerKind: record.providerKind,\n providerRef: record.providerRef,\n metadata: record.metadata,\n },\n namespace: record.name,\n environmentId,\n appId,\n organizationId,\n };\n }\n\n // ---------------------------------------------------------------- guards\n\n /**\n * Key rules are enforced here so a bad key fails with a 422 before it\n * reaches a filesystem or a bucket. The store re-checks containment; this is\n * the cheap check, not the authoritative one.\n */\n private assertKey(key: unknown): asserts key is string {\n if (typeof key !== 'string' || key.length === 0) {\n throw new TinyError('VALIDATION_FAILED', 'An object key is required.', {\n remediation: 'Pass ?key=<object key> on the request.',\n });\n }\n if (Buffer.byteLength(key) > STORAGE_LIMITS.maxKeyBytes) {\n throw new TinyError('VALIDATION_FAILED', `Object key exceeds the ${STORAGE_LIMITS.maxKeyBytes} byte limit.`);\n }\n if (key.startsWith('/') || key.includes('..') || key.includes('\\0') || key.includes('\\\\')) {\n throw new TinyError('VALIDATION_FAILED', `Object key \"${key}\" is not allowed.`, {\n remediation: 'Use relative keys without \"..\", backslashes, or leading slashes.',\n details: { key },\n });\n }\n }\n\n private assertBody(body: unknown): asserts body is Buffer {\n if (!Buffer.isBuffer(body)) {\n throw new TinyError('VALIDATION_FAILED', 'An object body is required.', {\n remediation: 'Send the object bytes as an application/octet-stream request body.',\n });\n }\n if (body.byteLength > STORAGE_LIMITS.maxObjectBytes) {\n throw new TinyError('PAYLOAD_TOO_LARGE', `Object exceeds the ${STORAGE_LIMITS.maxObjectBytes} byte limit.`, {\n remediation: 'Split the object, or store it in chunks under separate keys.',\n details: { bytes: body.byteLength, limit: STORAGE_LIMITS.maxObjectBytes },\n });\n }\n }\n\n /**\n * Serialize per environment and cap the queue depth. Ordering matters here\n * for the same reason it does in the data service: a quota check reads the\n * namespace size and then writes, so two concurrent puts could both pass a\n * check that only one of them should.\n */\n private async serialize<T>(environmentId: string, fn: () => T): Promise<T> {\n const depth = this.inFlight.get(environmentId) ?? 0;\n if (depth >= STORAGE_LIMITS.maxConcurrentOperationsPerEnvironment) {\n throw new TinyError('RATE_LIMITED', 'Too many concurrent storage operations for this environment.', {\n retryable: true, details: { limit: STORAGE_LIMITS.maxConcurrentOperationsPerEnvironment },\n });\n }\n this.inFlight.set(environmentId, depth + 1);\n const previous = this.locks.get(environmentId) ?? Promise.resolve();\n const run = previous.then(fn, fn);\n this.locks.set(environmentId, run.then(() => undefined, () => undefined));\n try {\n return await run;\n } finally {\n this.inFlight.set(environmentId, (this.inFlight.get(environmentId) ?? 1) - 1);\n }\n }\n\n /**\n * Bytes moved, not bytes stored: `storage_bytes` counts what crossed the\n * link, and `operation` in the metadata is what splits reads from writes.\n * The idempotency key is the request, the operation, and the key together,\n * so a retried put is counted once while two puts in one app request are\n * counted twice.\n */\n private meter(target: ResolvedNamespace, requestId: string, operation: string, key: string, bytes: number): void {\n if (bytes <= 0) return;\n this.deps.telemetry.meter({\n organizationId: target.organizationId,\n appId: target.appId,\n meter: METERS.storageBytes,\n quantity: bytes,\n idempotencyKey: `storage:${requestId}:${operation}:${key}`,\n metadata: { operation, namespace: target.namespace },\n });\n }\n\n private quotaOf(binding: ResourceBindingOutput): number | null {\n const max = Number(binding.metadata.maxSizeBytes ?? 0);\n return max > 0 ? max : null;\n }\n\n // -------------------------------------------------------------- requests\n\n async put(input: StorageRequest & { key: string; body: Buffer }): Promise<StorageObject> {\n this.assertKey(input.key);\n this.assertBody(input.body);\n const target = this.resolve(input.workloadToken, input.namespace);\n const object = await this.serialize(target.environmentId, () =>\n this.deps.objects.put(target.binding, input.key, input.body));\n this.meter(target, input.requestId, 'put', input.key, object.size);\n return object;\n }\n\n async get(input: StorageRequest & { key: string }): Promise<StorageObjectBody> {\n this.assertKey(input.key);\n const target = this.resolve(input.workloadToken, input.namespace);\n const found = await this.serialize(target.environmentId, () => {\n const object = this.deps.objects.stat(target.binding, input.key);\n const body = object ? this.deps.objects.get(target.binding, input.key) : null;\n return object && body ? { object, body } : null;\n });\n if (found === null) {\n // `reason` is what lets the SDK turn a missing object into `null` while\n // still surfacing a wrong namespace or a dead token as an error.\n throw new TinyError('NOT_FOUND', `No object with key \"${input.key}\".`, {\n remediation: 'List the namespace to see which keys exist.',\n details: { key: input.key, namespace: target.namespace, reason: 'object-not-found' },\n });\n }\n this.meter(target, input.requestId, 'get', input.key, found.body.byteLength);\n return { ...found.object, body: found.body };\n }\n\n async delete(input: StorageRequest & { key: string }): Promise<{ deleted: boolean }> {\n this.assertKey(input.key);\n const target = this.resolve(input.workloadToken, input.namespace);\n const deleted = await this.serialize(target.environmentId, () =>\n this.deps.objects.delete(target.binding, input.key));\n return { deleted };\n }\n\n async list(input: StorageRequest & { prefix?: string | undefined }): Promise<StorageListing> {\n if (input.prefix !== undefined && typeof input.prefix !== 'string') {\n throw new TinyError('VALIDATION_FAILED', 'A listing prefix must be a string.');\n }\n const target = this.resolve(input.workloadToken, input.namespace);\n return this.serialize(target.environmentId, () => {\n const all = this.deps.objects.list(target.binding, input.prefix ?? '');\n return {\n objects: all.slice(0, STORAGE_LIMITS.maxListKeys),\n usedBytes: this.deps.objects.usedBytes(target.binding),\n maxSizeBytes: this.quotaOf(target.binding),\n truncated: all.length > STORAGE_LIMITS.maxListKeys,\n };\n });\n }\n}\n", "/**\n * Control-plane repositories.\n *\n * Rule from blueprint \u00A77.2: every organization-scoped read takes an\n * `organizationId`. The store enforces it in the query rather than trusting\n * callers to filter afterwards.\n */\nimport { readdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport {\n DEFAULT_ORGANIZATION_POLICY, TinyError, newId,\n type AccessBinding, type Actor, type App, type AppRevision, type AppRole, type AppState, type AuditEvent,\n type CapabilityGrant, type Connection, type Deployment, type DeploymentState, type Environment,\n type EnvironmentKind, type Id, type LifecycleAction, type Membership, type Organization,\n type OrganizationPolicy, type ResourceBinding, type ResourceClaim, type ResourceKind,\n type RuntimeInstance, type RuntimeInstanceState, type RuntimeTarget, type Secret,\n type SecretVersion, type ServiceAccount, type UsageEvent, type User,\n} from '@tinycloud/domain';\nimport { Database, bool, json, nowIso, parseJson, type Row } from './sqlite.ts';\n\nconst MIGRATIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'migrations');\n\nfunction required<T>(value: T | undefined, kind: string, id: string): T {\n if (value === undefined) {\n throw new TinyError('NOT_FOUND', `${kind} ${id} was not found.`, {\n remediation: `Check the ${kind} ID, or list ${kind}s to find the right one.`,\n details: { kind, id },\n });\n }\n return value;\n}\n\nexport interface Page<T> {\n items: T[];\n nextCursor: string | null;\n}\n\nexport class ControlPlaneStore {\n readonly db: Database;\n\n constructor(location = ':memory:') {\n this.db = new Database(location);\n this.migrate();\n }\n\n /** Apply control-plane migrations; safe to call on every boot. */\n private migrate(): void {\n this.db.exec('create table if not exists schema_migrations (name text primary key, applied_at text not null)');\n const applied = new Set(this.db.all<{ name: string }>('select name from schema_migrations').map((r) => r.name));\n const files = readdirSync(MIGRATIONS_DIR).filter((f) => f.endsWith('.sql')).sort();\n for (const file of files) {\n if (applied.has(file)) continue;\n this.db.transaction(() => {\n this.db.exec(readFileSync(join(MIGRATIONS_DIR, file), 'utf8'));\n this.db.run('insert into schema_migrations (name, applied_at) values (?, ?)', file, nowIso());\n });\n }\n }\n\n transaction<T>(fn: () => T): T {\n return this.db.transaction(fn);\n }\n\n close(): void {\n this.db.close();\n }\n\n // ---------------------------------------------------------------- orgs\n\n createOrganization(input: { slug: string; displayName: string; policy?: Partial<OrganizationPolicy> }): Organization {\n const org: Organization = {\n id: newId('org'),\n slug: input.slug,\n displayName: input.displayName,\n plan: 'free',\n policy: { ...DEFAULT_ORGANIZATION_POLICY, ...input.policy },\n createdAt: nowIso(),\n };\n this.db.run(\n 'insert into organizations (id, slug, display_name, plan, policy, created_at) values (?,?,?,?,?,?)',\n org.id, org.slug, org.displayName, org.plan, json(org.policy), org.createdAt,\n );\n return org;\n }\n\n getOrganization(id: Id<'org'>): Organization {\n return required(this.findOrganization(id), 'organization', id);\n }\n\n findOrganization(id: Id<'org'>): Organization | undefined {\n const row = this.db.get<Row>('select * from organizations where id = ?', id);\n return row && toOrganization(row);\n }\n\n findOrganizationBySlug(slug: string): Organization | undefined {\n const row = this.db.get<Row>('select * from organizations where slug = ?', slug);\n return row && toOrganization(row);\n }\n\n updateOrganizationPolicy(id: Id<'org'>, policy: OrganizationPolicy): void {\n this.db.run('update organizations set policy = ? where id = ?', json(policy), id);\n }\n\n updateOrganizationProfile(id: Id<'org'>, patch: { displayName: string }): Organization {\n this.db.run('update organizations set display_name = ? where id = ?', patch.displayName, id);\n return this.getOrganization(id);\n }\n\n // --------------------------------------------------------------- users\n\n createUser(input: { email: string; displayName?: string }): User {\n const existing = this.findUserByEmail(input.email);\n if (existing) return existing;\n const user: User = {\n id: newId('user'),\n email: input.email.toLowerCase(),\n displayName: input.displayName ?? input.email.split('@')[0]!,\n createdAt: nowIso(),\n };\n this.db.run('insert into users (id, email, display_name, created_at) values (?,?,?,?)',\n user.id, user.email, user.displayName, user.createdAt);\n return user;\n }\n\n findUserByEmail(email: string): User | undefined {\n const row = this.db.get<Row>('select * from users where email = ?', email.toLowerCase());\n return row && toUser(row);\n }\n\n getUser(id: Id<'user'>): User {\n const row = this.db.get<Row>('select * from users where id = ?', id);\n return required(row && toUser(row), 'user', id);\n }\n\n setLocalCredential(userId: Id<'user'>, credential: { salt: string; hash: string }): void {\n this.db.run(\n `insert into local_credentials (user_id, password_salt, password_hash, created_at) values (?,?,?,?)\n on conflict(user_id) do update set password_salt = excluded.password_salt, password_hash = excluded.password_hash`,\n userId, credential.salt, credential.hash, nowIso());\n }\n\n findLocalCredential(userId: Id<'user'>): { salt: string; hash: string } | undefined {\n const row = this.db.get<Row>('select password_salt, password_hash from local_credentials where user_id = ?', userId);\n return row ? { salt: row.password_salt as string, hash: row.password_hash as string } : undefined;\n }\n\n addMembership(input: { organizationId: Id<'org'>; userId: Id<'user'>; role: Membership['role']; groups?: string[] }): Membership {\n const existing = this.findMembership(input.organizationId, input.userId);\n if (existing) return existing;\n const membership: Membership = {\n id: newId('mem'),\n organizationId: input.organizationId,\n userId: input.userId,\n role: input.role,\n groups: input.groups ?? [],\n createdAt: nowIso(),\n };\n this.db.run('insert into memberships (id, organization_id, user_id, role, groups, created_at) values (?,?,?,?,?,?)',\n membership.id, membership.organizationId, membership.userId, membership.role, json(membership.groups), membership.createdAt);\n return membership;\n }\n\n findMembership(organizationId: Id<'org'>, userId: Id<'user'>): Membership | undefined {\n const row = this.db.get<Row>('select * from memberships where organization_id = ? and user_id = ?', organizationId, userId);\n return row && toMembership(row);\n }\n\n getMembership(id: Id<'mem'>): Membership {\n const row = this.db.get<Row>('select * from memberships where id = ?', id);\n return required(row && toMembership(row), 'membership', id);\n }\n\n listMemberships(organizationId: Id<'org'>): Membership[] {\n return this.db.all<Row>('select * from memberships where organization_id = ? order by created_at', organizationId).map(toMembership);\n }\n\n /**\n * The organizations one identity belongs to. This is the one membership read\n * that is deliberately not organization-scoped: it answers \"which tenants may\n * this person address at all\", which is the question the dashboard asks\n * before it has an organization to scope by.\n */\n listMembershipsForUser(userId: Id<'user'>): Membership[] {\n return this.db.all<Row>('select * from memberships where user_id = ? order by created_at', userId).map(toMembership);\n }\n\n countMembershipsWithRole(organizationId: Id<'org'>, role: Membership['role']): number {\n return this.db.get<{ total: number }>(\n 'select count(*) as total from memberships where organization_id = ? and role = ?', organizationId, role)?.total ?? 0;\n }\n\n updateMembership(id: Id<'mem'>, patch: { role?: Membership['role']; groups?: string[] }): Membership {\n const membership = this.getMembership(id);\n const next: Membership = {\n ...membership,\n role: patch.role ?? membership.role,\n groups: patch.groups ?? membership.groups,\n };\n this.db.run('update memberships set role = ?, groups = ? where id = ?', next.role, json(next.groups), id);\n return next;\n }\n\n deleteMembership(id: Id<'mem'>): boolean {\n return this.db.run('delete from memberships where id = ?', id).changes > 0;\n }\n\n createServiceAccount(input: { organizationId: Id<'org'>; name: string; role: Membership['role']; tokenHash: string }): ServiceAccount {\n const account: ServiceAccount = {\n id: newId('sa'),\n organizationId: input.organizationId,\n name: input.name,\n role: input.role,\n tokenHash: input.tokenHash,\n createdAt: nowIso(),\n };\n this.db.run('insert into service_accounts (id, organization_id, name, role, token_hash, created_at) values (?,?,?,?,?,?)',\n account.id, account.organizationId, account.name, account.role, account.tokenHash, account.createdAt);\n return account;\n }\n\n findServiceAccount(organizationId: Id<'org'>, name: string): ServiceAccount | undefined {\n const row = this.db.get<Row>(\n 'select * from service_accounts where organization_id = ? and name = ?', organizationId, name);\n return row && toServiceAccount(row);\n }\n\n listServiceAccounts(organizationId: Id<'org'>): ServiceAccount[] {\n return this.db.all<Row>(\n 'select * from service_accounts where organization_id = ? order by created_at', organizationId)\n .map(toServiceAccount);\n }\n\n findServiceAccountByTokenHash(tokenHash: string): ServiceAccount | undefined {\n const row = this.db.get<Row>('select * from service_accounts where token_hash = ?', tokenHash);\n return row && toServiceAccount(row);\n }\n\n // ---------------------------------------------------------------- apps\n\n grantArtifactAccess(organizationId: Id<'org'>, digest: string): void {\n this.db.run('insert or ignore into artifact_access (organization_id, digest, created_at) values (?,?,?)',\n organizationId, digest, nowIso());\n }\n\n hasArtifactAccess(organizationId: Id<'org'>, digest: string): boolean {\n return Boolean(this.db.get<Row>('select 1 from artifact_access where organization_id = ? and digest = ?', organizationId, digest));\n }\n\n createApp(input: {\n organizationId: Id<'org'>; slug: string; displayName: string; description?: string | null;\n ownerMembershipId: Id<'mem'>; riskTier?: App['riskTier'];\n }): App {\n const now = nowIso();\n const app: App = {\n id: newId('app'),\n organizationId: input.organizationId,\n slug: input.slug,\n displayName: input.displayName,\n description: input.description ?? null,\n ownerMembershipId: input.ownerMembershipId,\n status: 'active',\n riskTier: input.riskTier ?? 'low',\n currentProductionRevisionId: null,\n lastAccessedAt: null,\n archivedAt: null,\n deleteAfter: null,\n createdAt: now,\n updatedAt: now,\n };\n this.db.run(\n `insert into apps (id, organization_id, slug, display_name, description, owner_membership_id, status,\n risk_tier, current_production_revision_id, last_accessed_at, archived_at, delete_after, created_at, updated_at)\n values (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n app.id, app.organizationId, app.slug, app.displayName, app.description, app.ownerMembershipId,\n app.status, app.riskTier, null, null, null, null, app.createdAt, app.updatedAt,\n );\n return app;\n }\n\n getApp(id: Id<'app'>): App {\n const row = this.db.get<Row>('select * from apps where id = ?', id);\n return required(row && toApp(row), 'app', id);\n }\n\n findApp(id: Id<'app'>): App | undefined {\n const row = this.db.get<Row>('select * from apps where id = ?', id);\n return row && toApp(row);\n }\n\n findAppBySlug(organizationId: Id<'org'>, slug: string): App | undefined {\n const row = this.db.get<Row>('select * from apps where organization_id = ? and slug = ?', organizationId, slug);\n return row && toApp(row);\n }\n\n listApps(organizationId: Id<'org'>, options: { status?: AppState; limit?: number; cursor?: string } = {}): Page<App> {\n const limit = Math.min(options.limit ?? 50, 200);\n const clauses = ['organization_id = ?'];\n const params: unknown[] = [organizationId];\n if (options.status) { clauses.push('status = ?'); params.push(options.status); }\n else { clauses.push(\"status != 'deleted'\"); }\n if (options.cursor) { clauses.push('id > ?'); params.push(options.cursor); }\n const rows = this.db.all<Row>(\n `select * from apps where ${clauses.join(' and ')} order by id limit ?`, ...params, limit + 1);\n const items = rows.slice(0, limit).map(toApp);\n return { items, nextCursor: rows.length > limit ? (items.at(-1)?.id ?? null) : null };\n }\n\n updateApp(id: Id<'app'>, patch: Partial<Pick<App, 'displayName' | 'description' | 'status' | 'riskTier' | 'currentProductionRevisionId' | 'lastAccessedAt' | 'archivedAt' | 'deleteAfter'>>): App {\n const columns: Record<string, string> = {\n displayName: 'display_name', description: 'description', status: 'status', riskTier: 'risk_tier',\n currentProductionRevisionId: 'current_production_revision_id', lastAccessedAt: 'last_accessed_at',\n archivedAt: 'archived_at', deleteAfter: 'delete_after',\n };\n const sets: string[] = [];\n const params: unknown[] = [];\n for (const [key, value] of Object.entries(patch)) {\n const column = columns[key];\n if (!column) continue;\n sets.push(`${column} = ?`);\n params.push(value ?? null);\n }\n sets.push('updated_at = ?');\n params.push(nowIso(), id);\n this.db.run(`update apps set ${sets.join(', ')} where id = ?`, ...params);\n return this.getApp(id);\n }\n\n touchApp(id: Id<'app'>, at = nowIso()): void {\n this.db.run('update apps set last_accessed_at = ? where id = ?', at, id);\n }\n\n // ----------------------------------------------------------- revisions\n\n createRevision(input: Omit<AppRevision, 'id' | 'revisionNumber' | 'createdAt'>): AppRevision {\n return this.transaction(() => {\n const existing = this.db.get<Row>(\n 'select * from app_revisions where app_id = ? and manifest_sha256 = ? and artifact_sha256 = ?',\n input.appId, input.manifestSha256, input.artifactSha256);\n // Identical manifest + artifact is the same revision; deploying it again\n // must not mint a new immutable record (blueprint \u00A715.2 step 3).\n if (existing) return toRevision(existing);\n\n const next = this.db.get<{ max: number | null }>(\n 'select max(revision_number) as max from app_revisions where app_id = ?', input.appId);\n const revision: AppRevision = {\n ...input,\n id: newId('rev'),\n revisionNumber: (next?.max ?? 0) + 1,\n createdAt: nowIso(),\n };\n this.db.run(\n `insert into app_revisions (id, app_id, revision_number, manifest_version, normalized_manifest,\n manifest_sha256, artifact_uri, artifact_sha256, source_metadata, created_by_type, created_by_id, created_at)\n values (?,?,?,?,?,?,?,?,?,?,?,?)`,\n revision.id, revision.appId, revision.revisionNumber, revision.manifestVersion,\n json(revision.normalizedManifest), revision.manifestSha256, revision.artifactUri,\n revision.artifactSha256, json(revision.sourceMetadata), revision.createdByType,\n revision.createdById, revision.createdAt,\n );\n return revision;\n });\n }\n\n getRevision(id: Id<'rev'>): AppRevision {\n const row = this.db.get<Row>('select * from app_revisions where id = ?', id);\n return required(row && toRevision(row), 'revision', id);\n }\n\n listRevisions(appId: Id<'app'>, limit = 20): AppRevision[] {\n return this.db.all<Row>('select * from app_revisions where app_id = ? order by revision_number desc limit ?', appId, limit)\n .map(toRevision);\n }\n\n // -------------------------------------------------------- environments\n\n createEnvironment(input: { appId: Id<'app'>; name: string; kind: EnvironmentKind; hostname: string; expiresAt?: string | null }): Environment {\n const environment: Environment = {\n id: newId('env'),\n appId: input.appId,\n name: input.name,\n kind: input.kind,\n hostname: input.hostname,\n expiresAt: input.expiresAt ?? null,\n createdAt: nowIso(),\n };\n this.db.run('insert into environments (id, app_id, name, kind, hostname, expires_at, created_at) values (?,?,?,?,?,?,?)',\n environment.id, environment.appId, environment.name, environment.kind, environment.hostname,\n environment.expiresAt, environment.createdAt);\n return environment;\n }\n\n getEnvironment(id: Id<'env'>): Environment {\n const row = this.db.get<Row>('select * from environments where id = ?', id);\n return required(row && toEnvironment(row), 'environment', id);\n }\n\n findEnvironmentByName(appId: Id<'app'>, name: string): Environment | undefined {\n const row = this.db.get<Row>('select * from environments where app_id = ? and name = ?', appId, name);\n return row && toEnvironment(row);\n }\n\n findEnvironmentByHostname(hostname: string): Environment | undefined {\n const row = this.db.get<Row>('select * from environments where hostname = ?', hostname.toLowerCase());\n return row && toEnvironment(row);\n }\n\n listEnvironments(appId: Id<'app'>): Environment[] {\n return this.db.all<Row>('select * from environments where app_id = ? order by created_at', appId).map(toEnvironment);\n }\n\n listExpiredEnvironments(now = nowIso()): Environment[] {\n return this.db.all<Row>(\"select * from environments where expires_at is not null and expires_at < ?\", now).map(toEnvironment);\n }\n\n deleteEnvironment(id: Id<'env'>): void {\n this.transaction(() => {\n this.db.run('delete from migration_runs where environment_id = ?', id);\n this.db.run('delete from resource_bindings where environment_id = ?', id);\n this.db.run('delete from resource_claims where environment_id = ?', id);\n this.db.run('delete from runtime_instances where deployment_id in (select id from deployments where environment_id = ?)', id);\n this.db.run('delete from deployments where environment_id = ?', id);\n this.db.run('delete from capability_grants where environment_id = ?', id);\n this.db.run('delete from environments where id = ?', id);\n });\n }\n\n // -------------------------------------------------------- deployments\n\n createDeployment(input: {\n organizationId: Id<'org'>; appId: Id<'app'>; environmentId: Id<'env'>; revisionId: Id<'rev'>;\n runtimeTargetId: Id<'target'>; idempotencyKey: string; plan?: Record<string, unknown>;\n }): Deployment {\n const existing = this.db.get<Row>('select * from deployments where environment_id = ? and idempotency_key = ?',\n input.environmentId, input.idempotencyKey);\n if (existing) {\n const deployment = toDeployment(existing);\n if (deployment.appId !== input.appId || deployment.revisionId !== input.revisionId\n || deployment.runtimeTargetId !== input.runtimeTargetId) {\n throw new TinyError('CONFLICT', 'Idempotency key was already used with different deployment inputs.', {\n remediation: 'Retry the original request unchanged, or use a new idempotency key.',\n details: {\n idempotencyKey: input.idempotencyKey,\n existingRevisionId: deployment.revisionId,\n requestedRevisionId: input.revisionId,\n },\n });\n }\n return deployment;\n }\n\n const deployment: Deployment = {\n id: newId('dep'),\n organizationId: input.organizationId,\n appId: input.appId,\n environmentId: input.environmentId,\n revisionId: input.revisionId,\n runtimeTargetId: input.runtimeTargetId,\n status: 'queued',\n idempotencyKey: input.idempotencyKey,\n plan: input.plan ?? {},\n providerState: {},\n errorCode: null,\n errorDetail: null,\n startedAt: null,\n readyAt: null,\n finishedAt: null,\n createdAt: nowIso(),\n };\n this.db.run(\n `insert into deployments (id, organization_id, app_id, environment_id, revision_id, runtime_target_id,\n status, idempotency_key, plan, provider_state, created_at) values (?,?,?,?,?,?,?,?,?,?,?)`,\n deployment.id, deployment.organizationId, deployment.appId, deployment.environmentId,\n deployment.revisionId, deployment.runtimeTargetId, deployment.status, deployment.idempotencyKey,\n json(deployment.plan), json(deployment.providerState), deployment.createdAt,\n );\n return deployment;\n }\n\n findDeploymentByIdempotency(environmentId: Id<'env'>, idempotencyKey: string): Deployment | undefined {\n const row = this.db.get<Row>(\n 'select * from deployments where environment_id = ? and idempotency_key = ?',\n environmentId, idempotencyKey);\n return row && toDeployment(row);\n }\n\n getDeployment(id: Id<'dep'>): Deployment {\n const row = this.db.get<Row>('select * from deployments where id = ?', id);\n return required(row && toDeployment(row), 'deployment', id);\n }\n\n updateDeployment(id: Id<'dep'>, patch: {\n status?: DeploymentState; plan?: Record<string, unknown>; providerState?: Record<string, unknown>;\n errorCode?: string | null; errorDetail?: Record<string, unknown> | null;\n startedAt?: string | null; readyAt?: string | null; finishedAt?: string | null;\n }): Deployment {\n const sets: string[] = [];\n const params: unknown[] = [];\n const push = (column: string, value: unknown) => { sets.push(`${column} = ?`); params.push(value); };\n if (patch.status !== undefined) push('status', patch.status);\n if (patch.plan !== undefined) push('plan', json(patch.plan));\n if (patch.providerState !== undefined) push('provider_state', json(patch.providerState));\n if (patch.errorCode !== undefined) push('error_code', patch.errorCode);\n if (patch.errorDetail !== undefined) push('error_detail', patch.errorDetail ? json(patch.errorDetail) : null);\n if (patch.startedAt !== undefined) push('started_at', patch.startedAt);\n if (patch.readyAt !== undefined) push('ready_at', patch.readyAt);\n if (patch.finishedAt !== undefined) push('finished_at', patch.finishedAt);\n if (sets.length === 0) return this.getDeployment(id);\n params.push(id);\n this.db.run(`update deployments set ${sets.join(', ')} where id = ?`, ...params);\n return this.getDeployment(id);\n }\n\n listDeployments(appId: Id<'app'>, options: { environmentId?: Id<'env'>; limit?: number } = {}): Deployment[] {\n const limit = Math.min(options.limit ?? 20, 100);\n if (options.environmentId) {\n return this.db.all<Row>('select * from deployments where environment_id = ? order by created_at desc limit ?',\n options.environmentId, limit).map(toDeployment);\n }\n return this.db.all<Row>('select * from deployments where app_id = ? order by created_at desc limit ?', appId, limit)\n .map(toDeployment);\n }\n\n /** The deployment currently serving an environment. */\n findLiveDeployment(environmentId: Id<'env'>): Deployment | undefined {\n const row = this.db.get<Row>(\n \"select * from deployments where environment_id = ? and status = 'ready' order by created_at desc limit 1\",\n environmentId);\n return row && toDeployment(row);\n }\n\n /** The most recent healthy deployment other than `excludeId` \u2014 the rollback target. */\n findRollbackCandidate(environmentId: Id<'env'>, excludeId: Id<'dep'>): Deployment | undefined {\n const row = this.db.get<Row>(\n `select * from deployments where environment_id = ? and id != ?\n and status in ('superseded','ready') and ready_at is not null\n order by created_at desc limit 1`,\n environmentId, excludeId);\n return row && toDeployment(row);\n }\n\n enqueueDeploymentJob(input: {\n deploymentId: Id<'dep'>; organizationId: Id<'org'>; actor: Actor;\n approvedBy?: string | null; requestId?: string | null;\n }): void {\n const now = nowIso();\n this.db.run(\n `insert into deployment_jobs\n (id, deployment_id, organization_id, actor, approved_by, request_id, status, attempts, available_at, created_at, updated_at)\n values (?,?,?,?,?,?,'pending',0,?,?,?)\n on conflict(deployment_id) do update set\n actor = excluded.actor, approved_by = excluded.approved_by, request_id = excluded.request_id,\n status = 'pending', available_at = excluded.available_at, locked_at = null, updated_at = excluded.updated_at`,\n newId('job'), input.deploymentId, input.organizationId, json(input.actor), input.approvedBy ?? null,\n input.requestId ?? null, now, now, now,\n );\n }\n\n claimDeploymentJob(now = nowIso()): {\n id: string; deploymentId: Id<'dep'>; actor: Actor; approvedBy: string | null; requestId: string | null; attempts: number;\n } | undefined {\n return this.transaction(() => {\n const row = this.db.get<Row>(\n \"select * from deployment_jobs where status = 'pending' and available_at <= ? order by created_at limit 1\", now);\n if (!row) return undefined;\n const changed = this.db.run(\n \"update deployment_jobs set status = 'running', attempts = attempts + 1, locked_at = ?, updated_at = ? where id = ? and status = 'pending'\",\n now, now, row.id).changes;\n if (changed !== 1) return undefined;\n return {\n id: row.id as string, deploymentId: row.deployment_id as Id<'dep'>,\n actor: parseJson<Actor>(row.actor, {} as Actor), approvedBy: (row.approved_by as string | null) ?? null,\n requestId: (row.request_id as string | null) ?? null, attempts: Number(row.attempts) + 1,\n };\n });\n }\n\n completeDeploymentJob(id: string): void {\n this.db.run(\"update deployment_jobs set status = 'completed', locked_at = null, updated_at = ? where id = ?\", nowIso(), id);\n }\n\n failDeploymentJob(id: string, error: string, retry: boolean, attempts: number): void {\n const delayMs = Math.min(60_000, 1000 * 2 ** Math.max(0, attempts - 1));\n const availableAt = new Date(Date.now() + delayMs).toISOString();\n this.db.run(\n `update deployment_jobs set status = ?, available_at = ?, locked_at = null, last_error = ?, updated_at = ? where id = ?`,\n retry && attempts < 5 ? 'pending' : 'failed', availableAt, error.slice(0, 4000), nowIso(), id,\n );\n }\n\n recoverStaleDeploymentJobs(olderThan: string): number {\n return this.db.run(\n \"update deployment_jobs set status = 'pending', locked_at = null, updated_at = ? where status = 'running' and locked_at < ?\",\n nowIso(), olderThan).changes;\n }\n\n recordResidualResource(input: {\n organizationId: Id<'org'>; appId: Id<'app'>; providerKind: string; providerRef: string; resourceKind: string;\n }): void {\n this.db.run(\n `insert into residual_resources\n (id, organization_id, app_id, provider_kind, provider_ref, resource_kind, next_attempt_at, created_at)\n values (?,?,?,?,?,?,?,?) on conflict(provider_kind, provider_ref) do nothing`,\n newId('res'), input.organizationId, input.appId, input.providerKind, input.providerRef,\n input.resourceKind, nowIso(), nowIso(),\n );\n }\n\n listResidualResources(organizationId?: Id<'org'>): Array<{\n id: string; organizationId: Id<'org'>; appId: Id<'app'>; providerKind: string; providerRef: string;\n resourceKind: string; attempts: number;\n }> {\n const rows = organizationId\n ? this.db.all<Row>('select * from residual_resources where resolved_at is null and organization_id = ? and next_attempt_at <= ? order by created_at', organizationId, nowIso())\n : this.db.all<Row>('select * from residual_resources where resolved_at is null and next_attempt_at <= ? order by created_at', nowIso());\n return rows.map((row) => ({\n id: row.id as string, organizationId: row.organization_id as Id<'org'>, appId: row.app_id as Id<'app'>,\n providerKind: row.provider_kind as string, providerRef: row.provider_ref as string,\n resourceKind: row.resource_kind as string, attempts: Number(row.attempts),\n }));\n }\n\n resolveResidualResource(id: string): void {\n this.db.run('update residual_resources set resolved_at = ? where id = ?', nowIso(), id);\n }\n\n retryResidualResource(id: string, error: string, attempts: number): void {\n const next = new Date(Date.now() + Math.min(86_400_000, 60_000 * 2 ** attempts)).toISOString();\n this.db.run('update residual_resources set attempts = attempts + 1, next_attempt_at = ?, last_error = ? where id = ?', next, error.slice(0, 2000), id);\n }\n\n // ----------------------------------------------------- runtime targets\n\n createRuntimeTarget(input: { organizationId?: Id<'org'> | null; name: string; providerKind: string; region: string; config?: Record<string, unknown> }): RuntimeTarget {\n const target: RuntimeTarget = {\n id: newId('target'),\n organizationId: input.organizationId ?? null,\n name: input.name,\n providerKind: input.providerKind,\n region: input.region,\n config: input.config ?? {},\n healthy: true,\n createdAt: nowIso(),\n };\n this.db.run('insert into runtime_targets (id, organization_id, name, provider_kind, region, config, healthy, created_at) values (?,?,?,?,?,?,?,?)',\n target.id, target.organizationId, target.name, target.providerKind, target.region, json(target.config), 1, target.createdAt);\n return target;\n }\n\n getRuntimeTarget(id: Id<'target'>): RuntimeTarget {\n const row = this.db.get<Row>('select * from runtime_targets where id = ?', id);\n return required(row && toRuntimeTarget(row), 'runtime target', id);\n }\n\n listRuntimeTargets(organizationId: Id<'org'>): RuntimeTarget[] {\n return this.db.all<Row>('select * from runtime_targets where organization_id is null or organization_id = ? order by created_at',\n organizationId).map(toRuntimeTarget);\n }\n\n upsertRuntimeInstance(input: { deploymentId: Id<'dep'>; runtimeTargetId: Id<'target'>; providerRef: string; state: RuntimeInstanceState }): RuntimeInstance {\n const existing = this.db.get<Row>('select * from runtime_instances where deployment_id = ?', input.deploymentId);\n const instance: RuntimeInstance = {\n id: existing ? (existing.id as Id<'inst'>) : newId('inst'),\n deploymentId: input.deploymentId,\n runtimeTargetId: input.runtimeTargetId,\n providerRef: input.providerRef,\n state: input.state,\n updatedAt: nowIso(),\n };\n if (existing) {\n this.db.run('update runtime_instances set provider_ref = ?, state = ?, updated_at = ? where id = ?',\n instance.providerRef, instance.state, instance.updatedAt, instance.id);\n } else {\n this.db.run('insert into runtime_instances (id, deployment_id, runtime_target_id, provider_ref, state, updated_at) values (?,?,?,?,?,?)',\n instance.id, instance.deploymentId, instance.runtimeTargetId, instance.providerRef, instance.state, instance.updatedAt);\n }\n return instance;\n }\n\n findRuntimeInstance(deploymentId: Id<'dep'>): RuntimeInstance | undefined {\n const row = this.db.get<Row>('select * from runtime_instances where deployment_id = ?', deploymentId);\n return row && toRuntimeInstance(row);\n }\n\n // ------------------------------------------------------------ resources\n\n upsertResourceClaim(input: { environmentId: Id<'env'>; kind: ResourceKind; name: string; spec: Record<string, unknown> }): ResourceClaim {\n const existing = this.db.get<Row>('select * from resource_claims where environment_id = ? and kind = ? and name = ?',\n input.environmentId, input.kind, input.name);\n if (existing) {\n this.db.run('update resource_claims set spec = ? where id = ?', json(input.spec), existing.id);\n return { ...toResourceClaim(existing), spec: input.spec };\n }\n const claim: ResourceClaim = { id: newId('res'), ...input };\n this.db.run('insert into resource_claims (id, environment_id, kind, name, spec) values (?,?,?,?,?)',\n claim.id, claim.environmentId, claim.kind, claim.name, json(claim.spec));\n return claim;\n }\n\n listResourceClaims(environmentId: Id<'env'>): ResourceClaim[] {\n return this.db.all<Row>('select * from resource_claims where environment_id = ? order by kind, name', environmentId)\n .map(toResourceClaim);\n }\n\n upsertResourceBinding(input: Omit<ResourceBinding, 'id' | 'createdAt'>): ResourceBinding {\n const existing = this.db.get<Row>('select * from resource_bindings where environment_id = ? and kind = ? and name = ?',\n input.environmentId, input.kind, input.name);\n if (existing) {\n this.db.run('update resource_bindings set provider_ref = ?, provider_kind = ?, metadata = ? where id = ?',\n input.providerRef, input.providerKind, json(input.metadata), existing.id);\n return toResourceBinding(this.db.get<Row>('select * from resource_bindings where id = ?', existing.id)!);\n }\n const binding: ResourceBinding = { ...input, id: newId('bind'), createdAt: nowIso() };\n this.db.run(\n 'insert into resource_bindings (id, claim_id, environment_id, kind, name, provider_kind, provider_ref, metadata, created_at) values (?,?,?,?,?,?,?,?,?)',\n binding.id, binding.claimId, binding.environmentId, binding.kind, binding.name,\n binding.providerKind, binding.providerRef, json(binding.metadata), binding.createdAt);\n return binding;\n }\n\n listResourceBindings(environmentId: Id<'env'>): ResourceBinding[] {\n return this.db.all<Row>('select * from resource_bindings where environment_id = ? order by kind, name', environmentId)\n .map(toResourceBinding);\n }\n\n deleteResourceBinding(id: Id<'bind'>): void {\n this.db.run('delete from resource_bindings where id = ?', id);\n }\n\n // ----------------------------------------------------------- migrations\n\n recordMigrationRun(input: { environmentId: Id<'env'>; bindingId: Id<'bind'>; migrationName: string; checksum: string; durationMs: number }): void {\n this.db.run('insert into migration_runs (id, environment_id, binding_id, migration_name, checksum, applied_at, duration_ms) values (?,?,?,?,?,?,?)',\n newId('mig'), input.environmentId, input.bindingId, input.migrationName, input.checksum, nowIso(), input.durationMs);\n }\n\n listMigrationRuns(environmentId: Id<'env'>): Array<{ migrationName: string; checksum: string; appliedAt: string }> {\n return this.db.all<Row>('select migration_name, checksum, applied_at from migration_runs where environment_id = ? order by migration_name', environmentId)\n .map((row) => ({ migrationName: row.migration_name as string, checksum: row.checksum as string, appliedAt: row.applied_at as string }));\n }\n\n // --------------------------------------------------------------- access\n\n putAccessBinding(input: { appId: Id<'app'>; subjectType: AccessBinding['subjectType']; subject: string; role: AppRole; grantedBy: string }): AccessBinding {\n const existing = this.db.get<Row>('select * from access_bindings where app_id = ? and subject_type = ? and subject = ?',\n input.appId, input.subjectType, input.subject);\n if (existing) {\n this.db.run('update access_bindings set role = ?, granted_by = ? where id = ?', input.role, input.grantedBy, existing.id);\n return { ...toAccessBinding(existing), role: input.role, grantedBy: input.grantedBy };\n }\n const binding: AccessBinding = { id: newId('access'), ...input, createdAt: nowIso() };\n this.db.run('insert into access_bindings (id, app_id, subject_type, subject, role, granted_by, created_at) values (?,?,?,?,?,?,?)',\n binding.id, binding.appId, binding.subjectType, binding.subject, binding.role, binding.grantedBy, binding.createdAt);\n return binding;\n }\n\n listAccessBindings(appId: Id<'app'>): AccessBinding[] {\n return this.db.all<Row>('select * from access_bindings where app_id = ? order by created_at', appId).map(toAccessBinding);\n }\n\n deleteAccessBinding(appId: Id<'app'>, subject: string): boolean {\n return this.db.run('delete from access_bindings where app_id = ? and subject = ?', appId, subject).changes > 0;\n }\n\n deleteAccessBindingById(id: Id<'access'>): boolean {\n return this.db.run('delete from access_bindings where id = ?', id).changes > 0;\n }\n\n // ---------------------------------------------------- connections/grants\n\n createConnection(input: { organizationId: Id<'org'>; name: string; provider: string; environmentClass: Connection['environmentClass']; secretId: Id<'sec'> }): Connection {\n const connection: Connection = { id: newId('conn'), ...input, createdAt: nowIso() };\n this.db.run('insert into connections (id, organization_id, name, provider, environment_class, secret_id, created_at) values (?,?,?,?,?,?,?)',\n connection.id, connection.organizationId, connection.name, connection.provider,\n connection.environmentClass, connection.secretId, connection.createdAt);\n return connection;\n }\n\n findConnectionByName(organizationId: Id<'org'>, name: string): Connection | undefined {\n const row = this.db.get<Row>('select * from connections where organization_id = ? and name = ?', organizationId, name);\n return row && toConnection(row);\n }\n\n getConnection(id: Id<'conn'>): Connection {\n const row = this.db.get<Row>('select * from connections where id = ?', id);\n return required(row && toConnection(row), 'connection', id);\n }\n\n listConnections(organizationId: Id<'org'>): Connection[] {\n return this.db.all<Row>('select * from connections where organization_id = ? order by name', organizationId).map(toConnection);\n }\n\n putCapabilityGrant(input: {\n appId: Id<'app'>; environmentId: Id<'env'> | null; connectionId: Id<'conn'>;\n operations: string[]; constraints: Record<string, unknown>; status: CapabilityGrant['status']; approvedBy?: string | null;\n }): CapabilityGrant {\n const existing = this.db.get<Row>(\n \"select * from capability_grants where app_id = ? and connection_id = ? and ifnull(environment_id, '') = ifnull(?, '')\",\n input.appId, input.connectionId, input.environmentId);\n if (existing) {\n this.db.run('update capability_grants set operations = ?, constraints = ?, status = ?, approved_by = ? where id = ?',\n json(input.operations), json(input.constraints), input.status, input.approvedBy ?? null, existing.id);\n return toCapabilityGrant(this.db.get<Row>('select * from capability_grants where id = ?', existing.id)!);\n }\n const grant: CapabilityGrant = {\n id: newId('grant'), appId: input.appId, environmentId: input.environmentId, connectionId: input.connectionId,\n operations: input.operations, constraints: input.constraints, status: input.status,\n approvedBy: input.approvedBy ?? null, createdAt: nowIso(),\n };\n this.db.run('insert into capability_grants (id, app_id, environment_id, connection_id, operations, constraints, status, approved_by, created_at) values (?,?,?,?,?,?,?,?,?)',\n grant.id, grant.appId, grant.environmentId, grant.connectionId, json(grant.operations),\n json(grant.constraints), grant.status, grant.approvedBy, grant.createdAt);\n return grant;\n }\n\n listCapabilityGrants(appId: Id<'app'>): CapabilityGrant[] {\n return this.db.all<Row>('select * from capability_grants where app_id = ? order by created_at', appId).map(toCapabilityGrant);\n }\n\n getCapabilityGrant(id: Id<'grant'>): CapabilityGrant {\n const row = this.db.get<Row>('select * from capability_grants where id = ?', id);\n return required(row && toCapabilityGrant(row), 'capability grant', id);\n }\n\n revokeCapabilityGrant(id: Id<'grant'>): void {\n this.db.run(\"update capability_grants set status = 'revoked' where id = ?\", id);\n }\n\n // -------------------------------------------------------------- secrets\n\n createSecret(input: { organizationId: Id<'org'>; name: string }): Secret {\n const existing = this.db.get<Row>('select * from secrets where organization_id = ? and name = ?', input.organizationId, input.name);\n if (existing) return toSecret(existing);\n const secret: Secret = { id: newId('sec'), ...input, createdAt: nowIso() };\n this.db.run('insert into secrets (id, organization_id, name, created_at) values (?,?,?,?)',\n secret.id, secret.organizationId, secret.name, secret.createdAt);\n return secret;\n }\n\n findSecretByName(organizationId: Id<'org'>, name: string): Secret | undefined {\n const row = this.db.get<Row>('select * from secrets where organization_id = ? and name = ?', organizationId, name);\n return row && toSecret(row);\n }\n\n addSecretVersion(input: { secretId: Id<'sec'>; ciphertext: string; keyId: string }): SecretVersion {\n const previous = this.db.get<{ max: number | null }>('select max(version) as max from secret_versions where secret_id = ?', input.secretId);\n const version: SecretVersion = {\n id: newId('ver'), secretId: input.secretId, version: (previous?.max ?? 0) + 1,\n ciphertext: input.ciphertext, keyId: input.keyId, createdAt: nowIso(),\n };\n this.db.run('insert into secret_versions (id, secret_id, version, ciphertext, key_id, created_at) values (?,?,?,?,?,?)',\n version.id, version.secretId, version.version, version.ciphertext, version.keyId, version.createdAt);\n return version;\n }\n\n getSecretVersion(secretId: Id<'sec'>, version: string | number = 'latest'): SecretVersion | undefined {\n const row = version === 'latest'\n ? this.db.get<Row>('select * from secret_versions where secret_id = ? order by version desc limit 1', secretId)\n : this.db.get<Row>('select * from secret_versions where secret_id = ? and version = ?', secretId, Number(version));\n return row && toSecretVersion(row);\n }\n\n listSecrets(organizationId: Id<'org'>): Secret[] {\n return this.db.all<Row>('select * from secrets where organization_id = ? order by name', organizationId).map(toSecret);\n }\n\n // ---------------------------------------------------------------- audit\n\n appendAuditEvent(event: Omit<AuditEvent, 'id'>): AuditEvent {\n const record: AuditEvent = { ...event, id: newId('evt') };\n this.db.run(\n `insert into audit_events (id, organization_id, occurred_at, actor_type, actor_id, action, target_type,\n target_id, decision, reason, request_id, source_ip, metadata) values (?,?,?,?,?,?,?,?,?,?,?,?,?)`,\n record.id, record.organizationId, record.occurredAt, record.actorType, record.actorId, record.action,\n record.targetType, record.targetId, record.decision, record.reason, record.requestId, record.sourceIp,\n json(record.metadata));\n return record;\n }\n\n listAuditEvents(organizationId: Id<'org'>, options: { limit?: number; targetId?: string; action?: string; since?: string } = {}): AuditEvent[] {\n const clauses = ['organization_id = ?'];\n const params: unknown[] = [organizationId];\n if (options.targetId) { clauses.push('target_id = ?'); params.push(options.targetId); }\n if (options.action) { clauses.push('action = ?'); params.push(options.action); }\n if (options.since) { clauses.push('occurred_at >= ?'); params.push(options.since); }\n return this.db.all<Row>(\n `select * from audit_events where ${clauses.join(' and ')} order by occurred_at desc limit ?`,\n ...params, Math.min(options.limit ?? 100, 500)).map(toAuditEvent);\n }\n\n // ---------------------------------------------------------------- usage\n\n recordUsage(event: Omit<UsageEvent, 'id'>): void {\n // Idempotency key makes double delivery from the runtime harmless (\u00A726.2).\n this.db.run(\n `insert or ignore into usage_events (id, organization_id, app_id, meter, quantity, occurred_at, idempotency_key, metadata)\n values (?,?,?,?,?,?,?,?)`,\n newId('usage'), event.organizationId, event.appId, event.meter, event.quantity,\n event.occurredAt, event.idempotencyKey, json(event.metadata));\n }\n\n /**\n * Record one usage event unless it would breach the organization's ceiling,\n * or -- when `appLimit` is given -- that one app's share of it. Both sums and\n * the insert happen in a single transaction, so concurrent requests cannot\n * read the same total and each decide they fit.\n */\n claimUsageWithinLimit(event: Omit<UsageEvent, 'id'>, limit: number, since: string, appLimit?: number): boolean {\n return this.transaction(() => {\n const existing = this.db.get<Row>('select id from usage_events where idempotency_key = ?', event.idempotencyKey);\n if (existing) return true;\n const row = this.db.get<Row>(\n 'select coalesce(sum(quantity), 0) as quantity from usage_events where organization_id = ? and meter = ? and occurred_at >= ?',\n event.organizationId, event.meter, since);\n if (Number(row?.quantity ?? 0) + event.quantity > limit) return false;\n if (appLimit !== undefined) {\n const perApp = this.db.get<Row>(\n `select coalesce(sum(quantity), 0) as quantity from usage_events\n where organization_id = ? and app_id = ? and meter = ? and occurred_at >= ?`,\n event.organizationId, event.appId, event.meter, since);\n if (Number(perApp?.quantity ?? 0) + event.quantity > appLimit) return false;\n }\n this.recordUsage(event);\n return true;\n });\n }\n\n updateUsageMetadata(idempotencyKey: string, metadata: Record<string, unknown>): void {\n this.db.run('update usage_events set metadata = ? where idempotency_key = ?', json(metadata), idempotencyKey);\n }\n\n usageRollup(organizationId: Id<'org'>, options: { appId?: Id<'app'>; since?: string } = {}): Array<{ meter: string; quantity: number }> {\n const clauses = ['organization_id = ?'];\n const params: unknown[] = [organizationId];\n if (options.appId) { clauses.push('app_id = ?'); params.push(options.appId); }\n if (options.since) { clauses.push('occurred_at >= ?'); params.push(options.since); }\n // Driver rows are null-prototype; hand callers ordinary objects.\n return this.db.all<Row>(\n `select meter, sum(quantity) as quantity from usage_events where ${clauses.join(' and ')} group by meter order by meter`,\n ...params).map((row) => ({ meter: row.meter as string, quantity: Number(row.quantity) }));\n }\n\n // ------------------------------------------- durable rate/idempotency guards\n\n /**\n * A durable fixed-window counter, keyed by an arbitrary string. The broker\n * was its first caller and named the table; ingress now shares it to bound\n * anonymous traffic, so the mechanism is named for what it does.\n */\n claimRateLimit(key: string, maximum: number, windowMs: number, now = Date.now()): boolean {\n return this.transaction(() => {\n const row = this.db.get<{ count: number; reset_at: number }>(\n 'select count, reset_at from broker_rate_limits where key = ?', key);\n if (!row || row.reset_at <= now) {\n this.db.run(\n `insert into broker_rate_limits (key, count, reset_at) values (?,?,?)\n on conflict(key) do update set count = excluded.count, reset_at = excluded.reset_at`,\n key, 1, now + windowMs);\n return true;\n }\n if (row.count >= maximum) return false;\n this.db.run('update broker_rate_limits set count = count + 1 where key = ?', key);\n return true;\n });\n }\n\n getBrokerIdempotency(key: string, now = Date.now()): unknown | undefined {\n const row = this.db.get<{ response: string; expires_at: number }>(\n 'select response, expires_at from broker_idempotency where key = ?', key);\n if (!row) return undefined;\n if (row.expires_at <= now) {\n this.db.run('delete from broker_idempotency where key = ?', key);\n return undefined;\n }\n return JSON.parse(row.response) as unknown;\n }\n\n putBrokerIdempotency(key: string, response: unknown, ttlMs: number, now = Date.now()): void {\n this.db.run(\n `insert into broker_idempotency (key, response, expires_at) values (?,?,?)\n on conflict(key) do nothing`,\n key, JSON.stringify(response), now + ttlMs);\n }\n\n // ------------------------------------------------------------ lifecycle\n\n createLifecycleAction(input: Omit<LifecycleAction, 'id' | 'createdAt' | 'completedAt'> & { completedAt?: string | null }): LifecycleAction {\n const action: LifecycleAction = { id: newId('life'), completedAt: input.completedAt ?? null, createdAt: nowIso(), ...input } as LifecycleAction;\n this.db.run('insert into lifecycle_actions (id, app_id, action, status, reason, scheduled_for, completed_at, created_at) values (?,?,?,?,?,?,?,?)',\n action.id, action.appId, action.action, action.status, action.reason, action.scheduledFor,\n action.completedAt ?? null, action.createdAt);\n return action;\n }\n\n updateLifecycleAction(id: Id<'life'>, patch: { status?: LifecycleAction['status']; completedAt?: string | null }): void {\n if (patch.status) this.db.run('update lifecycle_actions set status = ? where id = ?', patch.status, id);\n if (patch.completedAt !== undefined) this.db.run('update lifecycle_actions set completed_at = ? where id = ?', patch.completedAt, id);\n }\n\n listLifecycleActions(appId: Id<'app'>): LifecycleAction[] {\n return this.db.all<Row>('select * from lifecycle_actions where app_id = ? order by created_at desc', appId).map(toLifecycleAction);\n }\n\n // -------------------------------------------------------------- outbox\n\n enqueueEvent(input: { organizationId: Id<'org'>; type: string; payload: Record<string, unknown> }): void {\n this.db.run('insert into outbox_events (id, organization_id, type, payload, created_at) values (?,?,?,?,?)',\n newId('evt'), input.organizationId, input.type, json(input.payload), nowIso());\n }\n\n enqueueEventOnce(input: { organizationId: Id<'org'>; type: string; payload: Record<string, unknown> }): boolean {\n const existing = this.db.get<Row>('select id from outbox_events where organization_id = ? and type = ? limit 1',\n input.organizationId, input.type);\n if (existing) return false;\n this.enqueueEvent(input);\n return true;\n }\n\n estimatedMonthlyDeploymentCents(organizationId: Id<'org'>, excludingEnvironmentId?: Id<'env'>): number {\n const rows = this.db.all<Row>(\n `select d.environment_id, d.plan from deployments d where d.organization_id = ?\n and d.status in ('queued','planning','awaiting_approval','building','provisioning','migrating','deploying','verifying','ready')\n ${excludingEnvironmentId ? 'and d.environment_id != ?' : ''}`,\n ...[organizationId, excludingEnvironmentId].filter((value) => value !== undefined));\n const byEnvironment = new Map<string, number>();\n for (const row of rows) {\n const plan = parseJson<{ estimatedMonthlyCents?: number | null }>(row.plan, {});\n const cost = plan.estimatedMonthlyCents ?? 0;\n const environmentId = row.environment_id as string;\n byEnvironment.set(environmentId, Math.max(byEnvironment.get(environmentId) ?? 0, cost));\n }\n return [...byEnvironment.values()].reduce((sum, cost) => sum + cost, 0);\n }\n\n claimUnpublishedEvents(limit = 50): Array<{ id: string; organizationId: string; type: string; payload: Record<string, unknown> }> {\n return this.db.all<Row>('select * from outbox_events where published_at is null order by created_at limit ?', limit)\n .map((row) => ({\n id: row.id as string,\n organizationId: row.organization_id as string,\n type: row.type as string,\n payload: parseJson<Record<string, unknown>>(row.payload, {}),\n }));\n }\n\n markEventPublished(id: string): void {\n this.db.run('update outbox_events set published_at = ? where id = ?', nowIso(), id);\n }\n\n createWebhookEndpoint(input: {\n organizationId: Id<'org'>; url: string; secretCiphertext: string; secretKeyId: string; eventTypes: string[];\n }): { id: string; url: string; eventTypes: string[]; enabled: boolean; createdAt: string } {\n const existing = this.db.get<Row>('select * from webhook_endpoints where organization_id = ? and url = ?', input.organizationId, input.url);\n const endpoint = {\n id: (existing?.id as string | undefined) ?? newId('hook'), url: input.url,\n eventTypes: input.eventTypes, enabled: true,\n createdAt: (existing?.created_at as string | undefined) ?? nowIso(),\n };\n this.db.run(\n `insert into webhook_endpoints (id, organization_id, url, secret_ciphertext, secret_key_id, event_types, created_at)\n values (?,?,?,?,?,?,?) on conflict(organization_id, url) do update set\n secret_ciphertext = excluded.secret_ciphertext, secret_key_id = excluded.secret_key_id,\n event_types = excluded.event_types, enabled = 1`,\n endpoint.id, input.organizationId, input.url, input.secretCiphertext, input.secretKeyId, json(input.eventTypes), endpoint.createdAt,\n );\n return endpoint;\n }\n\n listWebhookEndpoints(organizationId: Id<'org'>): Array<{\n id: string; url: string; eventTypes: string[]; enabled: boolean; createdAt: string;\n secretCiphertext: string; secretKeyId: string;\n }> {\n return this.db.all<Row>('select * from webhook_endpoints where organization_id = ? order by created_at', organizationId).map((row) => ({\n id: row.id as string, url: row.url as string, eventTypes: parseJson<string[]>(row.event_types, []),\n enabled: bool(row.enabled), createdAt: row.created_at as string,\n secretCiphertext: row.secret_ciphertext as string, secretKeyId: row.secret_key_id as string,\n }));\n }\n\n deleteWebhookEndpoint(organizationId: Id<'org'>, id: string): boolean {\n return this.transaction(() => {\n const endpoint = this.db.get<Row>('select id from webhook_endpoints where organization_id = ? and id = ?', organizationId, id);\n if (!endpoint) return false;\n this.db.run('delete from webhook_deliveries where endpoint_id = ?', id);\n this.db.run('delete from webhook_endpoints where id = ?', id);\n return true;\n });\n }\n\n createWebhookDeliveries(): number {\n const endpoints = this.db.all<Row>('select * from webhook_endpoints where enabled = 1');\n let created = 0;\n for (const event of this.db.all<Row>('select * from outbox_events where published_at is null order by created_at limit 100')) {\n for (const endpoint of endpoints.filter((entry) => entry.organization_id === event.organization_id)) {\n const eventTypes = parseJson<string[]>(endpoint.event_types, []);\n if (eventTypes.length > 0 && !eventTypes.includes(event.type as string)) continue;\n created += this.db.run(\n `insert or ignore into webhook_deliveries\n (id, outbox_event_id, endpoint_id, status, attempts, next_attempt_at, created_at)\n values (?,?,?,'pending',0,?,?)`,\n newId('delivery'), event.id, endpoint.id, nowIso(), nowIso()).changes;\n }\n }\n return created;\n }\n\n claimWebhookDelivery(now = nowIso()): {\n id: string; eventId: string; endpointId: string; organizationId: Id<'org'>; url: string;\n secretCiphertext: string; secretKeyId: string; type: string; payload: Record<string, unknown>;\n createdAt: string; attempts: number;\n } | undefined {\n return this.transaction(() => {\n const row = this.db.get<Row>(\n `select d.*, e.organization_id, e.type, e.payload, e.created_at as event_created_at,\n w.url, w.secret_ciphertext, w.secret_key_id\n from webhook_deliveries d join outbox_events e on e.id = d.outbox_event_id\n join webhook_endpoints w on w.id = d.endpoint_id\n where d.status = 'pending' and d.next_attempt_at <= ? and w.enabled = 1\n order by d.created_at limit 1`, now);\n if (!row) return undefined;\n const leaseUntil = new Date(Date.now() + 30_000).toISOString();\n const changed = this.db.run(\n \"update webhook_deliveries set attempts = attempts + 1, next_attempt_at = ? where id = ? and status = 'pending' and next_attempt_at <= ?\",\n leaseUntil, row.id, now).changes;\n if (changed !== 1) return undefined;\n return {\n id: row.id as string, eventId: row.outbox_event_id as string, endpointId: row.endpoint_id as string,\n organizationId: row.organization_id as Id<'org'>, url: row.url as string,\n secretCiphertext: row.secret_ciphertext as string, secretKeyId: row.secret_key_id as string,\n type: row.type as string, payload: parseJson<Record<string, unknown>>(row.payload, {}),\n createdAt: row.event_created_at as string, attempts: Number(row.attempts) + 1,\n };\n });\n }\n\n finishWebhookDelivery(id: string, input: { delivered: boolean; status?: number; error?: string; attempts: number }): void {\n const terminal = input.delivered || input.attempts >= 8;\n const next = new Date(Date.now() + Math.min(3_600_000, 1000 * 2 ** input.attempts)).toISOString();\n this.db.run(\n `update webhook_deliveries set status = ?, next_attempt_at = ?, response_status = ?, last_error = ?, delivered_at = ? where id = ?`,\n input.delivered ? 'delivered' : terminal ? 'failed' : 'pending', next, input.status ?? null,\n input.error?.slice(0, 2000) ?? null, input.delivered ? nowIso() : null, id,\n );\n }\n\n markPublishableEvents(): number {\n return this.db.run(\n `update outbox_events set published_at = ? where published_at is null and\n not exists (select 1 from webhook_deliveries d where d.outbox_event_id = outbox_events.id and d.status = 'pending')`,\n nowIso()).changes;\n }\n}\n\n// ------------------------------------------------------------ row mappers\n\nfunction toOrganization(row: Row): Organization {\n const storedPolicy = parseJson<Partial<OrganizationPolicy>>(row.policy, {});\n return {\n id: row.id as Id<'org'>, slug: row.slug as string, displayName: row.display_name as string,\n plan: row.plan as string, policy: { ...DEFAULT_ORGANIZATION_POLICY, ...storedPolicy },\n createdAt: row.created_at as string,\n };\n}\n\nfunction toUser(row: Row): User {\n return { id: row.id as Id<'user'>, email: row.email as string, displayName: row.display_name as string, createdAt: row.created_at as string };\n}\n\nfunction toMembership(row: Row): Membership {\n return {\n id: row.id as Id<'mem'>, organizationId: row.organization_id as Id<'org'>, userId: row.user_id as Id<'user'>,\n role: row.role as Membership['role'], groups: parseJson<string[]>(row.groups, []), createdAt: row.created_at as string,\n };\n}\n\nfunction toServiceAccount(row: Row): ServiceAccount {\n return {\n id: row.id as Id<'sa'>, organizationId: row.organization_id as Id<'org'>, name: row.name as string,\n role: row.role as Membership['role'], tokenHash: row.token_hash as string, createdAt: row.created_at as string,\n };\n}\n\nfunction toApp(row: Row): App {\n return {\n id: row.id as Id<'app'>, organizationId: row.organization_id as Id<'org'>, slug: row.slug as string,\n displayName: row.display_name as string, description: (row.description as string | null) ?? null,\n ownerMembershipId: row.owner_membership_id as Id<'mem'>, status: row.status as AppState,\n riskTier: row.risk_tier as App['riskTier'],\n currentProductionRevisionId: (row.current_production_revision_id as Id<'rev'> | null) ?? null,\n lastAccessedAt: (row.last_accessed_at as string | null) ?? null,\n archivedAt: (row.archived_at as string | null) ?? null,\n deleteAfter: (row.delete_after as string | null) ?? null,\n createdAt: row.created_at as string, updatedAt: row.updated_at as string,\n };\n}\n\nfunction toRevision(row: Row): AppRevision {\n return {\n id: row.id as Id<'rev'>, appId: row.app_id as Id<'app'>, revisionNumber: Number(row.revision_number),\n manifestVersion: row.manifest_version as string,\n normalizedManifest: parseJson<Record<string, unknown>>(row.normalized_manifest, {}),\n manifestSha256: row.manifest_sha256 as string, artifactUri: row.artifact_uri as string,\n artifactSha256: row.artifact_sha256 as string,\n sourceMetadata: parseJson<Record<string, unknown>>(row.source_metadata, {}),\n createdByType: row.created_by_type as AppRevision['createdByType'],\n createdById: row.created_by_id as string, createdAt: row.created_at as string,\n };\n}\n\nfunction toEnvironment(row: Row): Environment {\n return {\n id: row.id as Id<'env'>, appId: row.app_id as Id<'app'>, name: row.name as string,\n kind: row.kind as EnvironmentKind, hostname: row.hostname as string,\n expiresAt: (row.expires_at as string | null) ?? null, createdAt: row.created_at as string,\n };\n}\n\nfunction toDeployment(row: Row): Deployment {\n return {\n id: row.id as Id<'dep'>, organizationId: row.organization_id as Id<'org'>, appId: row.app_id as Id<'app'>,\n environmentId: row.environment_id as Id<'env'>, revisionId: row.revision_id as Id<'rev'>,\n runtimeTargetId: row.runtime_target_id as Id<'target'>, status: row.status as DeploymentState,\n idempotencyKey: row.idempotency_key as string, plan: parseJson<Record<string, unknown>>(row.plan, {}),\n providerState: parseJson<Record<string, unknown>>(row.provider_state, {}),\n errorCode: (row.error_code as string | null) ?? null,\n errorDetail: row.error_detail ? parseJson<Record<string, unknown>>(row.error_detail, {}) : null,\n startedAt: (row.started_at as string | null) ?? null, readyAt: (row.ready_at as string | null) ?? null,\n finishedAt: (row.finished_at as string | null) ?? null, createdAt: row.created_at as string,\n };\n}\n\nfunction toRuntimeTarget(row: Row): RuntimeTarget {\n return {\n id: row.id as Id<'target'>, organizationId: (row.organization_id as Id<'org'> | null) ?? null,\n name: row.name as string, providerKind: row.provider_kind as string, region: row.region as string,\n config: parseJson<Record<string, unknown>>(row.config, {}), healthy: bool(row.healthy),\n createdAt: row.created_at as string,\n };\n}\n\nfunction toRuntimeInstance(row: Row): RuntimeInstance {\n return {\n id: row.id as Id<'inst'>, deploymentId: row.deployment_id as Id<'dep'>,\n runtimeTargetId: row.runtime_target_id as Id<'target'>, providerRef: row.provider_ref as string,\n state: row.state as RuntimeInstanceState, updatedAt: row.updated_at as string,\n };\n}\n\nfunction toResourceClaim(row: Row): ResourceClaim {\n return {\n id: row.id as Id<'res'>, environmentId: row.environment_id as Id<'env'>, kind: row.kind as ResourceKind,\n name: row.name as string, spec: parseJson<Record<string, unknown>>(row.spec, {}),\n };\n}\n\nfunction toResourceBinding(row: Row): ResourceBinding {\n return {\n id: row.id as Id<'bind'>, claimId: row.claim_id as Id<'res'>, environmentId: row.environment_id as Id<'env'>,\n kind: row.kind as ResourceKind, name: row.name as string, providerKind: row.provider_kind as string,\n providerRef: row.provider_ref as string, metadata: parseJson<Record<string, unknown>>(row.metadata, {}),\n createdAt: row.created_at as string,\n };\n}\n\nfunction toAccessBinding(row: Row): AccessBinding {\n return {\n id: row.id as Id<'access'>, appId: row.app_id as Id<'app'>,\n subjectType: row.subject_type as AccessBinding['subjectType'], subject: row.subject as string,\n role: row.role as AppRole, grantedBy: row.granted_by as string, createdAt: row.created_at as string,\n };\n}\n\nfunction toConnection(row: Row): Connection {\n return {\n id: row.id as Id<'conn'>, organizationId: row.organization_id as Id<'org'>, name: row.name as string,\n provider: row.provider as string, environmentClass: row.environment_class as Connection['environmentClass'],\n secretId: row.secret_id as Id<'sec'>, createdAt: row.created_at as string,\n };\n}\n\nfunction toCapabilityGrant(row: Row): CapabilityGrant {\n return {\n id: row.id as Id<'grant'>, appId: row.app_id as Id<'app'>,\n environmentId: (row.environment_id as Id<'env'> | null) ?? null,\n connectionId: row.connection_id as Id<'conn'>, operations: parseJson<string[]>(row.operations, []),\n constraints: parseJson<Record<string, unknown>>(row.constraints, {}),\n status: row.status as CapabilityGrant['status'], approvedBy: (row.approved_by as string | null) ?? null,\n createdAt: row.created_at as string,\n };\n}\n\nfunction toSecret(row: Row): Secret {\n return { id: row.id as Id<'sec'>, organizationId: row.organization_id as Id<'org'>, name: row.name as string, createdAt: row.created_at as string };\n}\n\nfunction toSecretVersion(row: Row): SecretVersion {\n return {\n id: row.id as Id<'ver'>, secretId: row.secret_id as Id<'sec'>, version: Number(row.version),\n ciphertext: row.ciphertext as string, keyId: row.key_id as string, createdAt: row.created_at as string,\n };\n}\n\nfunction toAuditEvent(row: Row): AuditEvent {\n return {\n id: row.id as Id<'evt'>, organizationId: row.organization_id as Id<'org'>, occurredAt: row.occurred_at as string,\n actorType: row.actor_type as AuditEvent['actorType'], actorId: (row.actor_id as string | null) ?? null,\n action: row.action as string, targetType: row.target_type as string, targetId: row.target_id as string,\n decision: (row.decision as AuditEvent['decision']) ?? null, reason: (row.reason as string | null) ?? null,\n requestId: (row.request_id as string | null) ?? null, sourceIp: (row.source_ip as string | null) ?? null,\n metadata: parseJson<Record<string, unknown>>(row.metadata, {}),\n };\n}\n\nfunction toLifecycleAction(row: Row): LifecycleAction {\n return {\n id: row.id as Id<'life'>, appId: row.app_id as Id<'app'>, action: row.action as LifecycleAction['action'],\n status: row.status as LifecycleAction['status'], reason: row.reason as string,\n scheduledFor: (row.scheduled_for as string | null) ?? null,\n completedAt: (row.completed_at as string | null) ?? null, createdAt: row.created_at as string,\n };\n}\n", "/**\n * Thin wrapper over `node:sqlite` so the rest of the control plane never\n * touches driver details. Swapping in Postgres later means reimplementing\n * this file and the repositories, not the callers.\n */\nimport { DatabaseSync } from 'node:sqlite';\n\nexport type Row = Record<string, unknown>;\n\nexport class Database {\n private readonly db: DatabaseSync;\n private depth = 0;\n\n constructor(location: string) {\n this.db = new DatabaseSync(location);\n this.db.exec('pragma journal_mode = WAL');\n this.db.exec('pragma foreign_keys = on');\n this.db.exec('pragma busy_timeout = 5000');\n }\n\n exec(sql: string): void {\n this.db.exec(sql);\n }\n\n all<T = Row>(sql: string, ...params: unknown[]): T[] {\n return this.db.prepare(sql).all(...(params as never[])) as T[];\n }\n\n get<T = Row>(sql: string, ...params: unknown[]): T | undefined {\n return this.db.prepare(sql).get(...(params as never[])) as T | undefined;\n }\n\n run(sql: string, ...params: unknown[]): { changes: number } {\n const result = this.db.prepare(sql).run(...(params as never[]));\n return { changes: Number(result.changes) };\n }\n\n /** Nesting uses savepoints so nested writes participate in one commit. */\n transaction<T>(fn: () => T): T {\n const name = `sp_${this.depth}`;\n this.depth++;\n this.db.exec(this.depth === 1 ? 'begin immediate' : `savepoint ${name}`);\n try {\n const result = fn();\n this.db.exec(this.depth === 1 ? 'commit' : `release ${name}`);\n return result;\n } catch (error) {\n this.db.exec(this.depth === 1 ? 'rollback' : `rollback to ${name}`);\n throw error;\n } finally {\n this.depth--;\n }\n }\n\n close(): void {\n this.db.close();\n }\n}\n\nexport function json(value: unknown): string {\n return JSON.stringify(value ?? null);\n}\n\nexport function parseJson<T>(value: unknown, fallback: T): T {\n if (typeof value !== 'string') return fallback;\n try {\n return JSON.parse(value) as T;\n } catch {\n return fallback;\n }\n}\n\nexport function bool(value: unknown): boolean {\n return value === 1 || value === true || value === '1';\n}\n\nexport function nowIso(): string {\n return new Date().toISOString();\n}\n", "/**\n * HTTP client for the control-plane API.\n *\n * Turns API error bodies back into `TinyError`, so the CLI, the MCP server,\n * and in-process callers all handle failures the same way.\n */\nimport { TinyError, type ErrorCode } from '@tinycloud/domain';\nimport { createArtifactUpload } from '@tinycloud/orchestrator';\n\nexport interface ClientOptions {\n apiUrl: string;\n token?: string | undefined;\n timeoutMs?: number;\n}\n\nexport class ApiClient {\n readonly apiUrl: string;\n private readonly token: string | undefined;\n private readonly timeoutMs: number;\n\n constructor(options: ClientOptions) {\n this.apiUrl = options.apiUrl.replace(/\\/$/, '');\n this.token = options.token;\n this.timeoutMs = options.timeoutMs ?? 120_000;\n }\n\n async request<T>(method: string, path: string, options: {\n body?: unknown;\n idempotencyKey?: string;\n query?: Record<string, string | number | undefined>;\n } = {}): Promise<T> {\n const url = new URL(this.apiUrl + path);\n for (const [key, value] of Object.entries(options.query ?? {})) {\n if (value !== undefined) url.searchParams.set(key, String(value));\n }\n\n const headers: Record<string, string> = { accept: 'application/json' };\n if (this.token) headers.authorization = `Bearer ${this.token}`;\n if (options.body !== undefined) headers['content-type'] = 'application/json';\n if (options.idempotencyKey) headers['idempotency-key'] = options.idempotencyKey;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n ...(options.body !== undefined ? { body: JSON.stringify(options.body) } : {}),\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch (error) {\n throw new TinyError('PROVIDER_UNAVAILABLE', `Could not reach the control plane at ${this.apiUrl}.`, {\n remediation: 'Check that the API is running and that `tiny login --api` points at it.',\n retryable: true,\n cause: error,\n });\n }\n\n if (response.status === 204) return undefined as T;\n const payload = await response.json().catch(() => ({})) as { error?: { code: ErrorCode; message: string; remediation?: string; retryable?: boolean; details?: Record<string, unknown> } };\n\n if (!response.ok) {\n const error = payload.error;\n throw new TinyError(error?.code ?? 'INTERNAL', error?.message ?? `Request failed with ${response.status}.`, {\n ...(error?.remediation ? { remediation: error.remediation } : {}),\n ...(error?.retryable !== undefined ? { retryable: error.retryable } : {}),\n ...(error?.details ? { details: error.details } : {}),\n ...(response.headers.get('x-request-id') ? { requestId: response.headers.get('x-request-id')! } : {}),\n });\n }\n return payload as T;\n }\n\n get<T>(path: string, query?: Record<string, string | number | undefined>): Promise<T> {\n return this.request<T>('GET', path, query ? { query } : {});\n }\n\n post<T>(path: string, body?: unknown, idempotencyKey?: string): Promise<T> {\n return this.request<T>('POST', path, {\n ...(body !== undefined ? { body } : {}),\n ...(idempotencyKey ? { idempotencyKey } : {}),\n });\n }\n\n put<T>(path: string, body?: unknown): Promise<T> {\n return this.request<T>('PUT', path, body !== undefined ? { body } : {});\n }\n\n del<T>(path: string, query?: Record<string, string | number | undefined>): Promise<T> {\n return this.request<T>('DELETE', path, query ? { query } : {});\n }\n\n uploadDirectory(directory: string): Promise<{ artifactUri: string; sha256: string; files: number; sizeBytes: number }> {\n return this.post('/v1/artifacts', createArtifactUpload(directory));\n }\n}\n", "/**\n * CLI credential and context storage.\n *\n * Tokens live in a 0600 file under the user's home directory, never in the\n * repository and never in a command argument where another process could read\n * it from the process list (blueprint \u00A719.1).\n */\nimport { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nexport interface CliCredentials {\n apiUrl: string;\n token: string;\n organizationSlug?: string;\n email?: string;\n}\n\nexport interface CliConfig {\n version: 1;\n current: string | null;\n profiles: Record<string, CliCredentials>;\n}\n\nconst EMPTY: CliConfig = { version: 1, current: null, profiles: {} };\n\nexport function configPath(): string {\n return process.env.TINY_CONFIG ?? join(homedir(), '.tiny', 'credentials.json');\n}\n\nexport function loadConfig(): CliConfig {\n const path = configPath();\n if (!existsSync(path)) return { ...EMPTY, profiles: {} };\n try {\n return JSON.parse(readFileSync(path, 'utf8')) as CliConfig;\n } catch {\n return { ...EMPTY, profiles: {} };\n }\n}\n\nexport function saveConfig(config: CliConfig): void {\n const path = configPath();\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(config, null, 2)}\\n`, { mode: 0o600 });\n chmodSync(path, 0o600);\n}\n\nexport function currentProfile(): CliCredentials | null {\n const config = loadConfig();\n if (!config.current) return null;\n return config.profiles[config.current] ?? null;\n}\n\nexport function setProfile(name: string, credentials: CliCredentials): void {\n const config = loadConfig();\n config.profiles[name] = credentials;\n config.current = name;\n saveConfig(config);\n}\n\nexport function clearProfile(name?: string): void {\n const config = loadConfig();\n const target = name ?? config.current;\n if (target) delete config.profiles[target];\n if (config.current === target) config.current = Object.keys(config.profiles)[0] ?? null;\n saveConfig(config);\n}\n", "/**\n * Watching a deployment finish.\n *\n * `/v1/apps:deploy` answers 202 and a worker does the work, so a deploy that\n * returned is not a deploy that happened. These helpers live outside `main.ts`\n * because the MCP server wants the same \"wait, then say what happened\" and\n * because a poll loop is worth testing without spawning a CLI.\n */\n\n/** The deployment record as `GET /v1/deployments/:id` returns it. */\nexport interface DeploymentRecord {\n id: string;\n status: string;\n errorCode?: string | null;\n errorDetail?: Record<string, unknown> | null;\n providerState?: Record<string, unknown>;\n}\n\n/** The states a deployment stops moving in. */\nexport const TERMINAL_STATUSES = ['ready', 'failed', 'superseded', 'rolled_back'];\n\nexport const DEFAULT_WAIT_MS = 300_000;\n\nexport interface WatchOptions {\n timeoutMs?: number;\n pollMs?: number;\n /** Called once per distinct status, for progress output. */\n onStatus?: (status: string) => void;\n}\n\n/**\n * Poll until the record reaches a terminal state. Returns `null` on timeout\n * rather than throwing: the deployment is unaffected by our having stopped\n * watching it, and reporting it as failed would be a lie.\n */\nexport async function awaitDeployment(\n fetchDeployment: () => Promise<DeploymentRecord>,\n options: WatchOptions = {},\n): Promise<DeploymentRecord | null> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_WAIT_MS;\n const pollMs = options.pollMs ?? 500;\n const deadline = Date.now() + timeoutMs;\n let reported = '';\n for (;;) {\n const record = await fetchDeployment();\n if (record.status !== reported) {\n reported = record.status;\n options.onStatus?.(record.status);\n }\n if (TERMINAL_STATUSES.includes(record.status)) return record;\n // Checked after the read, so a deployment that is already terminal is\n // reported even when the caller allowed no time at all.\n if (Date.now() >= deadline) return null;\n await new Promise((resolve) => setTimeout(resolve, pollMs));\n }\n}\n\nexport interface BuildSummary {\n artifactUri: string;\n cached: boolean;\n durationMs: number;\n}\n\n/**\n * How this deployment's artifact came to exist, or `undefined` when the app\n * ships prebuilt and no build ran at all.\n */\nexport function buildSummary(providerState: Record<string, unknown> | undefined): BuildSummary | undefined {\n if (!providerState || typeof providerState.builtArtifactUri !== 'string') return undefined;\n return {\n artifactUri: providerState.builtArtifactUri,\n cached: providerState.buildCached === true,\n durationMs: Number(providerState.buildMs ?? 0),\n };\n}\n\n/** One line about the build, or nothing when there was no build to report. */\nexport function describeBuild(summary: BuildSummary | undefined): string | null {\n if (!summary) return null;\n // A cached build reports zero duration, so printing \"built in 0.0s\" would\n // read as a suspiciously fast build rather than as no build at all.\n return summary.cached\n ? `Reused a cached build (${summary.artifactUri})`\n : `Built in ${(summary.durationMs / 1000).toFixed(1)}s (${summary.artifactUri})`;\n}\n\n/** Why it failed, in one line, without inventing detail the record lacks. */\nexport function describeFailure(record: DeploymentRecord | null): string {\n const message = record?.errorDetail?.message;\n if (typeof message === 'string' && message !== '') return message;\n return record?.errorCode ?? 'no reason was recorded';\n}\n\n/** The deployer's own remediation when it recorded one; a pointer otherwise. */\nexport function failureRemediation(record: DeploymentRecord | null): string {\n const remediation = record?.errorDetail?.remediation;\n return typeof remediation === 'string' && remediation !== ''\n ? remediation\n : 'Read `tiny logs` for the failure, fix it, and deploy again.';\n}\n", "/**\n * Agent-safe CLI output (blueprint \u00A719.1).\n *\n * Two contracts, both stable: `--json` prints exactly one versioned object to\n * stdout and nothing else, while progress and diagnostics go to stderr; and\n * exit codes map to error classes so a script can branch without parsing text.\n */\nimport { isTinyError, type ErrorCode } from '@tinycloud/domain';\n\nexport const CLI_API_VERSION = 'cli.tinycloud.dev/v1';\n\nexport const EXIT = {\n ok: 0,\n error: 1,\n usage: 2,\n auth: 3,\n validation: 4,\n policy: 5,\n notFound: 6,\n interactionRequired: 7,\n conflict: 8,\n unavailable: 9,\n} as const;\n\nconst EXIT_BY_CODE: Partial<Record<ErrorCode, number>> = {\n UNAUTHENTICATED: EXIT.auth,\n FORBIDDEN: EXIT.policy,\n NOT_FOUND: EXIT.notFound,\n CONFLICT: EXIT.conflict,\n VALIDATION_FAILED: EXIT.validation,\n MANIFEST_PARSE_FAILED: EXIT.validation,\n MANIFEST_SCHEMA_INVALID: EXIT.validation,\n MANIFEST_POLICY_VIOLATION: EXIT.policy,\n MANIFEST_UNSUPPORTED_API_VERSION: EXIT.validation,\n APPROVAL_REQUIRED: EXIT.policy,\n PLAN_STALE: EXIT.conflict,\n PLAN_EXPIRED: EXIT.conflict,\n TARGET_INCOMPATIBLE: EXIT.validation,\n INTERACTION_REQUIRED: EXIT.interactionRequired,\n PROVIDER_UNAVAILABLE: EXIT.unavailable,\n RATE_LIMITED: EXIT.unavailable,\n DELETE_CONFIRMATION_REQUIRED: EXIT.interactionRequired,\n};\n\nexport interface OutputOptions {\n json: boolean;\n}\n\nexport class Output {\n private readonly json: boolean;\n\n constructor(options: OutputOptions) {\n this.json = options.json;\n }\n\n /** Human-readable progress. Never written to stdout in JSON mode. */\n progress(message: string): void {\n process.stderr.write(`${message}\\n`);\n }\n\n step(ok: boolean, message: string): void {\n process.stderr.write(`${ok ? '\u2713' : '\u2717'} ${message}\\n`);\n }\n\n warn(message: string): void {\n process.stderr.write(`! ${message}\\n`);\n }\n\n /** Terminal success output. Exactly one JSON object in `--json` mode. */\n result(payload: Record<string, unknown>, human: () => void): void {\n if (this.json) {\n process.stdout.write(`${JSON.stringify({ apiVersion: CLI_API_VERSION, ok: true, ...payload }, null, 2)}\\n`);\n return;\n }\n human();\n }\n\n fail(error: unknown): never {\n if (isTinyError(error)) {\n const body = {\n apiVersion: CLI_API_VERSION,\n ok: false,\n error: {\n code: error.code,\n message: error.message,\n remediation: error.remediation,\n retryable: error.retryable,\n details: error.details,\n },\n };\n if (this.json) process.stdout.write(`${JSON.stringify(body, null, 2)}\\n`);\n else {\n process.stderr.write(`\\n\u2717 ${error.code}: ${error.message}\\n`);\n process.stderr.write(` \u2192 ${error.remediation}\\n`);\n const next = error.details.nextActions;\n if (Array.isArray(next)) for (const action of next) process.stderr.write(` \u2192 ${String(action)}\\n`);\n }\n process.exit(EXIT_BY_CODE[error.code] ?? EXIT.error);\n }\n const message = error instanceof Error ? error.message : String(error);\n if (this.json) process.stdout.write(`${JSON.stringify({ apiVersion: CLI_API_VERSION, ok: false, error: { code: 'INTERNAL', message } }, null, 2)}\\n`);\n else process.stderr.write(`\\n\u2717 ${message}\\n`);\n process.exit(EXIT.error);\n }\n}\n\n/** Non-interactive callers must never be blocked on a prompt (\u00A719.1). */\nexport function isInteractive(): boolean {\n return process.stdin.isTTY === true && process.stdout.isTTY === true;\n}\n"],
|
|
5
|
-
"mappings": ";;;AAWA,SAAS,cAAAA,aAAY,gBAAAC,eAAc,iBAAAC,sBAAqB;AACxD,SAAS,UAAU,QAAAC,OAAM,eAAe;AACxC,SAAS,uBAAuB;;;ACQhC,IAAI,aAAyB,IAAI,WAAW,EAAE;;;AChBvC,IAAM,cAAc;AAAA;AAAA,EAEzB,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,UAAU;AAAA;AAAA,EAEV,uBAAuB;AAAA,EACvB,yBAAyB;AAAA,EACzB,2BAA2B;AAAA,EAC3B,kCAAkC;AAAA;AAAA,EAElC,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,sBAAsB;AAAA,EACtB,cAAc;AAAA,EACd,oBAAoB;AAAA;AAAA,EAEpB,4BAA4B;AAAA,EAC5B,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,yBAAyB;AAAA;AAAA,EAEzB,cAAc;AAAA,EACd,eAAe;AAAA,EACf,8BAA8B;AAAA;AAAA,EAE9B,wBAAwB;AAAA,EACxB,gCAAgC;AAAA,EAChC,0BAA0B;AAAA,EAC1B,eAAe;AAAA;AAAA,EAEf,sBAAsB;AACxB;AAaO,IAAM,YAAN,cAAwB,MAAM;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EAEA,YACE,MACA,SACA,UAMI,CAAC,GACL;AACA,UAAM,SAAS,QAAQ,UAAU,SAAY,SAAY,EAAE,OAAO,QAAQ,MAAM,CAAC;AACjF,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,cAAc,QAAQ,eAAe,oBAAoB,IAAI,KAAK;AACvE,SAAK,YAAY,QAAQ,aAAa,UAAU,IAAI,IAAI;AACxD,SAAK,UAAU,QAAQ,WAAW,CAAC;AACnC,QAAI,QAAQ,UAAW,MAAK,YAAY,QAAQ;AAAA,EAClD;AAAA,EAEA,IAAI,aAAqB;AACvB,WAAO,YAAY,KAAK,IAAI;AAAA,EAC9B;AAAA,EAEA,SAAmC;AACjC,UAAM,OAAsB;AAAA,MAC1B,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,aAAa,KAAK;AAAA,MAClB,WAAW,KAAK;AAAA,IAClB;AACA,QAAI,KAAK,UAAW,MAAK,YAAY,KAAK;AAC1C,QAAI,OAAO,KAAK,KAAK,OAAO,EAAE,SAAS,EAAG,MAAK,UAAU,KAAK;AAC9D,WAAO,EAAE,OAAO,KAAK;AAAA,EACvB;AACF;AAEA,IAAM,YAAY,oBAAI,IAAe;AAAA,EACnC;AAAA,EAAgB;AAAA,EAAY;AAAA,EAAwB;AACtD,CAAC;AAED,IAAM,sBAA0D;AAAA,EAC9D,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,WAAW;AAAA,EACX,mBAAmB;AAAA,EACnB,YAAY;AAAA,EACZ,4BAA4B;AAAA,EAC5B,sBAAsB;AAAA,EACtB,yBAAyB;AAAA,EACzB,qBAAqB;AAAA,EACrB,8BAA8B;AAChC;AAEO,SAAS,YAAY,OAAoC;AAC9D,SAAO,iBAAiB;AAC1B;;;ACnHA,IAAM,UAAU,EAAE,GAAG,KAAM,GAAG,KAAQ,GAAG,MAAW,GAAG,MAAW;AAGlE,IAAM,UAAU;AAET,SAAS,cAAc,OAAuB;AACnD,QAAM,QAAQ,QAAQ,KAAK,KAAK;AAChC,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,UAAU,qBAAqB,qBAAqB,KAAK,MAAM;AAAA,MACvE,aAAa;AAAA,MACb,SAAS,EAAE,MAAM;AAAA,IACnB,CAAC;AAAA,EACH;AACA,SAAO,OAAO,MAAM,CAAC,CAAC,IAAI,QAAQ,MAAM,CAAC,CAAiB;AAC5D;;;AC4BO,IAAM,8BAAkD;AAAA,EAC7D,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,gCAAgC;AAAA,EAChC,8BAA8B;AAAA,EAC9B,eAAe;AAAA,EACf,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,0BAA0B;AAAA,EAC1B,4BAA4B;AAAA,EAC5B,2BAA2B;AAAA,EAC3B,8BAA8B;AAAA,EAC9B,eAAe,CAAC,OAAO,OAAO,QAAQ,SAAS,aAAa,WAAW,QAAQ,KAAK;AACtF;;;ACzDA,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,SAAS,YAAY;AAC9B,SAAS,qBAAqB;;;ACQ9B,IAAM,YAAY,oBAAI,IAAI;AAAA,EACxB;AAAA,EAAW;AAAA,EAAO;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAS;AAAA,EAAe;AAAA,EAAY;AAAA,EACvE;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAY;AAAA,EAAc;AAAA,EACnD;AAAA,EAAS;AAAA,EAAY;AAAA,EAAY;AAAA,EACjC;AAAA,EAAW;AAAA,EAAW;AAAA,EAAoB;AAAA,EAAoB;AAAA,EAC9D;AAAA,EAAa;AAAA,EAAa;AAAA,EAAW;AAAA,EACrC;AAAA,EAAiB;AAAA,EAAiB;AAAA,EAClC;AAAA,EAAS;AAAA,EAAS;AAAA,EAAS;AAC7B,CAAC;AAED,IAAM,UAAkC;AAAA,EACtC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,MAAM;AAAA,EACN,aAAa;AAAA,EACb,KAAK;AACP;AAEA,SAAS,OAAO,OAAwB;AACtC,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO;AACjC,MAAI,OAAO,UAAU,KAAK,EAAG,QAAO;AACpC,SAAO,OAAO;AAChB;AAEA,SAAS,YAAY,OAAgB,MAAuB;AAC1D,QAAM,SAAS,OAAO,KAAK;AAC3B,MAAI,SAAS,SAAU,QAAO,WAAW,YAAY,WAAW;AAChE,MAAI,SAAS,UAAW,QAAO,WAAW;AAC1C,SAAO,WAAW;AACpB;AAEA,SAAS,UAAU,GAAY,GAAqB;AAClD,SAAO,KAAK,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC;AAC/C;AAEA,SAAS,cAAc,OAAuB;AAC5C,SAAO,MAAM,QAAQ,MAAM,IAAI,EAAE,QAAQ,OAAO,IAAI;AACtD;AAEO,IAAM,kBAAN,MAAsB;AAAA,EACV;AAAA,EAEjB,YAAY,MAAc;AACxB,SAAK,OAAO;AACZ,SAAK,gBAAgB,MAAM,GAAG;AAAA,EAChC;AAAA,EAEQ,gBAAgB,QAAiB,IAAkB;AACzD,QAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,EAAG;AAC5E,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAI,CAAC,UAAU,IAAI,GAAG,GAAG;AACvB,cAAM,IAAI,MAAM,oCAAoC,GAAG,QAAQ,EAAE,GAAG;AAAA,MACtE;AACA,UAAI,QAAQ,gBAAgB,QAAQ,SAAS;AAC3C,mBAAW,CAAC,MAAM,GAAG,KAAK,OAAO,QAAQ,KAAe,EAAG,MAAK,gBAAgB,KAAK,GAAG,EAAE,IAAI,GAAG,IAAI,IAAI,EAAE;AAAA,MAC7G,WAAW,QAAQ,WAAW,QAAQ,WAAW,QAAQ,SAAS;AAChE,QAAC,MAAoB,QAAQ,CAAC,KAAK,MAAM,KAAK,gBAAgB,KAAK,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;AAAA,MACzF,WAAW,QAAQ,WAAW,QAAQ,SAAS,QAAQ,mBAAmB,QAAQ,wBAAwB;AACxG,aAAK,gBAAgB,OAAO,GAAG,EAAE,IAAI,GAAG,EAAE;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,QAAQ,KAAqB;AACnC,QAAI,CAAC,IAAI,WAAW,IAAI,EAAG,OAAM,IAAI,MAAM,sCAAsC,GAAG,IAAI;AACxF,QAAI,OAAgB,KAAK;AACzB,eAAW,SAAS,IAAI,MAAM,CAAC,EAAE,MAAM,GAAG,GAAG;AAC3C,aAAQ,OAAmC,MAAM,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,CAAC;AACxF,UAAI,SAAS,OAAW,OAAM,IAAI,MAAM,oBAAoB,GAAG,IAAI;AAAA,IACrE;AACA,WAAO;AAAA,EACT;AAAA,EAEA,SAAS,UAAmB,SAAiB,KAAK,MAAM,OAAO,IAAmB;AAChF,UAAM,SAAwB,CAAC;AAC/B,SAAK,MAAM,UAAU,QAAQ,MAAM,MAAM;AACzC,WAAO;AAAA,EACT;AAAA,EAEQ,MAAM,OAAgB,QAAgB,MAAc,QAA6B;AACvF,QAAI,OAAO,OAAO,SAAS,UAAU;AACnC,WAAK,MAAM,OAAO,KAAK,QAAQ,OAAO,IAAI,GAAG,MAAM,MAAM;AACzD;AAAA,IACF;AAEA,QAAI,OAAO,SAAS,QAAW;AAC7B,YAAM,QAAQ,MAAM,QAAQ,OAAO,IAAI,IAAI,OAAO,OAAmB,CAAC,OAAO,IAAc;AAC3F,UAAI,CAAC,MAAM,KAAK,CAAC,MAAM,YAAY,OAAO,CAAC,CAAC,GAAG;AAC7C,eAAO,KAAK,EAAE,MAAM,SAAS,QAAQ,SAAS,YAAY,MAAM,KAAK,MAAM,CAAC,YAAY,OAAO,KAAK,CAAC,GAAG,CAAC;AACzG;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,UAAU,UAAa,CAAC,UAAU,OAAO,OAAO,KAAK,GAAG;AACjE,aAAO,KAAK,EAAE,MAAM,SAAS,SAAS,SAAS,WAAW,KAAK,UAAU,OAAO,KAAK,CAAC,GAAG,CAAC;AAAA,IAC5F;AAEA,QAAI,MAAM,QAAQ,OAAO,IAAI,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,WAAW,UAAU,QAAQ,KAAK,CAAC,GAAG;AACzF,aAAO,KAAK,EAAE,MAAM,SAAS,QAAQ,SAAS,kBAAkB,OAAO,KAAK,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,CAAC;AAAA,IAC1H;AAEA,QAAI,OAAO,UAAU,SAAU,MAAK,YAAY,OAAO,QAAQ,MAAM,MAAM;AAC3E,QAAI,OAAO,UAAU,SAAU,MAAK,YAAY,OAAO,QAAQ,MAAM,MAAM;AAC3E,QAAI,MAAM,QAAQ,KAAK,EAAG,MAAK,WAAW,OAAO,QAAQ,MAAM,MAAM;AAAA,aAC5D,OAAO,UAAU,YAAY,UAAU,KAAM,MAAK,YAAY,OAAkC,QAAQ,MAAM,MAAM;AAE7H,SAAK,iBAAiB,OAAO,QAAQ,MAAM,MAAM;AAAA,EACnD;AAAA,EAEQ,YAAY,OAAe,QAAgB,MAAc,QAA6B;AAC5F,QAAI,OAAO,OAAO,cAAc,YAAY,MAAM,SAAS,OAAO,WAAW;AAC3E,aAAO,KAAK,EAAE,MAAM,SAAS,aAAa,SAAS,oBAAoB,OAAO,SAAS,cAAc,CAAC;AAAA,IACxG;AACA,QAAI,OAAO,OAAO,cAAc,YAAY,MAAM,SAAS,OAAO,WAAW;AAC3E,aAAO,KAAK,EAAE,MAAM,SAAS,aAAa,SAAS,mBAAmB,OAAO,SAAS,cAAc,CAAC;AAAA,IACvG;AACA,QAAI,OAAO,OAAO,YAAY,YAAY,CAAC,IAAI,OAAO,OAAO,SAAS,GAAG,EAAE,KAAK,KAAK,GAAG;AACtF,aAAO,KAAK,EAAE,MAAM,SAAS,WAAW,SAAS,cAAc,OAAO,OAAO,GAAG,CAAC;AAAA,IACnF;AACA,QAAI,OAAO,OAAO,WAAW,UAAU;AACrC,YAAM,OAAO,QAAQ,OAAO,MAAM;AAClC,UAAI,QAAQ,CAAC,KAAK,KAAK,KAAK,GAAG;AAC7B,eAAO,KAAK,EAAE,MAAM,SAAS,UAAU,SAAS,mBAAmB,OAAO,MAAM,GAAG,CAAC;AAAA,MACtF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,YAAY,OAAe,QAAgB,MAAc,QAA6B;AAC5F,QAAI,OAAO,OAAO,YAAY,YAAY,QAAQ,OAAO,SAAS;AAChE,aAAO,KAAK,EAAE,MAAM,SAAS,WAAW,SAAS,cAAc,OAAO,OAAO,GAAG,CAAC;AAAA,IACnF;AACA,QAAI,OAAO,OAAO,YAAY,YAAY,QAAQ,OAAO,SAAS;AAChE,aAAO,KAAK,EAAE,MAAM,SAAS,WAAW,SAAS,cAAc,OAAO,OAAO,GAAG,CAAC;AAAA,IACnF;AACA,QAAI,OAAO,OAAO,qBAAqB,YAAY,SAAS,OAAO,kBAAkB;AACnF,aAAO,KAAK,EAAE,MAAM,SAAS,oBAAoB,SAAS,aAAa,OAAO,gBAAgB,GAAG,CAAC;AAAA,IACpG;AACA,QAAI,OAAO,OAAO,qBAAqB,YAAY,SAAS,OAAO,kBAAkB;AACnF,aAAO,KAAK,EAAE,MAAM,SAAS,oBAAoB,SAAS,aAAa,OAAO,gBAAgB,GAAG,CAAC;AAAA,IACpG;AACA,QAAI,OAAO,OAAO,eAAe,YAAY,QAAQ,OAAO,eAAe,GAAG;AAC5E,aAAO,KAAK,EAAE,MAAM,SAAS,cAAc,SAAS,yBAAyB,OAAO,UAAU,GAAG,CAAC;AAAA,IACpG;AAAA,EACF;AAAA,EAEQ,WAAW,OAAkB,QAAgB,MAAc,QAA6B;AAC9F,QAAI,OAAO,OAAO,aAAa,YAAY,MAAM,SAAS,OAAO,UAAU;AACzE,aAAO,KAAK,EAAE,MAAM,SAAS,YAAY,SAAS,yBAAyB,OAAO,QAAQ,SAAS,CAAC;AAAA,IACtG;AACA,QAAI,OAAO,OAAO,aAAa,YAAY,MAAM,SAAS,OAAO,UAAU;AACzE,aAAO,KAAK,EAAE,MAAM,SAAS,YAAY,SAAS,wBAAwB,OAAO,QAAQ,SAAS,CAAC;AAAA,IACrG;AACA,QAAI,OAAO,gBAAgB,MAAM;AAC/B,YAAM,OAAO,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,KAAK,UAAU,IAAI,CAAC,CAAC;AAC9D,UAAI,KAAK,SAAS,MAAM,OAAQ,QAAO,KAAK,EAAE,MAAM,SAAS,eAAe,SAAS,8BAA8B,CAAC;AAAA,IACtH;AACA,QAAI,OAAO,OAAO;AAChB,YAAM,QAAQ,CAAC,MAAM,MAAM,KAAK,MAAM,MAAM,OAAO,OAAiB,GAAG,IAAI,IAAI,CAAC,IAAI,MAAM,CAAC;AAAA,IAC7F;AAAA,EACF;AAAA,EAEQ,YAAY,OAAgC,QAAgB,MAAc,QAA6B;AAC7G,UAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,QAAI,MAAM,QAAQ,OAAO,QAAQ,GAAG;AAClC,iBAAW,OAAO,OAAO,UAAsB;AAC7C,YAAI,EAAE,OAAO,OAAQ,QAAO,KAAK,EAAE,MAAM,GAAG,IAAI,IAAI,cAAc,GAAG,CAAC,IAAI,SAAS,YAAY,SAAS,cAAc,CAAC;AAAA,MACzH;AAAA,IACF;AACA,QAAI,OAAO,OAAO,kBAAkB,YAAY,KAAK,SAAS,OAAO,eAAe;AAClF,aAAO,KAAK,EAAE,MAAM,SAAS,iBAAiB,SAAS,qBAAqB,OAAO,aAAa,cAAc,CAAC;AAAA,IACjH;AACA,QAAI,OAAO,OAAO,kBAAkB,YAAY,KAAK,SAAS,OAAO,eAAe;AAClF,aAAO,KAAK,EAAE,MAAM,SAAS,iBAAiB,SAAS,sBAAsB,OAAO,aAAa,cAAc,CAAC;AAAA,IAClH;AACA,UAAM,aAAc,OAAO,cAAc,CAAC;AAC1C,eAAW,OAAO,MAAM;AACtB,YAAM,YAAY,GAAG,IAAI,IAAI,cAAc,GAAG,CAAC;AAC/C,UAAI,OAAO,cAAe,MAAK,MAAM,KAAK,OAAO,eAAyB,WAAW,MAAM;AAC3F,YAAM,QAAQ,WAAW,GAAG;AAC5B,UAAI,OAAO;AACT,aAAK,MAAM,MAAM,GAAG,GAAG,OAAO,WAAW,MAAM;AAAA,MACjD,WAAW,OAAO,yBAAyB,OAAO;AAChD,eAAO,KAAK,EAAE,MAAM,WAAW,SAAS,wBAAwB,SAAS,4BAA4B,CAAC;AAAA,MACxG,WAAW,OAAO,OAAO,yBAAyB,YAAY,OAAO,yBAAyB,MAAM;AAClG,aAAK,MAAM,MAAM,GAAG,GAAG,OAAO,sBAAgC,WAAW,MAAM;AAAA,MACjF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,iBAAiB,OAAgB,QAAgB,MAAc,QAA6B;AAClG,QAAI,MAAM,QAAQ,OAAO,KAAK,GAAG;AAC/B,iBAAW,OAAO,OAAO,MAAmB,MAAK,MAAM,OAAO,KAAK,MAAM,MAAM;AAAA,IACjF;AACA,QAAI,MAAM,QAAQ,OAAO,KAAK,GAAG;AAC/B,YAAM,WAAY,OAAO,MAAmB,IAAI,CAAC,QAAQ,KAAK,SAAS,OAAO,KAAK,IAAI,CAAC;AACxF,UAAI,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG;AACvC,eAAO,KAAK,EAAE,MAAM,SAAS,SAAS,SAAS,oCAAoC,SAAS,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC,GAAG,CAAC;AAAA,MACzI;AAAA,IACF;AACA,QAAI,MAAM,QAAQ,OAAO,KAAK,GAAG;AAC/B,YAAMC,WAAW,OAAO,MAAmB,OAAO,CAAC,QAAQ,KAAK,SAAS,OAAO,KAAK,IAAI,EAAE,WAAW,CAAC;AACvG,UAAIA,SAAQ,WAAW,GAAG;AACxB,eAAO,KAAK,EAAE,MAAM,SAAS,SAAS,SAAS,iDAAiDA,SAAQ,MAAM,IAAI,CAAC;AAAA,MACrH;AAAA,IACF;AACA,QAAI,OAAO,OAAO,KAAK,SAAS,OAAO,OAAO,KAAe,IAAI,EAAE,WAAW,GAAG;AAC/E,aAAO,KAAK,EAAE,MAAM,SAAS,OAAO,SAAS,4BAA4B,CAAC;AAAA,IAC5E;AAAA,EACF;AACF;;;AC9NO,IAAM,uBAAuB;;;ACK7B,IAAM,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/B,QAAQ;AAAA,EACR,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,eAAe;AAAA,EACf,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,YAAY;AAAA,EACZ,cAAc;AAChB;AAEA,SAAS,UAAU,OAAuB;AACxC,QAAM,QAAQ,yBAAyB,KAAK,KAAK;AACjD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,OAAO,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,OAAO,QAAQ,IAAI,QAAQ;AACrE;AAEA,SAAS,UAAU,OAAuB;AACxC,SAAO,KAAK,MAAM,UAAU,KAAK,IAAI,QAAQ,CAAC;AAChD;AAEA,SAAS,cAAc,UAAiC;AACtD,MAAI,SAAS,QAAQ,SAAU,QAAO,SAAS,QAAQ;AACvD,SAAO,SAAS,QAAQ,WAAW,SAAS,KAAK,IAAI,WAAW;AAClE;AAEA,SAAS,kBAAkB,UAA4C;AACrE,QAAM,YAAiC,SAAS,OAAO,YAAY,CAAC,GAAG,IAAI,CAAC,YAAY;AACtF,UAAM,OAAO,QAAQ,OAAO,SAAS,QAAQ,QAAQ,UAAU;AAC/D,UAAM,QAAS,QAAQ,QAAQ,QAAQ,SAAS,QAAQ;AACxD,WAAO,EAAE,MAAM,OAAO,SAAS,SAAS,MAAM,YAAY,IAAI,OAAO,MAAM,QAAQ,QAAQ,OAAO;AAAA,EACpG,CAAC;AAED,QAAM,QAAQ,SAAS,SAAS,MAAM,YAAY;AAClD,MAAI,CAAC,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,UAAU,KAAK,GAAG;AACjE,aAAS,QAAQ,EAAE,MAAM,QAAQ,OAAO,OAAO,MAAM,QAAQ,CAAC;AAAA,EAChE;AACA,SAAO;AACT;AAEO,SAAS,kBAAkB,UAA2C;AAC3E,QAAM,mBAAmB,SAAS,QAAQ,aAAa,CAAC;AACxD,QAAM,WAAW,SAAS,WAAW,YAAY;AACjD,QAAM,SAAS,SAAS,SAAS,UAAU,CAAC;AAE5C,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,UAAU;AAAA,MACR,MAAM,SAAS,SAAS;AAAA,MACxB,aAAa,SAAS,SAAS,eAAe,SAAS,SAAS;AAAA,MAChE,aAAa,SAAS,SAAS,eAAe;AAAA,MAC9C,OAAO,SAAS,SAAS,MAAM,YAAY;AAAA,MAC3C,QAAQ,SAAS,SAAS,UAAU,CAAC;AAAA,IACvC;AAAA,IACA,SAAS;AAAA,MACP,MAAM,SAAS,QAAQ;AAAA,MACvB,UAAU,cAAc,QAAQ;AAAA,MAChC,YAAY,SAAS,QAAQ;AAAA,MAC7B,mBAAmB,SAAS,QAAQ,qBAAqB;AAAA,MACzD,WAAW;AAAA,QACT,WAAW,UAAU,iBAAiB,UAAU,kBAAkB,MAAM;AAAA,QACxE,WAAW,iBAAiB,aAAa,kBAAkB;AAAA,QAC3D,kBAAkB,cAAc,iBAAiB,kBAAkB,kBAAkB,cAAc;AAAA,QACnG,aAAa,iBAAiB,eAAe,kBAAkB;AAAA,MACjE;AAAA,IACF;AAAA,IACA,OAAO;AAAA,MACL,SAAS,SAAS,OAAO,WAAW;AAAA,MACpC,QAAQ,SAAS,OAAO,UAAU;AAAA,MAClC,gBAAgB,SAAS,OAAO,kBAAkB,kBAAkB;AAAA;AAAA,MAEpE,kBAAkB,SAAS,OAAO,oBAAoB;AAAA,IACxD;AAAA,IACA,QAAQ;AAAA,MACN,YAAY,SAAS,OAAO;AAAA,MAC5B,cAAc,SAAS,OAAO,gBAAgB;AAAA,MAC9C,UAAU,kBAAkB,QAAQ;AAAA,IACtC;AAAA,IACA,WAAW;AAAA,MACT,UAAU,WACN;AAAA,QACE,QAAQ,SAAS;AAAA,QACjB,MAAM,SAAS,QAAQ,kBAAkB;AAAA,QACzC,YAAY,SAAS,cAAc,kBAAkB;AAAA,QACrD,aAAa,cAAc,SAAS,aAAa,kBAAkB,SAAS;AAAA,MAC9E,IACA;AAAA,MACJ,UAAU,SAAS,WAAW,WAAW,CAAC,GAAG,IAAI,CAAC,WAAW;AAAA,QAC3D,MAAM,MAAM;AAAA,QACZ,OAAO,MAAM,SAAS,kBAAkB;AAAA,QACxC,cAAc,UAAU,MAAM,WAAW,kBAAkB,cAAc;AAAA,QACzE,aAAa,cAAc,MAAM,aAAa,kBAAkB,SAAS;AAAA,MAC3E,EAAE;AAAA,IACJ;AAAA,IACA,eAAe,SAAS,gBAAgB,CAAC,GAAG,IAAI,CAAC,gBAAgB;AAAA,MAC/D,MAAM,WAAW;AAAA,MACjB,YAAY,WAAW;AAAA;AAAA;AAAA,MAGvB,OAAO,WAAW,MACf,IAAI,CAAC,cAAc,UAAU,WAAW,GAAG,WAAW,IAAI,GAAG,IAC1D,YACA,GAAG,WAAW,IAAI,IAAI,SAAS,EAAE,EACpC,KAAK;AAAA,MACR,aAAa,WAAW,eAAe,CAAC;AAAA,IAC1C,EAAE;AAAA,IACF,SAAS;AAAA,MACP,QAAQ;AAAA;AAAA,QAEN,MAAM,OAAO,QAAQ;AAAA,QACrB,QAAQ,OAAO,SAAS,CAAC,GAAG,IAAI,CAAC,UAAU;AAAA,UACzC,MAAM,KAAK,KAAK,YAAY;AAAA,UAC5B,OAAO,KAAK,SAAS,CAAC,GAAG;AAAA,QAC3B,EAAE;AAAA,MACJ;AAAA,IACF;AAAA,IACA,UAAU,SAAS,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY;AAAA,MACjD,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,SAAS,OAAO,WAAW;AAAA,IAC7B,EAAE;AAAA,IACF,WAAW;AAAA,MACT,cAAc,SAAS,UAAU,aAAa,cAAc,SAAS,UAAU,UAAU,IAAI;AAAA,MAC7F,sBAAsB,SAAS,UAAU,qBAAqB,cAAc,SAAS,UAAU,kBAAkB,IAAI;AAAA,MACrH,uBAAuB,SAAS,UAAU,sBAAsB,cAAc,SAAS,UAAU,mBAAmB,IAAI;AAAA,MACxH,cAAc,cAAc,SAAS,UAAU,cAAc,kBAAkB,UAAU;AAAA,IAC3F;AAAA,IACA,eAAe;AAAA,MACb,UAAU,SAAS,eAAe,YAAY;AAAA,MAC9C,aAAa,cAAc,SAAS,eAAe,aAAa,kBAAkB,YAAY;AAAA,MAC9F,QAAQ,EAAE,YAAY,SAAS,eAAe,QAAQ,cAAc,kBAAkB,WAAW;AAAA,MACjG,QAAQ,SAAS,eAAe,UAAU,CAAC;AAAA,IAC7C;AAAA,EACF;AACF;AAMO,SAAS,aAAa,OAAwB;AACnD,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO,KAAK,UAAU,KAAK,KAAK;AACjF,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,IAAI,MAAM,IAAI,YAAY,EAAE,KAAK,GAAG,CAAC;AACtE,QAAM,UAAU,OAAO,QAAQ,KAAgC,EAC5D,OAAO,CAAC,CAAC,EAAE,CAAC,MAAM,MAAM,MAAS,EACjC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE;AAClD,SAAO,IAAI,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,KAAK,UAAU,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC;AACzF;;;AC7JO,IAAM,qBAAqB,oBAAI,IAAI;AAAA,EACxC;AAAA,EAAe;AAAA,EAAiB;AAAA,EAAsB;AAAA,EACtD;AAAA,EAAe;AAAA,EAAmB;AAAA,EAAc;AAAA,EAChD;AAAA,EAAqB;AAAA,EAAqB;AAC5C,CAAC;AAQM,SAAS,kBACd,UACA,UAA2B,CAAC,GACb;AACf,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,SAAwB,CAAC;AAC/B,QAAM,MAAM,CAAC,MAAc,SAAiB,UAAU,eAAe,OAAO,KAAK,EAAE,MAAM,SAAS,QAAQ,CAAC;AAG3G,MAAI,SAAS,QAAQ,SAAS,YAAY,SAAS,QAAQ,aAAa,UAAU;AAChF,QAAI,qBAAqB,wDAAwD;AAAA,EACnF;AACA,MAAI,SAAS,QAAQ,UAAU,YAAY,OAAO,cAAc;AAC9D,QAAI,6BAA6B,qCAAqC,OAAO,YAAY,IAAI;AAAA,EAC/F;AAGA,MAAI,SAAS,OAAO,eAAe,YAAY,CAAC,OAAO,iBAAiB;AACtE,QAAI,sBAAsB,sDAAsD;AAAA,EAClF;AACA,MAAI,SAAS,OAAO,eAAe,YAAY,CAAC,SAAS,OAAO,cAAc;AAC5E,QAAI,wBAAwB,oCAAoC;AAAA,EAClE;AACA,QAAM,eAAe,oBAAI,IAAY;AACrC,WAAS,OAAO,SAAS,QAAQ,CAAC,SAAS,UAAU;AACnD,UAAM,MAAM,GAAG,QAAQ,IAAI,IAAI,QAAQ,KAAK;AAC5C,QAAI,aAAa,IAAI,GAAG,EAAG,KAAI,oBAAoB,KAAK,IAAI,qBAAqB,GAAG,EAAE;AACtF,iBAAa,IAAI,GAAG;AAAA,EACtB,CAAC;AAGD,MAAI,OAAO,cAAc,SAAS,SAAS,SAAS,IAAI,GAAG;AACzD,QAAI,kBAAkB,IAAI,SAAS,SAAS,IAAI,sBAAsB;AAAA,EACxE;AACA,WAAS,QAAQ,QAAQ,CAAC,QAAQ,UAAU;AAC1C,QAAI,mBAAmB,IAAI,OAAO,IAAI,GAAG;AACvC,UAAI,YAAY,KAAK,SAAS,IAAI,OAAO,IAAI,8CAA8C;AAAA,IAC7F;AAAA,EACF,CAAC;AACD,QAAM,cAAc,oBAAI,IAAY;AACpC,WAAS,QAAQ,QAAQ,CAAC,QAAQ,UAAU;AAC1C,QAAI,YAAY,IAAI,OAAO,IAAI,EAAG,KAAI,YAAY,KAAK,SAAS,0BAA0B,OAAO,IAAI,GAAG;AACxG,gBAAY,IAAI,OAAO,IAAI;AAAA,EAC7B,CAAC;AACD,MAAI,SAAS,QAAQ,SAAS,KAAK,CAAC,OAAO,iBAAiB;AAC1D,QAAI,YAAY,yDAAyD;AAAA,EAC3E;AAGA,QAAM,EAAE,cAAc,sBAAsB,uBAAuB,aAAa,IAAI,SAAS;AAC7F,MAAI,iBAAiB,QAAQ,yBAAyB,QAAQ,wBAAwB,cAAc;AAClG,QAAI,iCAAiC,0CAA0C;AAAA,EACjF;AACA,MAAI,0BAA0B,QAAQ,yBAAyB,MAAM;AACnE,QAAI,kCAAkC,iDAAiD;AAAA,EACzF;AACA,MAAI,eAAe,OAAO,qBAAqB,MAAW;AACxD,QAAI,yBAAyB,uCAAuC,OAAO,kBAAkB,GAAG;AAAA,EAClG;AAGA,QAAM,eAAe,oBAAI,IAAY;AACrC,WAAS,UAAU,QAAQ,QAAQ,CAAC,OAAO,UAAU;AACnD,QAAI,aAAa,IAAI,MAAM,IAAI,EAAG,KAAI,sBAAsB,KAAK,SAAS,2BAA2B,MAAM,IAAI,GAAG;AAClH,iBAAa,IAAI,MAAM,IAAI;AAAA,EAC7B,CAAC;AAGD,QAAM,kBAAkB,oBAAI,IAAY;AACxC,WAAS,aAAa,QAAQ,CAAC,YAAY,UAAU;AACnD,QAAI,gBAAgB,IAAI,WAAW,IAAI,GAAG;AACxC,UAAI,iBAAiB,KAAK,SAAS,8BAA8B,WAAW,IAAI,GAAG;AAAA,IACrF;AACA,oBAAgB,IAAI,WAAW,IAAI;AACnC,eAAW,aAAa,WAAW,OAAO;AACxC,UAAI,UAAU,SAAS,IAAI,KAAK,UAAU,SAAS,SAAS,KAAK,UAAU,SAAS,KAAK,GAAG;AAC1F,YAAI,iBAAiB,KAAK,UAAU,IAAI,SAAS,6CAA6C;AAAA,MAChG;AAAA,IACF;AAAA,EACF,CAAC;AAGD,MAAI,SAAS,QAAQ,OAAO,SAAS,UAAU,SAAS,QAAQ,OAAO,MAAM,SAAS,GAAG;AACvF,QAAI,wBAAwB,qDAAqD;AAAA,EACnF;AACA,MAAI,SAAS,QAAQ,OAAO,SAAS,eAAe,SAAS,QAAQ,OAAO,MAAM,WAAW,GAAG;AAC9F,QAAI,yBAAyB,mDAAmD;AAAA,EAClF;AAGA,MAAI,QAAQ,aAAa;AAGvB,QAAI,CAAC,SAAS,MAAM,WAAW,CAAC,QAAQ,YAAY,SAAS,SAAS,QAAQ,UAAU,GAAG;AACzF,UAAI,uBAAuB,IAAI,SAAS,QAAQ,UAAU,wCAAwC;AAAA,IACpG;AACA,UAAM,aAAa,SAAS,UAAU,UAAU;AAChD,QAAI,cAAc,CAAC,QAAQ,YAAY,KAAK,CAAC,SAAS,KAAK,WAAW,GAAG,UAAU,GAAG,CAAC,GAAG;AACxF,UAAI,kCAAkC,mCAAmC,UAAU,IAAI;AAAA,IACzF;AAAA,EACF;AAEA,SAAO;AACT;AAGO,SAAS,iBAAiB,UAAwC;AACvE,QAAM,WAAqB,CAAC;AAC5B,MAAI,SAAS,QAAQ,SAAS,GAAG;AAC/B,aAAS,KAAK,qBAAqB,SAAS,QAAQ,MAAM,6DAA6D;AAAA,EACzH;AACA,MAAI,SAAS,UAAU,yBAAyB,MAAM;AACpD,aAAS,KAAK,sFAAsF;AAAA,EACtG;AACA,MAAI,SAAS,OAAO,eAAe,kBAAkB,SAAS,OAAO,SAAS,UAAU,GAAG;AACzF,aAAS,KAAK,+DAA+D;AAAA,EAC/E;AACA,MAAI,SAAS,MAAM,WAAW,CAAC,SAAS,MAAM,QAAQ;AACpD,aAAS,KAAK,6GAA6G;AAAA,EAC7H;AACA,MAAI,SAAS,MAAM,WAAW,CAAC,SAAS,MAAM,kBAAkB;AAC9D,aAAS,KAAK,qGAAqG;AAAA,EACrH;AACA,MAAI,SAAS,QAAQ,OAAO,SAAS,aAAa;AAChD,aAAS;AAAA,MACP,oBAAoB,SAAS,QAAQ,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IAEvF;AAAA,EACF;AACA,SAAO;AACT;;;ACvIA,SAAS,KAAK,SAAiB,MAAc,cAAc,gDAAuD;AAChH,QAAM,IAAI,UAAU,yBAAyB,GAAG,OAAO,UAAU,IAAI,KAAK;AAAA,IACxE;AAAA,IACA,SAAS,EAAE,KAAK;AAAA,EAClB,CAAC;AACH;AAGA,SAAS,aAAa,MAAsB;AAC1C,MAAI,QAAuB;AAC3B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,KAAK,KAAK,CAAC;AACjB,QAAI,OAAO;AACT,UAAI,OAAO,QAAQ,UAAU,IAAK;AAAA,eACzB,OAAO,MAAO,SAAQ;AAAA,IACjC,WAAW,OAAO,OAAO,OAAO,KAAK;AACnC,cAAQ;AAAA,IACV,WAAW,OAAO,QAAQ,MAAM,KAAK,KAAK,KAAK,KAAK,IAAI,CAAC,CAAE,IAAI;AAC7D,aAAO,KAAK,MAAM,GAAG,CAAC;AAAA,IACxB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,KAAK,QAAwB;AACpC,QAAM,MAAc,CAAC;AACrB,QAAM,MAAM,OAAO,MAAM,OAAO;AAChC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;AACnC,UAAM,WAAW,IAAI,CAAC;AACtB,QAAI,SAAS,SAAS,GAAI,GAAG;AAC3B,WAAK,wCAAwC,IAAI,GAAG,0BAA0B;AAAA,IAChF;AACA,UAAM,OAAO,aAAa,QAAQ,EAAE,QAAQ;AAC5C,QAAI,KAAK,KAAK,MAAM,GAAI;AACxB,QAAI,KAAK,KAAK,MAAM,MAAO;AAC3B,QAAI,KAAK,KAAK,MAAM,MAAO;AAC3B,QAAI,KAAK,EAAE,QAAQ,SAAS,SAAS,SAAS,UAAU,EAAE,QAAQ,MAAM,KAAK,KAAK,GAAG,QAAQ,IAAI,EAAE,CAAC;AAAA,EACtG;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,OAAe,MAAyB;AAC/D,QAAM,OAAO,MAAM,KAAK;AACxB,MAAI,SAAS,GAAI,QAAO;AACxB,MAAI,KAAK,WAAW,GAAG,GAAG;AACxB,QAAI,CAAC,KAAK,SAAS,GAAG,KAAK,KAAK,SAAS,EAAG,MAAK,qCAAqC,IAAI;AAC1F,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB;AACA,MAAI,KAAK,WAAW,GAAG,GAAG;AACxB,QAAI,CAAC,KAAK,SAAS,GAAG,KAAK,KAAK,SAAS,EAAG,MAAK,qCAAqC,IAAI;AAC1F,WAAO,KAAK,MAAM,GAAG,EAAE,EAAE,WAAW,MAAM,GAAG;AAAA,EAC/C;AACA,MAAI,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,EAAG,QAAO,oBAAoB,MAAM,IAAI;AACvF,MAAI,SAAS,UAAU,SAAS,IAAK,QAAO;AAC5C,MAAI,SAAS,OAAQ,QAAO;AAC5B,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,mDAAmD,KAAK,IAAI,EAAG,QAAO,OAAO,IAAI;AACrF,MAAI,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,KAAK,KAAK,WAAW,GAAG,GAAG;AACxE,SAAK,6DAA6D,MAAM,yBAAyB;AAAA,EACnG;AACA,SAAO;AACT;AAGA,SAAS,UAAU,MAAc,MAAwB;AACvD,QAAM,QAAkB,CAAC;AACzB,MAAI,QAAQ;AACZ,MAAI,QAAuB;AAC3B,MAAI,UAAU;AACd,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,KAAK,KAAK,CAAC;AACjB,QAAI,OAAO;AACT,iBAAW;AACX,UAAI,OAAO,QAAQ,UAAU,KAAK;AAAE,mBAAW,KAAK,EAAE,CAAC,KAAK;AAAI;AAAA,MAAU;AAC1E,UAAI,OAAO,MAAO,SAAQ;AAC1B;AAAA,IACF;AACA,QAAI,OAAO,OAAO,OAAO,KAAK;AAAE,cAAQ;AAAI,iBAAW;AAAI;AAAA,IAAU;AACrE,QAAI,OAAO,OAAO,OAAO,IAAK;AAC9B,QAAI,OAAO,OAAO,OAAO,IAAK;AAC9B,QAAI,OAAO,OAAO,UAAU,GAAG;AAAE,YAAM,KAAK,OAAO;AAAG,gBAAU;AAAI;AAAA,IAAU;AAC9E,eAAW;AAAA,EACb;AACA,MAAI,MAAO,MAAK,0CAA0C,IAAI;AAC9D,MAAI,UAAU,EAAG,MAAK,0CAA0C,IAAI;AACpE,MAAI,QAAQ,KAAK,MAAM,GAAI,OAAM,KAAK,OAAO;AAC7C,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAc,MAAyB;AAClE,MAAI,KAAK,WAAW,GAAG,GAAG;AACxB,QAAI,CAAC,KAAK,SAAS,GAAG,EAAG,MAAK,8BAA8B,IAAI;AAChE,WAAO,UAAU,KAAK,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,IAAI,CAAC,SAAS,gBAAgB,MAAM,IAAI,CAAC;AAAA,EACrF;AACA,MAAI,CAAC,KAAK,SAAS,GAAG,EAAG,MAAK,6BAA6B,IAAI;AAC/D,QAAM,MAAiC,CAAC;AACxC,aAAW,QAAQ,UAAU,KAAK,MAAM,GAAG,EAAE,GAAG,IAAI,GAAG;AACrD,UAAM,MAAM,KAAK,QAAQ,GAAG;AAC5B,QAAI,QAAQ,GAAI,MAAK,qCAAqC,IAAI;AAC9D,UAAM,MAAM,OAAO,gBAAgB,KAAK,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC;AAC5D,QAAI,OAAO,IAAK,MAAK,kBAAkB,GAAG,KAAK,IAAI;AACnD,QAAI,GAAG,IAAI,gBAAgB,KAAK,MAAM,MAAM,CAAC,GAAG,IAAI;AAAA,EACtD;AACA,SAAO;AACT;AAGA,SAAS,aAAa,MAAsB;AAC1C,MAAI,QAAuB;AAC3B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,KAAK,KAAK,CAAC;AACjB,QAAI,OAAO;AACT,UAAI,OAAO,QAAQ,UAAU,IAAK;AAAA,eACzB,OAAO,MAAO,SAAQ;AAC/B;AAAA,IACF;AACA,QAAI,OAAO,OAAO,OAAO,KAAK;AAAE,cAAQ;AAAI;AAAA,IAAU;AACtD,QAAI,OAAO,OAAO,OAAO,IAAK,QAAO;AACrC,QAAI,OAAO,QAAQ,MAAM,KAAK,SAAS,KAAK,KAAK,IAAI,CAAC,MAAM,KAAM,QAAO;AAAA,EAC3E;AACA,SAAO;AACT;AAEA,IAAM,SAAN,MAAa;AAAA,EACH,QAAQ;AAAA,EACC;AAAA,EAEjB,YAAY,OAAe;AACzB,SAAK,QAAQ;AAAA,EACf;AAAA,EAEQ,OAAyB;AAC/B,WAAO,KAAK,MAAM,KAAK,KAAK;AAAA,EAC9B;AAAA,EAEA,WAAW,QAA2B;AACpC,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,CAAC,QAAQ,KAAK,SAAS,OAAQ,QAAO;AAC1C,QAAI,KAAK,KAAK,WAAW,IAAI,KAAK,KAAK,SAAS,IAAK,QAAO,KAAK,cAAc,KAAK,MAAM;AAC1F,WAAO,KAAK,aAAa,KAAK,MAAM;AAAA,EACtC;AAAA,EAEQ,cAAc,QAA6B;AACjD,UAAM,QAAqB,CAAC;AAC5B,eAAS;AACP,YAAM,OAAO,KAAK,KAAK;AACvB,UAAI,CAAC,QAAQ,KAAK,WAAW,UAAU,EAAE,KAAK,SAAS,OAAO,KAAK,KAAK,WAAW,IAAI,GAAI;AAC3F,YAAM,OAAO,KAAK,SAAS,MAAM,KAAK,KAAK,KAAK,MAAM,CAAC,EAAE,KAAK;AAC9D,WAAK;AACL,UAAI,SAAS,IAAI;AACf,cAAM,KAAK,KAAK,WAAW,SAAS,CAAC,CAAC;AACtC;AAAA,MACF;AACA,YAAMC,OAAM,aAAa,IAAI;AAC7B,UAAIA,SAAQ,IAAI;AAEd,cAAM,KAAK,KAAK,mBAAmB,MAAM,MAAMA,MAAK,MAAM,CAAC;AAAA,MAC7D,OAAO;AACL,cAAM,KAAK,KAAK,mBAAmB,MAAM,IAAI,CAAC;AAAA,MAChD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,mBAAmB,MAAY,MAAcA,MAAa,QAA2B;AAC3F,UAAM,MAAiC,CAAC;AACxC,UAAM,MAAM,OAAO,gBAAgB,KAAK,MAAM,GAAGA,IAAG,GAAG,KAAK,MAAM,CAAC;AACnE,UAAM,cAAc,KAAK,MAAMA,OAAM,CAAC,EAAE,KAAK;AAC7C,UAAM,cAAc,SAAS;AAC7B,QAAI,gBAAgB,IAAI;AACtB,UAAI,GAAG,IAAI,KAAK,WAAW,WAAW,IAAI,KAAK,WAAW,WAAW,IAAI;AAAA,IAC3E,OAAO;AACL,UAAI,GAAG,IAAI,KAAK,mBAAmB,aAAa,IAAI;AAAA,IACtD;AACA,UAAM,OAAO,KAAK,oBAAoB,WAAW;AACjD,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,IAAI,GAAG;AACzC,UAAI,KAAK,IAAK,MAAK,kBAAkB,CAAC,KAAK,KAAK,MAAM;AACtD,UAAI,CAAC,IAAI;AAAA,IACX;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,WAAW,QAAyB;AAC1C,UAAM,OAAO,KAAK,KAAK;AACvB,WAAO,SAAS,UAAa,KAAK,UAAU;AAAA,EAC9C;AAAA,EAEQ,aAAa,QAA2C;AAC9D,WAAO,KAAK,oBAAoB,MAAM;AAAA,EACxC;AAAA,EAEQ,oBAAoB,QAA2C;AACrE,UAAM,MAAiC,CAAC;AACxC,eAAS;AACP,YAAM,OAAO,KAAK,KAAK;AACvB,UAAI,CAAC,QAAQ,KAAK,SAAS,OAAQ;AACnC,UAAI,KAAK,SAAS,OAAQ,MAAK,0BAA0B,KAAK,QAAQ,oDAAoD;AAC1H,UAAI,KAAK,KAAK,WAAW,IAAI,EAAG;AAChC,YAAMA,OAAM,aAAa,KAAK,IAAI;AAClC,UAAIA,SAAQ,GAAI,MAAK,oCAAoC,KAAK,IAAI,KAAK,KAAK,MAAM;AAClF,YAAM,MAAM,OAAO,gBAAgB,KAAK,KAAK,MAAM,GAAGA,IAAG,GAAG,KAAK,MAAM,CAAC;AACxE,UAAI,OAAO,IAAK,MAAK,kBAAkB,GAAG,KAAK,KAAK,QAAQ,0BAA0B;AACtF,YAAM,SAAS,KAAK,KAAK,MAAMA,OAAM,CAAC,EAAE,KAAK;AAC7C,WAAK;AACL,UAAI,WAAW,IAAI;AACjB,cAAM,OAAO,KAAK,KAAK;AACvB,YAAI,GAAG,IAAI,QAAQ,KAAK,SAAS,SAAS,KAAK,WAAW,KAAK,MAAM,IACjE,QAAQ,KAAK,WAAW,UAAU,KAAK,KAAK,WAAW,IAAI,IAAI,KAAK,cAAc,MAAM,IACxF;AAAA,MACN,OAAO;AACL,YAAI,GAAG,IAAI,KAAK,mBAAmB,QAAQ,IAAI;AAAA,MACjD;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,mBAAmB,MAAc,MAAuB;AAC9D,QAAI,SAAS,OAAO,SAAS,OAAO,SAAS,QAAQ,SAAS,MAAM;AAClE,aAAO,gBAAgB,MAAM,KAAK,MAAM;AAAA,IAC1C;AACA,UAAM,SAAS,KAAK,WAAW,GAAG;AAClC,UAAM,QAAQ,KAAK,SAAS,GAAG;AAC/B,UAAM,QAAkB,CAAC;AACzB,UAAM,aAAa,KAAK,KAAK,GAAG,UAAU;AAC1C,WAAO,KAAK,KAAK,KAAK,KAAK,KAAK,EAAG,UAAU,cAAc,aAAa,KAAK,QAAQ;AACnF,YAAM,KAAK,KAAK,MAAM,KAAK,KAAK,EAAG,IAAI;AACvC,WAAK;AAAA,IACP;AACA,UAAM,OAAO,SAAS,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,IAAI;AACvD,WAAO,QAAQ,OAAO,QAAQ,SAAS,KAAK;AAAA,EAC9C;AAAA,EAEA,QAAiB;AACf,WAAO,KAAK,SAAS,KAAK,MAAM;AAAA,EAClC;AAAA,EAEA,cAAsB;AACpB,WAAO,KAAK,KAAK,GAAG,UAAU;AAAA,EAChC;AACF;AAEO,SAAS,UAAU,QAA2B;AACnD,QAAM,QAAQ,KAAK,MAAM;AACzB,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,UAAU,yBAAyB,0BAA0B;AAAA,MACrE,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AACA,QAAM,SAAS,IAAI,OAAO,KAAK;AAC/B,QAAM,QAAQ,OAAO,WAAW,MAAM,CAAC,EAAG,MAAM;AAChD,MAAI,CAAC,OAAO,MAAM,EAAG,MAAK,uCAAuC,OAAO,YAAY,CAAC;AACrF,SAAO;AACT;;;ALxPA,IAAM,cAAc,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC,GAAG,MAAM,UAAU,oBAAoB;AAE/F,IAAM,iBAAyB,KAAK,MAAM,aAAa,aAAa,MAAM,CAAC;AAElF,IAAI,YAAoC;AACxC,SAAS,kBAAmC;AAC1C,gBAAc,IAAI,gBAAgB,cAAc;AAChD,SAAO;AACT;AAYA,SAAS,aAAa,QAA+B;AACnD,SAAO,OACJ,MAAM,GAAG,EAAE,EACX,IAAI,CAAC,UAAU,GAAG,MAAM,SAAS,KAAK,WAAW,MAAM,IAAI,IAAI,MAAM,OAAO,EAAE,EAC9E,KAAK,IAAI;AACd;AAEO,SAAS,cAAc,QAAgB,UAA2B,CAAC,GAAmB;AAC3F,QAAM,WAAW,UAAU,MAAM;AACjC,MAAI,OAAO,aAAa,YAAY,aAAa,QAAQ,MAAM,QAAQ,QAAQ,GAAG;AAChF,UAAM,IAAI,UAAU,yBAAyB,sDAAsD;AAAA,MACjG,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAEA,QAAM,aAAc,SAAqC;AACzD,MAAI,eAAe,sBAAsB;AACvC,UAAM,IAAI,UAAU,oCAAoC,0BAA0B,KAAK,UAAU,UAAU,CAAC,KAAK;AAAA,MAC/G,aAAa,sBAAsB,oBAAoB;AAAA,MACvD,SAAS,EAAE,WAAW,CAAC,oBAAoB,GAAG,OAAO,cAAc,KAAK;AAAA,IAC1E,CAAC;AAAA,EACH;AAEA,QAAM,eAAe,gBAAgB,EAAE,SAAS,QAAQ;AACxD,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,IAAI,UAAU,2BAA2B,uCAAuC,aAAa,YAAY,CAAC,IAAI;AAAA,MAClH,SAAS,EAAE,QAAQ,aAAa;AAAA,IAClC,CAAC;AAAA,EACH;AAEA,QAAM,WAAW;AACjB,QAAM,aAAa,kBAAkB,QAAQ;AAE7C,QAAM,iBAAiB,kBAAkB,YAAY,OAAO;AAC5D,MAAI,eAAe,SAAS,GAAG;AAC7B,UAAM,IAAI,UAAU,6BAA6B,sDAAsD,aAAa,cAAc,CAAC,IAAI;AAAA,MACrI,aAAa;AAAA,MACb,SAAS,EAAE,QAAQ,eAAe;AAAA,IACpC,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,eAAe,UAAU;AAAA,IACjC,UAAU,iBAAiB,UAAU;AAAA,EACvC;AACF;AAEO,SAAS,eAAe,YAAwC;AACrE,SAAO,WAAW,QAAQ,EAAE,OAAO,aAAa,UAAU,CAAC,EAAE,OAAO,KAAK;AAC3E;AAGO,SAAS,gBAAgB,MAAc,OAAuB;AACnE,SAAO,eAAe,oBAAoB;AAAA;AAAA;AAAA;AAAA,UAIlC,IAAI;AAAA,WACH,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAehB;;;AM/GA,SAAS,cAAAC,aAAY,kBAAkB;AACvC,SAAS,QAAQ,YAAY,WAAW,gBAAAC,eAAc,aAAa,YAAY,QAAQ,UAAU,qBAAqB;AACtH,SAAS,WAAAC,UAAS,QAAAC,OAAM,WAAW,UAAU,WAAW;AAGjD,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EAAQ;AAAA,EAAgB;AAAA,EAAS;AAAA,EAAW;AAAA,EAAe;AAAA,EAC3D;AAAA,EAAQ;AAAA,EAAc;AAAA,EAAmB;AAAA,EAAS;AAAA,EAAS;AAAA,EAC3D;AAAA,EAAS;AAAA,EAAe;AAAA,EAAiB;AAAA,EAAU;AACrD;AAGA,IAAM,iBAAiB;AAgBhB,IAAM,iBAAiC;AAAA,EAC5C,UAAU;AAAA,EACV,eAAe,KAAK,OAAO;AAAA,EAC3B,cAAc,KAAK,OAAO;AAC5B;AAEA,SAAS,YAAY,MAAwB;AAC3C,QAAM,WAAW,CAAC,GAAG,eAAe;AACpC,QAAM,OAAOC,MAAK,MAAM,aAAa;AACrC,MAAI,WAAW,IAAI,GAAG;AACpB,eAAW,QAAQC,cAAa,MAAM,MAAM,EAAE,MAAM,IAAI,GAAG;AACzD,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,WAAW,CAAC,QAAQ,WAAW,GAAG,EAAG,UAAS,KAAK,OAAO;AAAA,IAChE;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,QAAQ,SAAiB,MAAuB;AACvD,MAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,UAAM,OAAO,IAAI,OAAO,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,YAAY,EAAE,KAAK,OAAO,CAAC,GAAG;AACjF,WAAO,KAAK,MAAM,GAAG,EAAE,KAAK,CAAC,YAAY,KAAK,KAAK,OAAO,CAAC,KAAK,KAAK,KAAK,IAAI;AAAA,EAChF;AACA,SAAO,SAAS,WAAW,KAAK,WAAW,GAAG,OAAO,GAAG,KAAK,KAAK,MAAM,GAAG,EAAE,SAAS,OAAO;AAC/F;AAEA,SAAS,aAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAGO,SAAS,WAAW,MAAc,SAAyB,gBAA8B;AAC9F,QAAM,UAAU,YAAY,IAAI;AAChC,QAAM,QAAkB,CAAC;AACzB,QAAM,iBAA2B,CAAC;AAClC,MAAI,YAAY;AAEhB,QAAM,OAAO,CAAC,cAA4B;AACxC,eAAW,SAAS,YAAY,WAAW,EAAE,eAAe,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAO,EAAE,OAAO,EAAE,OAAO,KAAK,CAAE,GAAG;AAC9G,YAAM,WAAWD,MAAK,WAAW,MAAM,IAAI;AAC3C,YAAM,OAAO,SAAS,MAAM,QAAQ,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG;AACzD,UAAI,eAAe,KAAK,IAAI,GAAG;AAC7B,uBAAe,KAAK,IAAI;AACxB;AAAA,MACF;AACA,UAAI,QAAQ,KAAK,CAAC,YAAY,QAAQ,SAAS,IAAI,CAAC,GAAG;AACrD;AAAA,MACF;AACA,UAAI,MAAM,eAAe,EAAG;AAC5B,UAAI,MAAM,YAAY,GAAG;AAAE,aAAK,QAAQ;AAAG;AAAA,MAAU;AACrD,YAAM,OAAO,SAAS,QAAQ,EAAE;AAChC,UAAI,OAAO,OAAO,cAAc;AAC9B,cAAM,IAAI,UAAU,2BAA2B,QAAQ,IAAI,uBAAuB,OAAO,YAAY,gBAAgB;AAAA,UACnH,aAAa;AAAA,UACb,SAAS,EAAE,MAAM,KAAK;AAAA,QACxB,CAAC;AAAA,MACH;AACA,YAAM,KAAK,IAAI;AACf,mBAAa;AACb,UAAI,MAAM,SAAS,OAAO,YAAY,YAAY,OAAO,eAAe;AACtE,cAAM,IAAI,UAAU,2BAA2B,qDAAqD;AAAA,UAClG,aAAa;AAAA,UACb,SAAS,EAAE,OAAO,MAAM,QAAQ,WAAW,OAAO;AAAA,QACpD,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,OAAK,IAAI;AAGT,QAAM,OAAOE,YAAW,QAAQ;AAChC,aAAW,QAAQ,OAAO;AACxB,SAAK,OAAO,IAAI;AAChB,SAAK,OAAOD,cAAaD,MAAK,MAAM,IAAI,CAAC,CAAC;AAAA,EAC5C;AAEA,SAAO,EAAE,OAAO,WAAW,QAAQ,KAAK,OAAO,KAAK,GAAG,eAAe;AACxE;AAwCO,SAAS,qBAAqB,YAAoB,SAAS,WAAW,UAAU,GAAmB;AACxG,SAAO;AAAA,IACL,QAAQ,OAAO;AAAA,IACf,OAAO,OAAO,MAAM,IAAI,CAAC,UAAU,EAAE,MAAM,SAASG,cAAaC,MAAK,YAAY,IAAI,CAAC,EAAE,SAAS,QAAQ,EAAE,EAAE;AAAA,EAChH;AACF;;;ACrJA,SAAS,oBAAoB;;;ACD7B,OAAO,UAAU;;;ACyCjB,IAAM,cAAc,KAAK,KAAK;;;ACyFvB,IAAM,YAAY;AAAA,EACvB,SAAS,KAAK,KAAK;AAAA,EACnB,SAAS,KAAK;AAAA,EACd,aAAa,KAAK;AAAA,EAClB,cAAc,KAAK;AAAA,EACnB,KAAK,KAAK,KAAK,KAAK;AACtB;;;ACjIA,SAAS,gBAAAC,qBAAoB;AAQtB,IAAM,cAAc;AAAA,EACzB,aAAa;AAAA,EACb,eAAe;AAAA,EACf,SAAS;AAAA,EACT,gBAAgB,IAAI,OAAO;AAAA,EAC3B,kBAAkB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlB,mCAAmC;AAAA,EACnC,yCAAyC;AAAA,EACzC,uCAAuC;AACzC;;;ACfO,IAAM,iBAAiB;AAAA,EAC5B,gBAAgB,KAAK,OAAO;AAAA,EAC5B,aAAa;AAAA;AAAA,EAEb,aAAa;AAAA,EACb,uCAAuC;AACzC;;;ACrBA,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAC9B,SAAS,iBAAAC,sBAAqB;;;ACJ9B,SAAS,gBAAAC,qBAAoB;;;ADgB7B,IAAM,iBAAiBC,MAAKC,SAAQC,eAAc,YAAY,GAAG,CAAC,GAAG,MAAM,YAAY;;;AENhF,IAAM,YAAN,MAAgB;AAAA,EACZ;AAAA,EACQ;AAAA,EACA;AAAA,EAEjB,YAAY,SAAwB;AAClC,SAAK,SAAS,QAAQ,OAAO,QAAQ,OAAO,EAAE;AAC9C,SAAK,QAAQ,QAAQ;AACrB,SAAK,YAAY,QAAQ,aAAa;AAAA,EACxC;AAAA,EAEA,MAAM,QAAW,QAAgB,MAAc,UAI3C,CAAC,GAAe;AAClB,UAAM,MAAM,IAAI,IAAI,KAAK,SAAS,IAAI;AACtC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,SAAS,CAAC,CAAC,GAAG;AAC9D,UAAI,UAAU,OAAW,KAAI,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IAClE;AAEA,UAAM,UAAkC,EAAE,QAAQ,mBAAmB;AACrE,QAAI,KAAK,MAAO,SAAQ,gBAAgB,UAAU,KAAK,KAAK;AAC5D,QAAI,QAAQ,SAAS,OAAW,SAAQ,cAAc,IAAI;AAC1D,QAAI,QAAQ,eAAgB,SAAQ,iBAAiB,IAAI,QAAQ;AAEjE,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,GAAI,QAAQ,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,QAAQ,IAAI,EAAE,IAAI,CAAC;AAAA,QAC3E,QAAQ,YAAY,QAAQ,KAAK,SAAS;AAAA,MAC5C,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,IAAI,UAAU,wBAAwB,wCAAwC,KAAK,MAAM,KAAK;AAAA,QAClG,aAAa;AAAA,QACb,WAAW;AAAA,QACX,OAAO;AAAA,MACT,CAAC;AAAA,IACH;AAEA,QAAI,SAAS,WAAW,IAAK,QAAO;AACpC,UAAM,UAAU,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAEtD,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,QAAQ;AACtB,YAAM,IAAI,UAAU,OAAO,QAAQ,YAAY,OAAO,WAAW,uBAAuB,SAAS,MAAM,KAAK;AAAA,QAC1G,GAAI,OAAO,cAAc,EAAE,aAAa,MAAM,YAAY,IAAI,CAAC;AAAA,QAC/D,GAAI,OAAO,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;AAAA,QACvE,GAAI,OAAO,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,QACnD,GAAI,SAAS,QAAQ,IAAI,cAAc,IAAI,EAAE,WAAW,SAAS,QAAQ,IAAI,cAAc,EAAG,IAAI,CAAC;AAAA,MACrG,CAAC;AAAA,IACH;AACA,WAAO;AAAA,EACT;AAAA,EAEA,IAAO,MAAc,OAAiE;AACpF,WAAO,KAAK,QAAW,OAAO,MAAM,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC;AAAA,EAC5D;AAAA,EAEA,KAAQ,MAAc,MAAgB,gBAAqC;AACzE,WAAO,KAAK,QAAW,QAAQ,MAAM;AAAA,MACnC,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC;AAAA,MACrC,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;AAAA,IAC7C,CAAC;AAAA,EACH;AAAA,EAEA,IAAO,MAAc,MAA4B;AAC/C,WAAO,KAAK,QAAW,OAAO,MAAM,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC,CAAC;AAAA,EACxE;AAAA,EAEA,IAAO,MAAc,OAAiE;AACpF,WAAO,KAAK,QAAW,UAAU,MAAM,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC;AAAA,EAC/D;AAAA,EAEA,gBAAgB,WAAuG;AACrH,WAAO,KAAK,KAAK,iBAAiB,qBAAqB,SAAS,CAAC;AAAA,EACnE;AACF;;;ACvFA,SAAS,WAAW,cAAAC,aAAY,aAAAC,YAAW,gBAAAC,eAAc,iBAAAC,sBAAqB;AAC9E,SAAS,eAAe;AACxB,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAe9B,IAAM,QAAmB,EAAE,SAAS,GAAG,SAAS,MAAM,UAAU,CAAC,EAAE;AAE5D,SAAS,aAAqB;AACnC,SAAO,QAAQ,IAAI,eAAeA,MAAK,QAAQ,GAAG,SAAS,kBAAkB;AAC/E;AAEO,SAAS,aAAwB;AACtC,QAAM,OAAO,WAAW;AACxB,MAAI,CAACL,YAAW,IAAI,EAAG,QAAO,EAAE,GAAG,OAAO,UAAU,CAAC,EAAE;AACvD,MAAI;AACF,WAAO,KAAK,MAAME,cAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO,EAAE,GAAG,OAAO,UAAU,CAAC,EAAE;AAAA,EAClC;AACF;AAEO,SAAS,WAAW,QAAyB;AAClD,QAAM,OAAO,WAAW;AACxB,EAAAD,WAAUG,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,EAAAD,eAAc,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,GAAM,EAAE,MAAM,IAAM,CAAC;AAC3E,YAAU,MAAM,GAAK;AACvB;AAEO,SAAS,iBAAwC;AACtD,QAAM,SAAS,WAAW;AAC1B,MAAI,CAAC,OAAO,QAAS,QAAO;AAC5B,SAAO,OAAO,SAAS,OAAO,OAAO,KAAK;AAC5C;AAEO,SAAS,WAAW,MAAc,aAAmC;AAC1E,QAAM,SAAS,WAAW;AAC1B,SAAO,SAAS,IAAI,IAAI;AACxB,SAAO,UAAU;AACjB,aAAW,MAAM;AACnB;AAEO,SAAS,aAAa,MAAqB;AAChD,QAAM,SAAS,WAAW;AAC1B,QAAM,SAAS,QAAQ,OAAO;AAC9B,MAAI,OAAQ,QAAO,OAAO,SAAS,MAAM;AACzC,MAAI,OAAO,YAAY,OAAQ,QAAO,UAAU,OAAO,KAAK,OAAO,QAAQ,EAAE,CAAC,KAAK;AACnF,aAAW,MAAM;AACnB;;;AC/CO,IAAM,oBAAoB,CAAC,SAAS,UAAU,cAAc,aAAa;AAEzE,IAAM,kBAAkB;AAc/B,eAAsB,gBACpB,iBACA,UAAwB,CAAC,GACS;AAClC,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,MAAI,WAAW;AACf,aAAS;AACP,UAAM,SAAS,MAAM,gBAAgB;AACrC,QAAI,OAAO,WAAW,UAAU;AAC9B,iBAAW,OAAO;AAClB,cAAQ,WAAW,OAAO,MAAM;AAAA,IAClC;AACA,QAAI,kBAAkB,SAAS,OAAO,MAAM,EAAG,QAAO;AAGtD,QAAI,KAAK,IAAI,KAAK,SAAU,QAAO;AACnC,UAAM,IAAI,QAAQ,CAACG,aAAY,WAAWA,UAAS,MAAM,CAAC;AAAA,EAC5D;AACF;AAYO,SAAS,aAAa,eAA8E;AACzG,MAAI,CAAC,iBAAiB,OAAO,cAAc,qBAAqB,SAAU,QAAO;AACjF,SAAO;AAAA,IACL,aAAa,cAAc;AAAA,IAC3B,QAAQ,cAAc,gBAAgB;AAAA,IACtC,YAAY,OAAO,cAAc,WAAW,CAAC;AAAA,EAC/C;AACF;AAGO,SAAS,cAAc,SAAkD;AAC9E,MAAI,CAAC,QAAS,QAAO;AAGrB,SAAO,QAAQ,SACX,0BAA0B,QAAQ,WAAW,MAC7C,aAAa,QAAQ,aAAa,KAAM,QAAQ,CAAC,CAAC,MAAM,QAAQ,WAAW;AACjF;AAGO,SAAS,gBAAgB,QAAyC;AACvE,QAAM,UAAU,QAAQ,aAAa;AACrC,MAAI,OAAO,YAAY,YAAY,YAAY,GAAI,QAAO;AAC1D,SAAO,QAAQ,aAAa;AAC9B;AAGO,SAAS,mBAAmB,QAAyC;AAC1E,QAAM,cAAc,QAAQ,aAAa;AACzC,SAAO,OAAO,gBAAgB,YAAY,gBAAgB,KACtD,cACA;AACN;;;AC1FO,IAAM,kBAAkB;AAExB,IAAM,OAAO;AAAA,EAClB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AAAA,EACN,YAAY;AAAA,EACZ,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,qBAAqB;AAAA,EACrB,UAAU;AAAA,EACV,aAAa;AACf;AAEA,IAAM,eAAmD;AAAA,EACvD,iBAAiB,KAAK;AAAA,EACtB,WAAW,KAAK;AAAA,EAChB,WAAW,KAAK;AAAA,EAChB,UAAU,KAAK;AAAA,EACf,mBAAmB,KAAK;AAAA,EACxB,uBAAuB,KAAK;AAAA,EAC5B,yBAAyB,KAAK;AAAA,EAC9B,2BAA2B,KAAK;AAAA,EAChC,kCAAkC,KAAK;AAAA,EACvC,mBAAmB,KAAK;AAAA,EACxB,YAAY,KAAK;AAAA,EACjB,cAAc,KAAK;AAAA,EACnB,qBAAqB,KAAK;AAAA,EAC1B,sBAAsB,KAAK;AAAA,EAC3B,sBAAsB,KAAK;AAAA,EAC3B,cAAc,KAAK;AAAA,EACnB,8BAA8B,KAAK;AACrC;AAMO,IAAM,SAAN,MAAa;AAAA,EACD;AAAA,EAEjB,YAAY,SAAwB;AAClC,SAAK,OAAO,QAAQ;AAAA,EACtB;AAAA;AAAA,EAGA,SAAS,SAAuB;AAC9B,YAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AAAA,EACrC;AAAA,EAEA,KAAK,IAAa,SAAuB;AACvC,YAAQ,OAAO,MAAM,GAAG,KAAK,WAAM,QAAG,IAAI,OAAO;AAAA,CAAI;AAAA,EACvD;AAAA,EAEA,KAAK,SAAuB;AAC1B,YAAQ,OAAO,MAAM,KAAK,OAAO;AAAA,CAAI;AAAA,EACvC;AAAA;AAAA,EAGA,OAAO,SAAkC,OAAyB;AAChE,QAAI,KAAK,MAAM;AACb,cAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,YAAY,iBAAiB,IAAI,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAC1G;AAAA,IACF;AACA,UAAM;AAAA,EACR;AAAA,EAEA,KAAK,OAAuB;AAC1B,QAAI,YAAY,KAAK,GAAG;AACtB,YAAM,OAAO;AAAA,QACX,YAAY;AAAA,QACZ,IAAI;AAAA,QACJ,OAAO;AAAA,UACL,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM;AAAA,UACf,aAAa,MAAM;AAAA,UACnB,WAAW,MAAM;AAAA,UACjB,SAAS,MAAM;AAAA,QACjB;AAAA,MACF;AACA,UAAI,KAAK,KAAM,SAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,WACnE;AACH,gBAAQ,OAAO,MAAM;AAAA,SAAO,MAAM,IAAI,KAAK,MAAM,OAAO;AAAA,CAAI;AAC5D,gBAAQ,OAAO,MAAM,YAAO,MAAM,WAAW;AAAA,CAAI;AACjD,cAAM,OAAO,MAAM,QAAQ;AAC3B,YAAI,MAAM,QAAQ,IAAI,EAAG,YAAW,UAAU,KAAM,SAAQ,OAAO,MAAM,YAAO,OAAO,MAAM,CAAC;AAAA,CAAI;AAAA,MACpG;AACA,cAAQ,KAAK,aAAa,MAAM,IAAI,KAAK,KAAK,KAAK;AAAA,IACrD;AACA,UAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACrE,QAAI,KAAK,KAAM,SAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,YAAY,iBAAiB,IAAI,OAAO,OAAO,EAAE,MAAM,YAAY,QAAQ,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,QAC/I,SAAQ,OAAO,MAAM;AAAA,SAAO,OAAO;AAAA,CAAI;AAC5C,YAAQ,KAAK,KAAK,KAAK;AAAA,EACzB;AACF;AAGO,SAAS,gBAAyB;AACvC,SAAO,QAAQ,MAAM,UAAU,QAAQ,QAAQ,OAAO,UAAU;AAClE;;;AvB9EA,SAAS,UAAU,MAAsB;AACvC,QAAM,aAAuB,CAAC;AAC9B,QAAM,QAA0C,CAAC;AACjD,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS;AAChD,UAAM,QAAQ,KAAK,KAAK;AACxB,QAAI,MAAM,WAAW,IAAI,GAAG;AAC1B,YAAM,CAAC,MAAM,MAAM,IAAI,MAAM,MAAM,CAAC,EAAE,MAAM,GAAG;AAC/C,UAAI,WAAW,OAAW,OAAM,IAAK,IAAI;AAAA,eAChC,KAAK,QAAQ,CAAC,KAAK,CAAC,KAAK,QAAQ,CAAC,EAAG,WAAW,GAAG,EAAG,OAAM,IAAK,IAAI,KAAK,EAAE,KAAK;AAAA,UACrF,OAAM,IAAK,IAAI;AAAA,IACtB,OAAO;AACL,iBAAW,KAAK,KAAK;AAAA,IACvB;AAAA,EACF;AACA,QAAM,CAAC,UAAU,QAAQ,GAAG,IAAI,IAAI;AACpC,QAAM,UAAU,oBAAI,IAAI,CAAC,UAAU,WAAW,gBAAgB,WAAW,KAAK,CAAC;AAC/E,SAAO;AAAA,IACL;AAAA,IACA,YAAY,QAAQ,IAAI,OAAO,IAAK,KAAK,CAAC,KAAK,OAAQ;AAAA,IACvD,YAAY,QAAQ,IAAI,OAAO,IAAI,KAAK,MAAM,CAAC,IAAI;AAAA,IACnD;AAAA,EACF;AACF;AAEA,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkCd,eAAe,OAAsB;AACnC,QAAM,OAAO,UAAU,QAAQ,KAAK,MAAM,CAAC,CAAC;AAC5C,QAAM,SAAS,IAAI,OAAO,EAAE,MAAM,KAAK,MAAM,SAAS,QAAQ,KAAK,MAAM,SAAS,OAAO,CAAC;AAE1F,MAAI;AACF,UAAM,SAAS,MAAM,MAAM;AAAA,EAC7B,SAAS,OAAO;AACd,WAAO,KAAK,KAAK;AAAA,EACnB;AACF;AAEA,SAAS,MAAM,MAAoB;AACjC,SAAO,QAAQ,OAAO,KAAK,MAAM,QAAQ,WAAW,KAAK,MAAM,MAAM,QAAQ,IAAI,CAAC;AACpF;AAEA,SAAS,aAAa,KAA+C;AACnE,QAAM,OAAOC,MAAK,KAAK,WAAW;AAClC,MAAI,CAACC,YAAW,IAAI,GAAG;AACrB,UAAM,IAAI,UAAU,aAAa,mCAAmC;AAAA,MAClE,aAAa;AAAA,MACb,SAAS,EAAE,UAAU,KAAK;AAAA,IAC5B,CAAC;AAAA,EACH;AACA,SAAO,EAAE,QAAQC,cAAa,MAAM,MAAM,GAAG,KAAK;AACpD;AAEA,SAAS,UAAU,MAAuB;AACxC,QAAM,UAAU,eAAe;AAC/B,QAAM,UAAU,OAAO,KAAK,MAAM,QAAQ,WAAW,KAAK,MAAM,MAAM,WACjE,QAAQ,IAAI,gBACZ,SAAS;AACd,QAAM,SAAS,OAAO,KAAK,MAAM,UAAU,WAAW,KAAK,MAAM,QAAQ,WACpE,QAAQ,IAAI,cACZ,SAAS;AAEd,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,UAAU,wBAAwB,gCAAgC;AAAA,MAC1E,aAAa;AAAA,MACb,SAAS,EAAE,cAAc,QAAQ;AAAA,IACnC,CAAC;AAAA,EACH;AACA,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,UAAU,mBAAmB,0CAA0C;AAAA,MAC/E,aAAa;AAAA,MACb,SAAS,EAAE,cAAc,UAAU;AAAA,IACrC,CAAC;AAAA,EACH;AACA,SAAO,IAAI,UAAU,EAAE,QAAQ,MAAM,CAAC;AACxC;AAGA,eAAe,aAAa,OAAe,cAAuC;AAChF,MAAI,CAAC,cAAc,GAAG;AACpB,QAAI,CAAC,QAAQ,MAAM,OAAO;AACxB,YAAM,QAAQA,cAAa,GAAG,MAAM,EAAE,KAAK;AAC3C,UAAI,MAAO,QAAO;AAAA,IACpB;AACA,UAAM,IAAI,UAAU,wBAAwB,GAAG,KAAK,6CAA6C;AAAA,MAC/F,aAAa,oCAAoC,YAAY;AAAA,MAC7D,SAAS,EAAE,aAAa;AAAA,IAC1B,CAAC;AAAA,EACH;AACA,QAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,MAAI;AACF,YAAQ,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,GAAG,KAAK;AAAA,EAChD,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;AAWA,SAAS,cAAc,MAAoB;AACzC,QAAM,OAAO,KAAK,MAAM;AACxB,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,QAAM,UAAU,OAAO,IAAI;AAC3B,MAAI,CAAC,OAAO,SAAS,OAAO,KAAK,WAAW,EAAG,OAAM,WAAW,8BAA8B;AAC9F,SAAO,UAAU;AACnB;AAEA,eAAe,SAAS,MAAY,QAA+B;AACjE,QAAM,MAAM,MAAM,IAAI;AAEtB,UAAQ,KAAK,SAAS;AAAA,IACpB,KAAK;AAAA,IACL,KAAK;AACH,cAAQ,OAAO,MAAM,KAAK;AAC1B,cAAQ,KAAK,KAAK,YAAY,SAAS,KAAK,KAAK,KAAK,KAAK;AAC3D;AAAA;AAAA,IAGF,KAAK,SAAS;AACZ,YAAM,SAAS,OAAO,KAAK,MAAM,QAAQ,WAAW,KAAK,MAAM,MAAM,QAAQ,IAAI;AACjF,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,UAAU,wBAAwB,oCAAoC;AAAA,UAC9E,aAAa;AAAA,UAAqB,SAAS,EAAE,cAAc,QAAQ;AAAA,QACrE,CAAC;AAAA,MACH;AACA,YAAM,QAAQ,OAAO,KAAK,MAAM,UAAU,WACtC,KAAK,MAAM,QACX,MAAM,aAAa,aAAa,SAAS;AAC7C,YAAM,UAAU,OAAO,KAAK,MAAM,YAAY,WAAW,KAAK,MAAM,UAAU;AAC9E,iBAAW,SAAS,EAAE,QAAQ,MAAM,CAAC;AACrC,aAAO,OAAO,EAAE,SAAS,OAAO,GAAG,MAAM,OAAO,KAAK,MAAM,0BAA0B,MAAM,cAAc,OAAO,KAAK,CAAC;AACtH;AAAA,IACF;AAAA,IAEA,KAAK,UAAU;AACb,mBAAa,OAAO,KAAK,MAAM,YAAY,WAAW,KAAK,MAAM,UAAU,MAAS;AACpF,aAAO,OAAO,EAAE,WAAW,KAAK,GAAG,MAAM,OAAO,KAAK,MAAM,sBAAsB,CAAC;AAClF;AAAA,IACF;AAAA,IAEA,KAAK,UAAU;AACb,YAAM,UAAU,eAAe;AAC/B,UAAI,CAAC,SAAS;AACZ,cAAM,IAAI,UAAU,mBAAmB,kBAAkB,EAAE,aAAa,gCAAgC,CAAC;AAAA,MAC3G;AACA,YAAM,eAAe,MAAM,UAAU,IAAI,EAAE,IAA6B,2BAA2B;AACnG,aAAO,OAAO,EAAE,QAAQ,QAAQ,QAAQ,UAAU,aAAa,GAAG,MAAM;AACtE,eAAO,KAAK,MAAM,kBAAkB,QAAQ,MAAM,EAAE;AAAA,MACtD,CAAC;AACD;AAAA,IACF;AAAA,IAEA,KAAK,OAAO;AACV,UAAI,KAAK,eAAe,SAAU,OAAM,WAAW,+CAA+C;AAClG,YAAM,OAAO,OAAO,KAAK,MAAM,QAAQ,EAAE;AACzC,YAAM,QAAQ,OAAO,KAAK,MAAM,SAAS,EAAE;AAC3C,UAAI,CAAC,QAAQ,CAAC,MAAO,OAAM,WAAW,+CAA+C;AACrF,YAAM,UAAU,OAAO,KAAK,MAAM,QAAQ,WAAW,KAAK,MAAM,MAAM,WAAc,QAAQ,IAAI;AAChG,UAAI,CAAC,OAAQ,OAAM,WAAW,2DAA2D;AACzF,YAAM,UAAU,MAAM,IAAI,UAAU,EAAE,OAAO,CAAC,EAAE;AAAA,QAC9C;AAAA,QAAqB,EAAE,MAAM,aAAa,OAAO,KAAK,MAAM,QAAQ,IAAI,GAAG,YAAY,MAAM;AAAA,MAAC;AAChG,iBAAW,WAAW,EAAE,QAAQ,OAAO,QAAQ,cAAc,kBAAkB,MAAM,OAAO,MAAM,CAAC;AACnG,aAAO;AAAA,QACL,EAAE,gBAAgB,QAAQ,aAAa,IAAI,MAAM,QAAQ,aAAa,MAAM,UAAU,QAAQ,SAAS;AAAA,QACvG,MAAM;AACJ,iBAAO,KAAK,MAAM,gBAAgB,QAAQ,aAAa,IAAI,WAAW;AACtE,iBAAO,KAAK,MAAM,oCAAoC;AACtD,iBAAO,SAAS;AAAA,sCAAyC,QAAQ,QAAQ,EAAE;AAAA,QAC7E;AAAA,MACF;AACA;AAAA,IACF;AAAA;AAAA,IAGA,KAAK,QAAQ;AACX,YAAM,OAAO,KAAK,WAAW,CAAC,KAAK,SAAS,GAAG;AAC/C,YAAM,QAAQ,OAAO,KAAK,MAAM,SAAS,eAAe,GAAG,SAAS,iBAAiB;AACrF,YAAM,OAAOF,MAAK,KAAK,WAAW;AAClC,UAAIC,YAAW,IAAI,KAAK,KAAK,MAAM,UAAU,MAAM;AACjD,cAAM,IAAI,UAAU,YAAY,6BAA6B;AAAA,UAC3D,aAAa;AAAA,UAAiC,SAAS,EAAE,KAAK;AAAA,QAChE,CAAC;AAAA,MACH;AACA,MAAAE,eAAc,MAAM,gBAAgB,MAAM,KAAK,CAAC;AAChD,aAAO,OAAO,EAAE,MAAM,KAAK,GAAG,MAAM,OAAO,KAAK,MAAM,SAAS,IAAI,EAAE,CAAC;AACtE;AAAA,IACF;AAAA,IAEA,KAAK,YAAY;AACf,YAAM,EAAE,OAAO,IAAI,aAAa,GAAG;AACnC,YAAM,SAAS,cAAc,MAAM;AACnC,aAAO;AAAA,QACL,EAAE,MAAM,OAAO,WAAW,SAAS,MAAM,gBAAgB,OAAO,QAAQ,UAAU,OAAO,SAAS;AAAA,QAClG,MAAM;AACJ,iBAAO,KAAK,MAAM,uBAAuB,OAAO,WAAW,SAAS,IAAI,IAAI;AAC5E,qBAAW,WAAW,OAAO,SAAU,QAAO,KAAK,OAAO;AAAA,QAC5D;AAAA,MACF;AACA;AAAA,IACF;AAAA,IAEA,KAAK,UAAU;AACb,YAAM,SAA+D,CAAC;AACtE,YAAM,UAAU,eAAe;AAC/B,aAAO,KAAK,EAAE,MAAM,eAAe,IAAI,QAAQ,OAAO,GAAG,QAAQ,UAAU,cAAc,QAAQ,MAAM,KAAK,mBAAmB,CAAC;AAEhI,UAAI,aAAa;AACjB,UAAI,SAAS;AACb,UAAI;AACF,cAAM,SAAS,cAAc,aAAa,GAAG,EAAE,MAAM;AACrD,qBAAa;AACb,iBAAS,GAAG,OAAO,WAAW,SAAS,IAAI,KAAK,OAAO,WAAW,QAAQ,IAAI,IAAI,OAAO,WAAW,QAAQ,QAAQ;AACpH,cAAM,QAAQH,MAAK,KAAK,OAAO,WAAW,QAAQ,UAAU;AAC5D,eAAO,KAAK,EAAE,MAAM,cAAc,IAAIC,YAAW,KAAK,GAAG,QAAQ,OAAO,WAAW,QAAQ,WAAW,CAAC;AACvG,YAAI,OAAO,WAAW,UAAU,UAAU;AACxC,gBAAM,aAAaD,MAAK,KAAK,OAAO,WAAW,UAAU,SAAS,UAAU;AAC5E,iBAAO,KAAK,EAAE,MAAM,cAAc,IAAIC,YAAW,UAAU,GAAG,QAAQ,OAAO,WAAW,UAAU,SAAS,WAAW,CAAC;AAAA,QACzH;AAAA,MACF,SAAS,OAAO;AACd,iBAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAChE;AACA,aAAO,QAAQ,EAAE,MAAM,YAAY,IAAI,YAAY,OAAO,CAAC;AAE3D,iBAAW,SAAS,CAAC,QAAQ,cAAc,kBAAkB,GAAG;AAC9D,YAAIA,YAAWD,MAAK,KAAK,KAAK,CAAC,GAAG;AAChC,iBAAO,KAAK,EAAE,MAAM,WAAW,KAAK,IAAI,IAAI,MAAM,QAAQ,yCAAyC,CAAC;AAAA,QACtG;AAAA,MACF;AAEA,UAAI,SAAS;AACX,YAAI;AACF,gBAAM,UAAU,IAAI,EAAE,IAAI,2BAA2B;AACrD,iBAAO,KAAK,EAAE,MAAM,iBAAiB,IAAI,MAAM,QAAQ,QAAQ,OAAO,CAAC;AAAA,QACzE,SAAS,OAAO;AACd,iBAAO,KAAK,EAAE,MAAM,iBAAiB,IAAI,OAAO,QAAS,MAAgB,QAAQ,CAAC;AAAA,QACpF;AAAA,MACF;AAEA,YAAM,KAAK,OAAO,MAAM,CAAC,UAAU,MAAM,EAAE;AAC3C,aAAO,OAAO,EAAE,QAAQ,SAAS,GAAG,GAAG,MAAM;AAC3C,mBAAW,SAAS,OAAQ,QAAO,KAAK,MAAM,IAAI,GAAG,MAAM,IAAI,KAAK,MAAM,MAAM,EAAE;AAAA,MACpF,CAAC;AACD,UAAI,CAAC,GAAI,SAAQ,KAAK,KAAK,UAAU;AACrC;AAAA,IACF;AAAA;AAAA,IAGA,KAAK,QAAQ;AACX,YAAM,EAAE,OAAO,IAAI,aAAa,GAAG;AACnC,YAAM,SAAS,UAAU,IAAI;AAC7B,YAAM,WAAW,MAAM,OAAO,gBAAgB,GAAG;AACjD,YAAM,WAAW,MAAM,OAAO,KAA8C,iBAAiB;AAAA,QAC3F,aAAa,SAAS;AAAA,QACtB,UAAU;AAAA,QACV,aAAa,OAAO,KAAK,MAAM,QAAQ,WAAW,KAAK,MAAM,MAAM;AAAA,MACrE,CAAC;AACD,aAAO,OAAO,EAAE,MAAM,SAAS,MAAM,UAAU,SAAS,SAAS,GAAG,MAAM,UAAU,QAAQ,SAAS,MAAM,SAAS,QAAQ,CAAC;AAC7H;AAAA,IACF;AAAA,IAEA,KAAK,UAAU;AACb,YAAM,EAAE,OAAO,IAAI,aAAa,GAAG;AACnC,YAAM,SAAS,UAAU,IAAI;AAC7B,aAAO,SAAS,gBAAW;AAC3B,YAAM,WAAW,MAAM,OAAO,gBAAgB,GAAG;AACjD,YAAM,UAAU,MAAM,OAAO,KAA8C,iBAAiB;AAAA,QAC1F,aAAa,SAAS;AAAA,QAAa,UAAU;AAAA,QAC7C,aAAa,OAAO,KAAK,MAAM,QAAQ,WAAW,KAAK,MAAM,MAAM;AAAA,MACrE,CAAC;AACD,UAAI,KAAK,MAAM,SAAS,KAAM,WAAU,QAAQ,QAAQ,MAAM,QAAQ,QAAQ;AAE9E,UAAI,QAAQ,KAAK,UAAU,SAAS,KAAK,KAAK,MAAM,QAAQ,QAAQ,CAAC,cAAc,GAAG;AACpF,cAAM,IAAI,UAAU,qBAAqB,+DAA+D;AAAA,UACtG,SAAS,EAAE,WAAW,QAAQ,KAAK,WAAW,QAAQ,QAAQ,KAAK,QAAQ,aAAa;AAAA,YACtF;AAAA,YACA;AAAA,UACF,EAAE;AAAA,QACJ,CAAC;AAAA,MACH;AAEA,aAAO,SAAS,iBAAY;AAC5B,YAAM,SAAS,MAAM,OAAO,KAAqB,mBAAmB;AAAA,QAClE,aAAa,SAAS;AAAA,QACtB,UAAU;AAAA,QACV,aAAa,OAAO,KAAK,MAAM,QAAQ,WAAW,KAAK,MAAM,MAAM;AAAA,QACnE,QAAQ,QAAQ,KAAK;AAAA,QACrB,SAAS,KAAK,MAAM,QAAQ;AAAA,MAC9B,GAAG,OAAO,KAAK,MAAM,iBAAiB,MAAM,WAAW,KAAK,MAAM,iBAAiB,IAAI,MAAS;AAKhG,YAAM,UAAmC,KAAK,MAAM,SAAS,MAAM,OAC/D,OACA,MAAM;AAAA,QACN,MAAM,OAAO,IAAsB,mBAAmB,OAAO,YAAY,EAAE;AAAA,QAC3E,EAAE,WAAW,cAAc,IAAI,GAAG,UAAU,CAACI,YAAW,OAAO,SAAS,KAAKA,OAAM,QAAG,EAAE;AAAA,MAC1F;AACF,YAAM,SAAS,SAAS,UAAU,OAAO;AACzC,YAAM,QAAQ,aAAa,SAAS,aAAa;AAEjD,aAAO;AAAA,QACL;AAAA,UACE,OAAO,OAAO;AAAA,UAAO,cAAc,OAAO;AAAA,UAAc;AAAA,UAAQ,KAAK,OAAO;AAAA,UAC5E,UAAU,OAAO;AAAA,UACjB,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,QAC3B;AAAA,QACA,MAAM;AACJ,iBAAO,SAAS,EAAE;AAClB,gBAAM,QAAQ,cAAc,KAAK;AACjC,cAAI,MAAO,QAAO,KAAK,MAAM,KAAK;AAClC,cAAI,YAAY,MAAM;AAGpB,mBAAO,SAAS,KAAK,MAAM,SAAS,MAAM,OACtC,qBAAgB,OAAO,YAAY,OAAO,MAAM,oCAChD,qBAAgB,OAAO,YAAY,2BAA2B,KAAK,MAAM,cAAc,IAAI,IAAI,GAAI,CAAC,kCAAkC;AAAA,UAC5I,OAAO;AACL,mBAAO,KAAK,WAAW,SAAS,cAAc,OAAO,YAAY,OAAO,MAAM,EAAE;AAChF,gBAAI,QAAQ,UAAW,QAAO,KAAK,GAAG,QAAQ,SAAS,KAAK,gBAAgB,OAAO,CAAC,EAAE;AAAA,UACxF;AACA,iBAAO,KAAK,MAAM,QAAQ,OAAO,GAAG,EAAE;AACtC,qBAAW,WAAW,OAAO,SAAU,QAAO,KAAK,OAAO;AAAA,QAC5D;AAAA,MACF;AAEA,UAAI,WAAW,UAAU;AACvB,cAAM,IAAI,UAAU,wBAAwB,cAAc,OAAO,YAAY,YAAY,gBAAgB,OAAO,CAAC,IAAI;AAAA;AAAA;AAAA,UAGnH,aAAa,mBAAmB,OAAO;AAAA,UACvC,SAAS;AAAA,YACP,cAAc,OAAO;AAAA,YACrB,GAAI,SAAS,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,YAC7D,GAAI,SAAS,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;AAAA,UACrE;AAAA,QACF,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAAA,IAEA,KAAK,YAAY;AACf,YAAM,eAAe,KAAK,WAAW,CAAC;AACtC,UAAI,CAAC,aAAc,OAAM,WAAW,4BAA4B;AAChE,YAAM,SAAS,MAAM,UAAU,IAAI,EAAE;AAAA,QACnC,mBAAmB,YAAY;AAAA,MAAW;AAC5C,aAAO,OAAO,EAAE,cAAc,OAAO,WAAW,IAAI,QAAQ,OAAO,WAAW,QAAQ,eAAe,OAAO,cAAc,GAAG,MAAM;AACjI,eAAO,KAAK,MAAM,yBAAyB,OAAO,WAAW,EAAE,EAAE;AACjE,YAAI,OAAO,cAAe,QAAO,KAAK,OAAO,aAAa;AAAA,MAC5D,CAAC;AACD;AAAA,IACF;AAAA;AAAA,IAGA,KAAK,UAAU;AACb,YAAM,SAAS,UAAU,IAAI;AAC7B,YAAM,OAAO,MAAM,OAAO,IAA2B,6BAA6B;AAClF,aAAO,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM;AACxC,YAAI,KAAK,MAAM,WAAW,GAAG;AAAE,iBAAO,SAAS,iCAAiC;AAAG;AAAA,QAAQ;AAC3F,mBAAW,OAAO,KAAK,OAAO;AAC5B,iBAAO,SAAS,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,IAAI,IAAI,OAAO,OAAO,EAAE,CAAC,SAAS,IAAI,QAAQ,cAAc,IAAI,kBAAkB,OAAO,EAAE;AAAA,QACnI;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAAA,IAEA,KAAK,QAAQ;AACX,YAAM,SAAS,UAAU,IAAI;AAC7B,YAAM,SAAS,KAAK,WAAW,CAAC,KAAK,cAAc,aAAa,GAAG,EAAE,MAAM,EAAE,WAAW,SAAS;AACjG,YAAM,MAAM,MAAM,WAAW,QAAQ,MAAM;AAC3C,YAAM,cAAc,MAAM,OAAO,IAAsC,YAAY,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC;AACrH,YAAM,aAAa,YAAY,MAAM,CAAC;AACtC,UAAI,CAAC,WAAY,OAAM,IAAI,UAAU,aAAa,OAAO,MAAM,0BAA0B;AACzF,YAAM,OAAO,MAAM,OAAO;AAAA,QACxB,mBAAmB,WAAW,EAAE;AAAA,QAAS;AAAA,UACvC,OAAO,OAAO,KAAK,MAAM,SAAS,GAAG;AAAA,UACrC,OAAO,OAAO,KAAK,MAAM,UAAU,WAAW,WAAW,KAAK,MAAM,KAAK,IAAI;AAAA,QAC/E;AAAA,MAAC;AACH,aAAO,OAAO,EAAE,OAAO,IAAI,IAAI,cAAc,WAAW,IAAI,MAAM,KAAK,MAAM,GAAG,MAAM;AACpF,mBAAW,SAAS,KAAK,OAAO;AAC9B,kBAAQ,OAAO,MAAM,GAAG,MAAM,SAAS,IAAI,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,MAAM,OAAO;AAAA,CAAI;AAAA,QACvF;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAAA,IAEA,KAAK,QAAQ;AACX,YAAM,SAAS,UAAU,IAAI;AAC7B,YAAM,SAAS,KAAK,WAAW,CAAC,KAAK,cAAc,aAAa,GAAG,EAAE,MAAM,EAAE,WAAW,SAAS;AACjG,YAAM,MAAM,MAAM,WAAW,QAAQ,MAAM;AAC3C,YAAM,eAAe,MAAM,OAAO,IAA0D,YAAY,IAAI,EAAE,eAAe;AAC7H,YAAM,cAAc,aAAa,MAAM,KAAK,CAAC,UAAU,MAAM,UAAU,KAAK,MAAM,OAAO,aAAa,KAAK,aAAa,MAAM,CAAC;AAC/H,UAAI,CAAC,YAAa,OAAM,IAAI,UAAU,aAAa,OAAO,IAAI,IAAI,2BAA2B;AAC7F,YAAM,MAAM,WAAW,YAAY,QAAQ;AAC3C,aAAO,OAAO,EAAE,OAAO,IAAI,IAAI,KAAK,aAAa,YAAY,KAAK,GAAG,MAAM,QAAQ,OAAO,MAAM,GAAG,GAAG;AAAA,CAAI,CAAC;AAC3G;AAAA,IACF;AAAA;AAAA,IAGA,KAAK,UAAU;AACb,YAAM,SAAS,UAAU,IAAI;AAC7B,YAAM,SAAS,OAAO,KAAK,MAAM,QAAQ,WACrC,KAAK,MAAM,MACX,cAAc,aAAa,GAAG,EAAE,MAAM,EAAE,WAAW,SAAS;AAChE,YAAM,MAAM,MAAM,WAAW,QAAQ,MAAM;AAE3C,UAAI,KAAK,eAAe,UAAU,KAAK,eAAe,MAAM;AAC1D,cAAM,WAAW,MAAM,OAAO,IAA8E,YAAY,IAAI,EAAE,SAAS;AACvI,eAAO,OAAO,EAAE,OAAO,IAAI,IAAI,UAAU,SAAS,MAAM,GAAG,MAAM;AAC/D,qBAAW,WAAW,SAAS,MAAO,QAAO,SAAS,GAAG,QAAQ,QAAQ,OAAO,EAAE,CAAC,IAAI,QAAQ,IAAI,KAAK,QAAQ,WAAW,GAAG;AAAA,QAChI,CAAC;AACD;AAAA,MACF;AACA,UAAI,KAAK,eAAe,SAAS;AAC/B,cAAM,UAAU,KAAK,WAAW,CAAC;AACjC,YAAI,CAAC,QAAS,OAAM,WAAW,2CAA2C;AAC1E,cAAM,UAAU,MAAM,OAAO;AAAA,UAC3B,YAAY,IAAI,EAAE,WAAW,mBAAmB,OAAO,CAAC;AAAA,UACxD,EAAE,MAAM,OAAO,KAAK,MAAM,SAAS,WAAW,KAAK,MAAM,OAAO,OAAO;AAAA,QAAC;AAC1E,eAAO;AAAA,UAAO,EAAE,OAAO,IAAI,IAAI,SAAS,QAAQ,SAAS,MAAM,QAAQ,KAAK;AAAA,UAC1E,MAAM,OAAO,KAAK,MAAM,GAAG,QAAQ,OAAO,YAAY,QAAQ,IAAI,EAAE;AAAA,QAAC;AACvE;AAAA,MACF;AACA,UAAI,KAAK,eAAe,UAAU;AAChC,cAAM,UAAU,KAAK,WAAW,CAAC;AACjC,YAAI,CAAC,QAAS,OAAM,WAAW,8BAA8B;AAC7D,cAAM,OAAO,IAAI,YAAY,IAAI,EAAE,WAAW,mBAAmB,OAAO,CAAC,EAAE;AAC3E,eAAO,OAAO,EAAE,OAAO,IAAI,IAAI,SAAS,SAAS,KAAK,GAAG,MAAM,OAAO,KAAK,MAAM,GAAG,OAAO,UAAU,CAAC;AACtG;AAAA,MACF;AACA,YAAM,WAAW,+BAA+B;AAAA,IAClD;AAAA;AAAA,IAGA,KAAK,WAAW;AACd,YAAM,SAAS,UAAU,IAAI;AAC7B,UAAI,KAAK,eAAe,QAAQ;AAC9B,cAAM,UAAU,MAAM,OAAO,IAA2D,gCAAgC;AACxH,eAAO,OAAO,EAAE,SAAS,QAAQ,MAAM,GAAG,MAAM;AAC9C,qBAAW,UAAU,QAAQ,MAAO,QAAO,SAAS,GAAG,OAAO,KAAK,OAAO,EAAE,CAAC,IAAI,OAAO,SAAS,EAAE;AAAA,QACrG,CAAC;AACD;AAAA,MACF;AACA,UAAI,KAAK,eAAe,OAAO;AAC7B,cAAM,OAAO,KAAK,WAAW,CAAC;AAC9B,YAAI,CAAC,KAAM,OAAM,WAAW,yBAAyB;AAErD,cAAM,QAAQ,MAAM,aAAa,aAAa,IAAI,IAAI,SAAS;AAC/D,cAAM,SAAS,MAAM,OAAO,KAA4C,kCAAkC,EAAE,MAAM,MAAM,CAAC;AACzH,eAAO,OAAO,EAAE,MAAM,SAAS,OAAO,QAAQ,GAAG,MAAM,OAAO,KAAK,MAAM,UAAU,IAAI,sBAAsB,OAAO,OAAO,EAAE,CAAC;AAC9H;AAAA,MACF;AACA,YAAM,WAAW,uBAAuB;AAAA,IAC1C;AAAA;AAAA,IAGA,KAAK,gBAAgB;AACnB,YAAM,SAAS,UAAU,IAAI;AAC7B,UAAI,KAAK,eAAe,UAAU,KAAK,eAAe,MAAM;AAC1D,cAAM,UAAU,MAAM,OAAO,IAAqE,kBAAkB;AACpH,eAAO,OAAO,EAAE,cAAc,QAAQ,MAAM,GAAG,MAAM;AACnD,qBAAW,aAAa,QAAQ,MAAO,QAAO,SAAS,GAAG,UAAU,GAAG,OAAO,EAAE,CAAC,IAAI,UAAU,KAAK,OAAO,EAAE,CAAC,IAAI,UAAU,OAAO,EAAE;AAAA,QACvI,CAAC;AACD;AAAA,MACF;AACA,UAAI,KAAK,eAAe,WAAW;AACjC,cAAM,YAAY,KAAK,WAAW,CAAC;AACnC,cAAM,aAAa,OAAO,KAAK,MAAM,eAAe,WAAW,KAAK,MAAM,aAAa;AACvF,YAAI,CAAC,aAAa,CAAC,WAAY,OAAM,WAAW,2DAA2D;AAC3G,cAAM,SAAS,OAAO,KAAK,MAAM,QAAQ,WACrC,KAAK,MAAM,MACX,cAAc,aAAa,GAAG,EAAE,MAAM,EAAE,WAAW,SAAS;AAChE,cAAM,MAAM,MAAM,WAAW,QAAQ,MAAM;AAC3C,cAAM,QAAQ,MAAM,OAAO,KAAqC,YAAY,IAAI,EAAE,sBAAsB;AAAA,UACtG;AAAA,UAAY,YAAY,CAAC,SAAS;AAAA,QACpC,CAAC;AACD,eAAO,OAAO,EAAE,SAAS,MAAM,IAAI,QAAQ,MAAM,QAAQ,UAAU,GAAG,MAAM;AAC1E,iBAAO,KAAK,MAAM,WAAW,YAAY,SAAS,MAAM,EAAE,OAAO,MAAM,MAAM,EAAE;AAC/E,cAAI,MAAM,WAAW,UAAW,QAAO,SAAS,8EAA8E;AAAA,QAChI,CAAC;AACD;AAAA,MACF;AACA,YAAM,WAAW,gCAAgC;AAAA,IACnD;AAAA;AAAA,IAGA,KAAK,WAAW;AACd,YAAM,SAAS,UAAU,IAAI;AAC7B,UAAI,KAAK,eAAe,UAAU;AAChC,cAAM,EAAE,OAAO,IAAI,aAAa,GAAG;AACnC,cAAM,WAAW,MAAM,OAAO,gBAAgB,GAAG;AACjD,cAAM,SAAS,MAAM,OAAO,KAA4D,oBAAoB;AAAA,UAC1G,aAAa,SAAS;AAAA,UAAa,UAAU;AAAA,UAC7C,MAAM,OAAO,KAAK,MAAM,SAAS,WAAW,KAAK,MAAM,OAAO;AAAA,UAC9D,UAAU,KAAK,MAAM,MAAM,UAAU,OAAO,KAAK,MAAM,GAAG,CAAC,IAAI;AAAA,QACjE,CAAC;AACD,eAAO,OAAO,QAAQ,MAAM,OAAO,KAAK,MAAM,oBAAoB,OAAO,GAAG,EAAE,CAAC;AAC/E;AAAA,MACF;AACA,UAAI,KAAK,eAAe,UAAU;AAChC,cAAM,gBAAgB,KAAK,WAAW,CAAC;AACvC,YAAI,CAAC,cAAe,OAAM,WAAW,mCAAmC;AACxE,cAAM,OAAO,IAAI,oBAAoB,aAAa,EAAE;AACpD,eAAO,OAAO,EAAE,eAAe,SAAS,KAAK,GAAG,MAAM,OAAO,KAAK,MAAM,WAAW,aAAa,UAAU,CAAC;AAC3G;AAAA,MACF;AACA,YAAM,WAAW,4BAA4B;AAAA,IAC/C;AAAA;AAAA,IAGA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,UAAU;AACb,YAAM,SAAS,UAAU,IAAI;AAC7B,YAAM,SAAS,KAAK,WAAW,CAAC,KAAK,cAAc,aAAa,GAAG,EAAE,MAAM,EAAE,WAAW,SAAS;AACjG,YAAM,MAAM,MAAM,WAAW,QAAQ,MAAM;AAE3C,UAAI,KAAK,YAAY,WAAW;AAC9B,cAAM,SAAS,MAAM,OAAO,KAA+C,YAAY,IAAI,EAAE,UAAU;AACvG,eAAO;AAAA,UAAO,EAAE,OAAO,IAAI,IAAI,GAAG,OAAO;AAAA,UACvC,MAAM,OAAO,KAAK,MAAM,GAAG,IAAI,IAAI,kBAAkB,OAAO,SAAS,eAAe;AAAA,QAAC;AACvF;AAAA,MACF;AACA,UAAI,KAAK,YAAY,WAAW;AAC9B,cAAM,WAAW,MAAM,OAAO,KAAe,YAAY,IAAI,EAAE,UAAU;AACzE,eAAO;AAAA,UAAO,EAAE,OAAO,IAAI,IAAI,QAAQ,SAAS,OAAO;AAAA,UACrD,MAAM,OAAO,KAAK,MAAM,GAAG,IAAI,IAAI,kDAAkD;AAAA,QAAC;AACxF;AAAA,MACF;AAEA,YAAM,UAAU,OAAO,KAAK,MAAM,YAAY,WAAW,KAAK,MAAM,UAAU;AAC9E,UAAI,CAAC,SAAS;AACZ,cAAM,IAAI,UAAU,gCAAgC,aAAa,IAAI,IAAI,4BAA4B;AAAA,UACnG,aAAa,yBAAyB,IAAI,IAAI;AAAA,UAC9C,SAAS,EAAE,cAAc,aAAa,UAAU,IAAI,KAAK;AAAA,QAC3D,CAAC;AAAA,MACH;AACA,YAAM,YAAY,MAAM,OAAO,IAAc,YAAY,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC;AAC9E,aAAO;AAAA,QAAO,EAAE,OAAO,IAAI,IAAI,aAAa,UAAU,YAAY;AAAA,QAChE,MAAM,OAAO,KAAK,MAAM,GAAG,IAAI,IAAI,0BAA0B,UAAU,WAAW,GAAG;AAAA,MAAC;AACxF;AAAA,IACF;AAAA,IAEA;AACE,cAAQ,OAAO,MAAM,oBAAoB,KAAK,OAAO;AAAA;AAAA,EAAS,KAAK,EAAE;AACrE,cAAQ,KAAK,KAAK,KAAK;AAAA,EAC3B;AACF;AAuBA,eAAe,WAAW,QAAmB,WAAsC;AACjF,MAAI,UAAU,WAAW,MAAM,EAAG,QAAO,OAAO,IAAc,YAAY,SAAS,EAAE;AACrF,QAAM,OAAO,MAAM,OAAO,IAA2B,6BAA6B;AAClF,QAAM,MAAM,KAAK,MAAM,KAAK,CAAC,UAAU,MAAM,SAAS,SAAS;AAC/D,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,UAAU,aAAa,WAAW,SAAS,2BAA2B;AAAA,MAC9E,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,UAAU,QAAgB,MAAiB,UAA0B;AAC5E,SAAO,SAAS,EAAE;AAClB,aAAW,QAAQ,KAAK,MAAO,QAAO,SAAS,KAAK,KAAK,KAAK,OAAO,CAAC,CAAC,IAAI,KAAK,SAAS,OAAO,EAAE,CAAC,IAAI,KAAK,MAAM,EAAE;AACpH,MAAI,KAAK,WAAW,QAAQ,SAAS,GAAG;AACtC,WAAO,SAAS,iBAAiB,KAAK,WAAW,QAAQ,KAAK,IAAI,CAAC,EAAE;AACrE,eAAW,eAAe,KAAK,WAAW,YAAa,QAAO,KAAK,GAAG,WAAW,mCAAmC;AAAA,EACtH;AACA,aAAW,cAAc,KAAK,cAAc;AAC1C,WAAO,SAAS,gBAAgB,WAAW,SAAS,WAAM,WAAW,MAAM,EAAE;AAAA,EAC/E;AACA,SAAO,SAAS,WAAW,KAAK,QAAQ,GAAG,KAAK,0BAA0B,OAAO,KAAK,kBAAkB,KAAK,wBAAwB,KAAK,QAAQ,CAAC,CAAC,KAAK,EAAE;AAC3J,aAAW,WAAW,CAAC,GAAG,UAAU,GAAG,KAAK,QAAQ,EAAG,QAAO,KAAK,OAAO;AAC1E,aAAW,WAAW,KAAK,kBAAmB,QAAO,KAAK,iBAAiB,OAAO,EAAE;AACpF,aAAW,YAAY,KAAK,UAAW,QAAO,KAAK,4BAAuB,SAAS,IAAI,KAAK,SAAS,MAAM,EAAE;AAC7G,SAAO,SAAS,EAAE;AACpB;AAEA,SAAS,WAAW,OAA0B;AAC5C,SAAO,IAAI,UAAU,qBAAqB,UAAU,KAAK,IAAI,EAAE,aAAa,QAAQ,KAAK,GAAG,CAAC;AAC/F;AAEA,SAAS,WAAW,OAAuB;AACzC,QAAM,QAAQ,kBAAkB,KAAK,KAAK;AAC1C,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,OAAO,EAAE,GAAG,KAAM,GAAG,KAAQ,GAAG,MAAW,GAAG,MAAW,EAAE,MAAM,CAAC,CAAE;AAC1E,SAAO,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,MAAM,CAAC,CAAC,IAAI,IAAI,EAAE,YAAY;AACpE;AAEA,SAAS,UAAU,OAAuB;AACxC,QAAM,QAAQ,gBAAgB,KAAK,KAAK;AACxC,MAAI,CAAC,MAAO,QAAO,OAAO,KAAK;AAC/B,SAAO,OAAO,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,MAAM,KAAK;AACrD;AAEA,MAAM,KAAK;",
|
|
6
|
-
"names": ["existsSync", "readFileSync", "writeFileSync", "join", "matches", "sep", "createHash", "readFileSync", "dirname", "join", "join", "readFileSync", "createHash", "readFileSync", "join", "DatabaseSync", "dirname", "join", "fileURLToPath", "DatabaseSync", "join", "dirname", "fileURLToPath", "existsSync", "mkdirSync", "readFileSync", "writeFileSync", "dirname", "join", "resolve", "join", "existsSync", "readFileSync", "writeFileSync", "status"]
|
|
7
|
-
}
|