@agentic-kit/pi 0.17.1 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +5 -7
  2. package/confirm-gate.d.ts +1 -2
  3. package/esm/confirm-gate.d.ts +1 -2
  4. package/esm/host.d.ts +5 -110
  5. package/esm/host.js +5 -18
  6. package/esm/index.d.ts +17 -9
  7. package/esm/index.js +20 -38
  8. package/esm/pi-tool.d.ts +16 -0
  9. package/esm/pi-tool.js +32 -0
  10. package/host.d.ts +5 -110
  11. package/host.js +10 -22
  12. package/index.d.ts +17 -9
  13. package/index.js +47 -62
  14. package/package.json +6 -10
  15. package/pi-tool.d.ts +16 -0
  16. package/pi-tool.js +36 -0
  17. package/app-workspace.d.ts +0 -16
  18. package/app-workspace.js +0 -145
  19. package/context.d.ts +0 -62
  20. package/context.js +0 -282
  21. package/db-probe.d.ts +0 -30
  22. package/db-probe.js +0 -50
  23. package/esm/app-workspace.d.ts +0 -16
  24. package/esm/app-workspace.js +0 -134
  25. package/esm/context.d.ts +0 -62
  26. package/esm/context.js +0 -270
  27. package/esm/db-probe.d.ts +0 -30
  28. package/esm/db-probe.js +0 -47
  29. package/esm/policy/add-policies-to-table.d.ts +0 -26
  30. package/esm/policy/add-policies-to-table.js +0 -66
  31. package/esm/policy/provision-helpers.d.ts +0 -38
  32. package/esm/policy/provision-helpers.js +0 -50
  33. package/esm/provision-database/credential.d.ts +0 -14
  34. package/esm/provision-database/credential.js +0 -19
  35. package/esm/provision-database/env-file.d.ts +0 -37
  36. package/esm/provision-database/env-file.js +0 -85
  37. package/esm/provision-database/manifest.d.ts +0 -10
  38. package/esm/provision-database/manifest.js +0 -48
  39. package/esm/provision-database/pg-fixups.d.ts +0 -16
  40. package/esm/provision-database/pg-fixups.js +0 -132
  41. package/esm/provision-database/preset-match.d.ts +0 -13
  42. package/esm/provision-database/preset-match.js +0 -66
  43. package/esm/provision-database/presets.d.ts +0 -4
  44. package/esm/provision-database/presets.js +0 -13
  45. package/esm/provision-database/request-database.d.ts +0 -61
  46. package/esm/provision-database/request-database.js +0 -130
  47. package/esm/provision-database/resolve.d.ts +0 -27
  48. package/esm/provision-database/resolve.js +0 -49
  49. package/esm/records/meta.d.ts +0 -41
  50. package/esm/records/meta.js +0 -167
  51. package/esm/run-codegen/barrels.d.ts +0 -3
  52. package/esm/run-codegen/barrels.js +0 -48
  53. package/esm/run-codegen/endpoints.d.ts +0 -8
  54. package/esm/run-codegen/endpoints.js +0 -46
  55. package/esm/schema-resolve.d.ts +0 -23
  56. package/esm/schema-resolve.js +0 -69
  57. package/esm/tool-schema.d.ts +0 -3
  58. package/esm/tool-schema.js +0 -10
  59. package/esm/tools/add-policies.d.ts +0 -8
  60. package/esm/tools/add-policies.js +0 -96
  61. package/esm/tools/add-records.d.ts +0 -14
  62. package/esm/tools/add-records.js +0 -124
  63. package/esm/tools/add-relation-schema.d.ts +0 -30
  64. package/esm/tools/add-relation-schema.js +0 -58
  65. package/esm/tools/add-relation.d.ts +0 -10
  66. package/esm/tools/add-relation.js +0 -91
  67. package/esm/tools/create-api-key.d.ts +0 -18
  68. package/esm/tools/create-api-key.js +0 -216
  69. package/esm/tools/describe-schema.d.ts +0 -18
  70. package/esm/tools/describe-schema.js +0 -132
  71. package/esm/tools/manage-entity-types.d.ts +0 -41
  72. package/esm/tools/manage-entity-types.js +0 -203
  73. package/esm/tools/mutations.d.ts +0 -14
  74. package/esm/tools/mutations.js +0 -235
  75. package/esm/tools/provision-blueprint.d.ts +0 -22
  76. package/esm/tools/provision-blueprint.js +0 -111
  77. package/esm/tools/provision-database.d.ts +0 -14
  78. package/esm/tools/provision-database.js +0 -262
  79. package/esm/tools/run-codegen.d.ts +0 -11
  80. package/esm/tools/run-codegen.js +0 -139
  81. package/esm/tools/templates.d.ts +0 -49
  82. package/esm/tools/templates.js +0 -337
  83. package/policy/add-policies-to-table.d.ts +0 -26
  84. package/policy/add-policies-to-table.js +0 -71
  85. package/policy/provision-helpers.d.ts +0 -38
  86. package/policy/provision-helpers.js +0 -58
  87. package/provision-database/credential.d.ts +0 -14
  88. package/provision-database/credential.js +0 -22
  89. package/provision-database/env-file.d.ts +0 -37
  90. package/provision-database/env-file.js +0 -91
  91. package/provision-database/manifest.d.ts +0 -10
  92. package/provision-database/manifest.js +0 -56
  93. package/provision-database/pg-fixups.d.ts +0 -16
  94. package/provision-database/pg-fixups.js +0 -170
  95. package/provision-database/preset-match.d.ts +0 -13
  96. package/provision-database/preset-match.js +0 -70
  97. package/provision-database/presets.d.ts +0 -4
  98. package/provision-database/presets.js +0 -17
  99. package/provision-database/request-database.d.ts +0 -61
  100. package/provision-database/request-database.js +0 -134
  101. package/provision-database/resolve.d.ts +0 -27
  102. package/provision-database/resolve.js +0 -53
  103. package/records/meta.d.ts +0 -41
  104. package/records/meta.js +0 -172
  105. package/run-codegen/barrels.d.ts +0 -3
  106. package/run-codegen/barrels.js +0 -54
  107. package/run-codegen/endpoints.d.ts +0 -8
  108. package/run-codegen/endpoints.js +0 -51
  109. package/schema-resolve.d.ts +0 -23
  110. package/schema-resolve.js +0 -75
  111. package/tool-schema.d.ts +0 -3
  112. package/tool-schema.js +0 -13
  113. package/tools/add-policies.d.ts +0 -8
  114. package/tools/add-policies.js +0 -99
  115. package/tools/add-records.d.ts +0 -14
  116. package/tools/add-records.js +0 -127
  117. package/tools/add-relation-schema.d.ts +0 -30
  118. package/tools/add-relation-schema.js +0 -62
  119. package/tools/add-relation.d.ts +0 -10
  120. package/tools/add-relation.js +0 -94
  121. package/tools/create-api-key.d.ts +0 -18
  122. package/tools/create-api-key.js +0 -222
  123. package/tools/describe-schema.d.ts +0 -18
  124. package/tools/describe-schema.js +0 -135
  125. package/tools/manage-entity-types.d.ts +0 -41
  126. package/tools/manage-entity-types.js +0 -208
  127. package/tools/mutations.d.ts +0 -14
  128. package/tools/mutations.js +0 -238
  129. package/tools/provision-blueprint.d.ts +0 -22
  130. package/tools/provision-blueprint.js +0 -114
  131. package/tools/provision-database.d.ts +0 -14
  132. package/tools/provision-database.js +0 -268
  133. package/tools/run-codegen.d.ts +0 -11
  134. package/tools/run-codegen.js +0 -143
  135. package/tools/templates.d.ts +0 -49
  136. package/tools/templates.js +0 -341
package/esm/context.js DELETED
@@ -1,270 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { api, auth, modules } from '@constructive-io/sdk';
4
- import { parseDotenv } from '12factor-env/dotenv';
5
- import { probeDatabase } from './db-probe';
6
- import { DEFAULT_DATA_TOKEN_SKEW_MS, getHost } from './host';
7
- const DEFAULT_API_ENDPOINT = 'http://api.localhost:3000/graphql';
8
- const DEFAULT_MODULES_ENDPOINT = 'http://modules.localhost:3000/graphql';
9
- // The project values pi needs, and where they come from. A scaffolded project
10
- // folder carries them in `.env` (desktop); a headless host — a container, a
11
- // Job, CI — injects them as real environment variables, so no credential is
12
- // ever written into a git clone the agent could commit. Both lanes produce the
13
- // same record, which is why the resolver takes VALUES, not a directory.
14
- export const CONTEXT_ENV_KEYS = [
15
- 'ACCESS_TOKEN',
16
- 'DATABASE_ID',
17
- 'API_ENDPOINT',
18
- 'MODULES_ENDPOINT',
19
- 'DATABASE_NAME',
20
- 'OWNER_ID',
21
- ];
22
- /** Prefix for injected variables: `ACCESS_TOKEN` is far too generic to claim in
23
- * a shared process environment, `CONSTRUCTIVE_ACCESS_TOKEN` is not. Inside a
24
- * project `.env` the bare names stay readable (and are what the scaffolder
25
- * writes), so both spellings resolve, prefixed winning. */
26
- export const CONTEXT_ENV_PREFIX = 'CONSTRUCTIVE_';
27
- const lookup = (source) => typeof source === 'function' ? source : (name) => source[name];
28
- const readContextValue = (source, key) => {
29
- const get = lookup(source);
30
- const value = get(`${CONTEXT_ENV_PREFIX}${key}`) ?? get(key);
31
- return value ? value : undefined;
32
- };
33
- /** Use the process environment (or any injected record) as the source. */
34
- export function fromEnvironment(environment = process.env) {
35
- return environment;
36
- }
37
- /** Read a project `.env` as the source. The file is authoritative here — the
38
- * key in it belongs to whatever backend wrote it, so it is not merged under
39
- * the ambient environment. Returns null when the file is absent. */
40
- export async function fromEnvFile(cwd) {
41
- try {
42
- return parseDotenv(await readFile(path.join(cwd, '.env'), 'utf8'));
43
- }
44
- catch {
45
- return null;
46
- }
47
- }
48
- /**
49
- * Resolve the project context from injected values, or from a project folder.
50
- *
51
- * Passing a `cwd` string keeps the original behavior (read `<cwd>/.env`) so
52
- * existing hosts — Constructive Desktop, the confirm gate — are unchanged.
53
- * Headless hosts pass values instead: `fromEnvironment()` for a container or
54
- * Job, an explicit record for anything else.
55
- */
56
- export async function resolveProjectContext(input, options = {}) {
57
- const source = typeof input === 'string' ? await fromEnvFile(input) : input;
58
- if (!source) {
59
- return {
60
- context: null,
61
- reason: 'No .env found in the project. Provision a Constructive database first (scaffold the project), then retry.',
62
- code: 'no-env',
63
- };
64
- }
65
- const env = Object.fromEntries(CONTEXT_ENV_KEYS.map((key) => [key, readContextValue(source, key)]));
66
- const accessToken = env.ACCESS_TOKEN;
67
- const databaseId = env.DATABASE_ID;
68
- if (!accessToken || !databaseId) {
69
- return {
70
- context: null,
71
- reason: `Project is not connected to a Constructive database yet (missing ${CONTEXT_ENV_PREFIX}ACCESS_TOKEN/${CONTEXT_ENV_PREFIX}DATABASE_ID in the environment, or ACCESS_TOKEN/DATABASE_ID in .env). Provision the database first, then retry.`,
72
- code: 'missing-credentials',
73
- };
74
- }
75
- // A source-supplied pin wins for the DATA plane (the access key belongs to
76
- // whatever backend wrote it); otherwise fall back to the app's backend-config
77
- // store (environment-aware) so app + harness share one endpoint source.
78
- const host = getHost();
79
- const backend = host.backendConfig();
80
- const apiEndpoint = env.API_ENDPOINT || backend?.apiEndpoint || DEFAULT_API_ENDPOINT;
81
- const modulesEndpoint = env.MODULES_ENDPOINT || backend?.modulesEndpoint || DEFAULT_MODULES_ENDPOINT;
82
- // The whole control plane (binding probe, schema resolution, blueprint/schema
83
- // tools) authenticates with the ACCOUNT bearer: the platform api rejects
84
- // per-database keys, so the project ACCESS_TOKEN can never act on metaschema
85
- // surfaces. The source's key stays in the context for the data plane only.
86
- // The bearer is only ever sent to the app-configured backend — never a
87
- // source-pinned endpoint, which an untrusted cloned project controls.
88
- const controlApiEndpoint = backend?.apiEndpoint || DEFAULT_API_ENDPOINT;
89
- const controlModulesEndpoint = backend?.modulesEndpoint || DEFAULT_MODULES_ENDPOINT;
90
- const account = host.account();
91
- const accountBearer = account?.apiKey ?? account?.accessToken;
92
- if (!accountBearer) {
93
- return {
94
- context: null,
95
- reason: `No usable account credential to reach the Constructive control plane. ${host.signInHint ?? 'Sign in to the app, then retry.'}`,
96
- code: 'missing-credentials',
97
- };
98
- }
99
- const controlHeaders = { Authorization: `Bearer ${accountBearer}` };
100
- const apiClient = api.createClient({ endpoint: controlApiEndpoint, headers: controlHeaders });
101
- // Always probe the bound database — the DATABASE_ID stamp proves it WAS
102
- // bound, never that the database still exists (backend refresh, deletion,
103
- // revoked key). The probe is the single source of binding health.
104
- const probe = await probeDatabase({
105
- endpoint: controlApiEndpoint,
106
- bearer: accountBearer,
107
- databaseId,
108
- signInHint: host.signInHint,
109
- });
110
- if (probe.outcome === 'unreachable') {
111
- return {
112
- context: null,
113
- reason: `Could not reach the Constructive backend at ${controlApiEndpoint} (${probe.detail}). Check it is running, then retry.`,
114
- code: 'backend-unreachable',
115
- };
116
- }
117
- if (probe.outcome === 'missing') {
118
- return {
119
- context: null,
120
- reason: `The bound database (DATABASE_ID=${databaseId}) no longer exists on this backend. Re-provision with provision_database (reprovision: true) — the schema is rebuilt from the project; records do not carry over.`,
121
- code: 'db-missing',
122
- };
123
- }
124
- // Schemas are account-scoped: a live binding under a different account is
125
- // never shown in the Schemas tab or written to by the agent. The project's
126
- // local blueprint survives account changes, so recovery is a reprovision
127
- // under the signed-in account. Owner truth comes from the probe (backend),
128
- // not the source's stamp. Data-plane resolution skips this gate — see
129
- // ResolveOptions.
130
- const sessionUserId = account?.userId;
131
- if (options.plane !== 'data' &&
132
- probe.ownerId &&
133
- sessionUserId &&
134
- probe.ownerId !== sessionUserId) {
135
- return {
136
- context: null,
137
- reason: `The bound database (DATABASE_ID=${databaseId}) was provisioned under a different account than the one signed in. Re-provision with provision_database (reprovision: true) to rebuild it under this account — the schema is rebuilt from the project; records do not carry over.`,
138
- code: 'account-mismatch',
139
- };
140
- }
141
- const databaseName = env.DATABASE_NAME || probe.name || '';
142
- const ownerId = probe.ownerId || env.OWNER_ID || undefined;
143
- const dataEndpoint = databaseName
144
- ? deriveSubdomainEndpoint(apiEndpoint, `api-${databaseName}`)
145
- : '';
146
- const schemaId = await resolveSchemaId(apiClient, databaseId);
147
- if (!schemaId) {
148
- return {
149
- context: null,
150
- reason: 'Could not resolve a schema (app_public/public) for this database. The database may not be fully provisioned yet.',
151
- code: 'schema-unresolved',
152
- };
153
- }
154
- return {
155
- context: {
156
- api: apiClient,
157
- modules: modules.createClient({ endpoint: controlModulesEndpoint, headers: controlHeaders }),
158
- databaseId,
159
- schemaId,
160
- ownerId,
161
- apiEndpoint,
162
- modulesEndpoint,
163
- databaseName,
164
- accessToken,
165
- dataEndpoint,
166
- },
167
- reason: '',
168
- };
169
- }
170
- const needsAuthReason = () => `Not signed in to the app database yet. ${getHost().signInHint ??
171
- 'Sign in from the Sheets tab, or open the Preview and sign in to your app, then try again.'}`;
172
- // Resolve a data-plane token for record operations: serve the broker's active
173
- // account while valid, otherwise harvest the token the user created by signing
174
- // into their app in the Preview and adopt it into the broker. Also returns the
175
- // signed-in user's id (used to scope rows by entityId). Returns a reason (no
176
- // token) when neither source has a valid token — the caller surfaces a sign-in
177
- // prompt.
178
- export async function resolveDataToken(context) {
179
- const host = getHost();
180
- const broker = host.dataAuthBroker;
181
- const active = broker?.getActiveToken(context.databaseId);
182
- if (active)
183
- return { token: active.token, userId: active.userId };
184
- const preview = host.previewToken ? await host.previewToken() : null;
185
- if (!preview)
186
- return { reason: needsAuthReason() };
187
- if (broker?.isInvalidToken(context.databaseId, preview.accessToken)) {
188
- return { reason: needsAuthReason() };
189
- }
190
- const parsed = preview.accessTokenExpiresAt ? Date.parse(preview.accessTokenExpiresAt) : NaN;
191
- const expiresAt = Number.isNaN(parsed) ? Date.now() + 60 * 60 * 1000 : parsed;
192
- const skewMs = host.dataTokenSkewMs ?? DEFAULT_DATA_TOKEN_SKEW_MS;
193
- if (expiresAt <= Date.now() + skewMs)
194
- return { reason: needsAuthReason() };
195
- broker?.adoptToken(context.databaseId, {
196
- userId: preview.userId,
197
- token: preview.accessToken,
198
- expiresAt,
199
- origin: 'harvest',
200
- });
201
- return { token: preview.accessToken, userId: preview.userId };
202
- }
203
- // Per-DB endpoints are deterministic on the provisioning subdomain (= DATABASE_NAME):
204
- // each plane swaps the first host label of its control-plane endpoint
205
- // (e.g. api.localhost -> api-myapp.localhost, auth.localhost -> auth-myapp.localhost).
206
- export function deriveSubdomainEndpoint(baseEndpoint, firstLabel) {
207
- try {
208
- const url = new URL(baseEndpoint);
209
- const labels = url.hostname.split('.');
210
- labels[0] = firstLabel;
211
- url.hostname = labels.join('.');
212
- return url.toString();
213
- }
214
- catch {
215
- return '';
216
- }
217
- }
218
- // The backend hierarchy is org -> db -> tables; the owning org is the auth-plane
219
- // user the database's ownerId points at. Best-effort display lookup only —
220
- // binding health never depends on it. Authenticates with the account bearer
221
- // against the app-configured auth endpoint (the platform rejects per-database
222
- // keys, and the bearer never goes to a .env-pinned endpoint).
223
- export async function resolveOrgName(ownerId) {
224
- try {
225
- const host = getHost();
226
- const account = host.account();
227
- const bearer = account?.apiKey ?? account?.accessToken;
228
- if (!bearer)
229
- return undefined;
230
- const backend = host.backendConfig();
231
- const authEndpoint = deriveSubdomainEndpoint(backend?.apiEndpoint || DEFAULT_API_ENDPOINT, 'auth');
232
- if (!authEndpoint)
233
- return undefined;
234
- const client = auth.createClient({
235
- endpoint: authEndpoint,
236
- headers: { Authorization: `Bearer ${bearer}` },
237
- });
238
- const result = await client.user
239
- .findMany({
240
- select: { displayName: true, username: true },
241
- where: { id: { equalTo: ownerId } },
242
- })
243
- .execute();
244
- if (!result.ok)
245
- return undefined;
246
- const node = (result.data.users?.nodes ?? [])[0];
247
- return node?.displayName || node?.username || undefined;
248
- }
249
- catch {
250
- return undefined;
251
- }
252
- }
253
- async function resolveSchemaId(apiClient, databaseId) {
254
- const result = await apiClient.schema
255
- .findMany({
256
- select: { id: true, name: true },
257
- where: { databaseId: { equalTo: databaseId } },
258
- })
259
- .execute();
260
- if (!result.ok)
261
- return undefined;
262
- const nodes = (result.data.schemas?.nodes ?? []).filter((s) => Boolean(s && s.id));
263
- const appPublic = nodes.find((s) => s.name === 'app_public');
264
- if (appPublic)
265
- return appPublic.id;
266
- const pub = nodes.find((s) => s.name === 'public');
267
- if (pub)
268
- return pub.id;
269
- return nodes[0]?.id;
270
- }
package/esm/db-probe.d.ts DELETED
@@ -1,30 +0,0 @@
1
- export type DatabaseProbe = {
2
- outcome: 'found';
3
- name?: string;
4
- ownerId?: string;
5
- } | {
6
- outcome: 'missing';
7
- } | {
8
- outcome: 'unreachable';
9
- detail: string;
10
- };
11
- export type ProbeExecutor = {
12
- execute<T>(document: string, variables?: Record<string, unknown>): Promise<{
13
- ok: true;
14
- data: T;
15
- errors: undefined;
16
- } | {
17
- ok: false;
18
- data: null;
19
- errors: {
20
- message: string;
21
- }[];
22
- }>;
23
- };
24
- export declare function probeDatabase(args: {
25
- endpoint: string;
26
- bearer: string;
27
- databaseId: string;
28
- executor?: ProbeExecutor;
29
- signInHint?: string;
30
- }): Promise<DatabaseProbe>;
package/esm/db-probe.js DELETED
@@ -1,47 +0,0 @@
1
- import { api } from '@constructive-io/sdk';
2
- const AUTH_ERROR_RE = /unauthenticated|unauthorized|permission denied|jwt|http 401|http 403/i;
3
- // Liveness probe for a bound database. The split drives recovery: 'missing'
4
- // means the backend answered and the binding is dead (database deleted,
5
- // backend refreshed) — recovery is reprovision. 'unreachable' means no usable
6
- // answer (backend down, network error, rejected credential) — recovery is
7
- // retry/re-sign-in, never reprovision.
8
- //
9
- // The probe authenticates with the ACCOUNT bearer, not the project's .env key:
10
- // the platform api rejects per-database keys, and the singular `database(id:)`
11
- // field is gone from the current contract, so the lookup goes through the
12
- // `databases` collection filter via the SDK's raw FetchAdapter.
13
- export async function probeDatabase(args) {
14
- const executor = args.executor ??
15
- new api.FetchAdapter(args.endpoint, { Authorization: `Bearer ${args.bearer}` });
16
- const document = `
17
- query ProbeDatabase {
18
- databases(where: { id: { equalTo: ${JSON.stringify(args.databaseId)} } }) {
19
- nodes { id name ownerId }
20
- }
21
- }`;
22
- try {
23
- const result = await executor.execute(document);
24
- if (result.ok) {
25
- const node = result.data.databases?.nodes?.[0];
26
- if (!node)
27
- return { outcome: 'missing' };
28
- return { outcome: 'found', name: node.name ?? undefined, ownerId: node.ownerId ?? undefined };
29
- }
30
- const detail = result.errors?.[0]?.message ?? 'unknown error';
31
- // A 5xx is the backend failing, not an answer about this database. An auth
32
- // rejection is a dead ACCOUNT credential — never evidence the binding is
33
- // dead, so it must not push toward reprovision.
34
- if (/^HTTP 5\d\d/i.test(detail))
35
- return { outcome: 'unreachable', detail };
36
- if (AUTH_ERROR_RE.test(detail)) {
37
- return {
38
- outcome: 'unreachable',
39
- detail: `${detail} — the account credential was rejected. ${args.signInHint ?? 'Sign in again, then retry.'}`,
40
- };
41
- }
42
- return { outcome: 'missing' };
43
- }
44
- catch (err) {
45
- return { outcome: 'unreachable', detail: err instanceof Error ? err.message : String(err) };
46
- }
47
- }
@@ -1,26 +0,0 @@
1
- import { type PolicyProvisioningCategory } from '@agentic-kit/harness';
2
- import type { ModulesClient } from '../context';
3
- import { type CrudOperation, type CrudPolicyConfigs } from './provision-helpers';
4
- export interface AddPoliciesToTablePolicyEntry {
5
- policyType: string;
6
- dataNodeType?: string;
7
- nodeData?: Record<string, unknown>;
8
- sharedPolicyData: Record<string, unknown>;
9
- operations: CrudPolicyConfigs;
10
- enabledOperations?: CrudOperation[];
11
- }
12
- export interface AddPoliciesToTableInput {
13
- client: ModulesClient;
14
- databaseId: string;
15
- schemaId: string;
16
- tableId: string;
17
- policies: AddPoliciesToTablePolicyEntry[];
18
- }
19
- export declare class UnsupportedPolicyCategoryError extends Error {
20
- readonly policyType: string;
21
- readonly category: PolicyProvisioningCategory;
22
- constructor(policyType: string, category: PolicyProvisioningCategory);
23
- }
24
- export declare function addPoliciesToExistingTable(input: AddPoliciesToTableInput): Promise<{
25
- success: true;
26
- }>;
@@ -1,66 +0,0 @@
1
- import { getPolicyCategory } from '@agentic-kit/harness';
2
- import { buildGrants, buildPolicyEntry, CRUD_OPERATIONS, groupOperationsByConfig, } from './provision-helpers';
3
- const SUPPORTED_CATEGORIES = new Set([
4
- 'has-module',
5
- 'no-fields',
6
- 'needs-table',
7
- ]);
8
- export class UnsupportedPolicyCategoryError extends Error {
9
- policyType;
10
- category;
11
- constructor(policyType, category) {
12
- super(`Policy type "${policyType}" has category "${category}" which requires column creation or composite handling — use the policies UI instead.`);
13
- this.policyType = policyType;
14
- this.category = category;
15
- this.name = 'UnsupportedPolicyCategoryError';
16
- }
17
- }
18
- export async function addPoliciesToExistingTable(input) {
19
- const { client } = input;
20
- for (const entry of input.policies) {
21
- const category = getPolicyCategory(entry.policyType);
22
- if (!SUPPORTED_CATEGORIES.has(category)) {
23
- throw new UnsupportedPolicyCategoryError(entry.policyType, category);
24
- }
25
- }
26
- const policiesArray = [];
27
- const nodesByType = new Map();
28
- for (const entry of input.policies) {
29
- const groups = groupOperationsByConfig(entry.enabledOperations ?? CRUD_OPERATIONS, entry.operations);
30
- for (const group of groups) {
31
- const ops = group.privileges.join('_');
32
- const rand = Math.random().toString(36).slice(2, 8);
33
- policiesArray.push(buildPolicyEntry(entry.policyType, {
34
- privileges: group.privileges,
35
- policy_role: group.roleName,
36
- permissive: group.isPermissive,
37
- data: entry.sharedPolicyData,
38
- policy_name: `${ops}_${rand}`,
39
- }));
40
- }
41
- if (entry.dataNodeType && !nodesByType.has(entry.dataNodeType)) {
42
- const nodeData = entry.nodeData ?? {};
43
- nodesByType.set(entry.dataNodeType, {
44
- $type: entry.dataNodeType,
45
- ...(Object.keys(nodeData).length > 0 ? { data: nodeData } : {}),
46
- });
47
- }
48
- }
49
- const provisionInput = {
50
- databaseId: input.databaseId,
51
- schemaId: input.schemaId,
52
- tableId: input.tableId,
53
- grants: buildGrants(),
54
- policies: policiesArray,
55
- };
56
- if (nodesByType.size > 0) {
57
- provisionInput.nodes = [...nodesByType.values()];
58
- }
59
- await client.secureTableProvision
60
- .create({
61
- data: provisionInput,
62
- select: { id: true, tableId: true, tableName: true },
63
- })
64
- .unwrap();
65
- return { success: true };
66
- }
@@ -1,38 +0,0 @@
1
- export type CrudOperation = 'create' | 'read' | 'update' | 'delete';
2
- export interface OperationPolicyConfig {
3
- roleName: string;
4
- isPermissive: boolean;
5
- policyData: Record<string, unknown>;
6
- isCustomized: boolean;
7
- }
8
- export interface CrudPolicyConfigs {
9
- create: OperationPolicyConfig;
10
- read: OperationPolicyConfig;
11
- update: OperationPolicyConfig;
12
- delete: OperationPolicyConfig;
13
- }
14
- export declare const CRUD_OPERATIONS: CrudOperation[];
15
- export type GrantPrivilege = [string, string | string[]];
16
- export interface GrantEntry {
17
- roles: string[];
18
- privileges: GrantPrivilege[];
19
- }
20
- export interface PolicyEntry {
21
- $type: string;
22
- data?: Record<string, unknown>;
23
- privileges?: string[];
24
- policy_role?: string;
25
- permissive?: boolean;
26
- policy_name?: string;
27
- }
28
- export declare const ALL_CRUD_PRIVILEGES: GrantPrivilege[];
29
- export declare function crudOpToPrivilege(op: CrudOperation): string;
30
- export declare function crudOpsToPrivileges(ops: readonly CrudOperation[]): string[];
31
- export declare function buildGrants(roles?: string[], privileges?: GrantPrivilege[]): GrantEntry[];
32
- export declare function buildPolicyEntry(type: string, opts?: Partial<Omit<PolicyEntry, '$type'>>): PolicyEntry;
33
- export interface OperationGroup {
34
- roleName: string;
35
- isPermissive: boolean;
36
- privileges: string[];
37
- }
38
- export declare function groupOperationsByConfig(enabledOperations: readonly CrudOperation[], operations: CrudPolicyConfigs): OperationGroup[];
@@ -1,50 +0,0 @@
1
- export const CRUD_OPERATIONS = ['create', 'read', 'update', 'delete'];
2
- export const ALL_CRUD_PRIVILEGES = [
3
- ['select', '*'],
4
- ['insert', '*'],
5
- ['update', '*'],
6
- ['delete', '*'],
7
- ];
8
- const CRUD_OP_TO_LOWER_PRIVILEGE = {
9
- create: 'insert',
10
- read: 'select',
11
- update: 'update',
12
- delete: 'delete',
13
- };
14
- export function crudOpToPrivilege(op) {
15
- return CRUD_OP_TO_LOWER_PRIVILEGE[op];
16
- }
17
- export function crudOpsToPrivileges(ops) {
18
- return ops.map(crudOpToPrivilege);
19
- }
20
- export function buildGrants(roles = ['authenticated'], privileges = ALL_CRUD_PRIVILEGES) {
21
- return [{ roles, privileges }];
22
- }
23
- export function buildPolicyEntry(type, opts = {}) {
24
- const entry = { $type: type };
25
- if (opts.data && Object.keys(opts.data).length > 0)
26
- entry.data = opts.data;
27
- if (opts.privileges && opts.privileges.length > 0)
28
- entry.privileges = opts.privileges;
29
- if (opts.policy_role)
30
- entry.policy_role = opts.policy_role;
31
- if (opts.permissive !== undefined)
32
- entry.permissive = opts.permissive;
33
- if (opts.policy_name)
34
- entry.policy_name = opts.policy_name;
35
- return entry;
36
- }
37
- export function groupOperationsByConfig(enabledOperations, operations) {
38
- const groupMap = new Map();
39
- for (const op of enabledOperations) {
40
- const config = operations[op];
41
- const key = `${config.roleName}:${config.isPermissive}`;
42
- let group = groupMap.get(key);
43
- if (!group) {
44
- group = { roleName: config.roleName, isPermissive: config.isPermissive, privileges: [] };
45
- groupMap.set(key, group);
46
- }
47
- group.privileges.push(...crudOpsToPrivileges([op]));
48
- }
49
- return [...groupMap.values()];
50
- }
@@ -1,14 +0,0 @@
1
- export type AccountCredential = {
2
- userId: string;
3
- accessToken: string;
4
- apiKey?: string;
5
- };
6
- export type ProvisionCredential = {
7
- mode: 'account';
8
- ownerId: string;
9
- bearer: string;
10
- } | {
11
- mode: 'error';
12
- reason: string;
13
- };
14
- export declare function selectProvisionCredential(account: AccountCredential | null | undefined, signInHint?: string): ProvisionCredential;
@@ -1,19 +0,0 @@
1
- // Pure: decide which credential authenticates a project's provisioning. Under the
2
- // account gate a signed-in session is guaranteed, so every new project provisions
3
- // UNDER the account (account-owned + enumerable): the database's owner_id is the
4
- // account's own userId, and the account's API key (falling back to its session
5
- // token) is the bearer that authenticates the provision mutation and lands in the
6
- // project .env as ACCESS_TOKEN (refreshed on every provision run, including the
7
- // skip path). A signed-in session with no usable bearer (keychain locked) is an
8
- // explicit error, never a silent throwaway owner.
9
- // Side-effect-free so the branch is unit-testable without runtime/network.
10
- export function selectProvisionCredential(account, signInHint) {
11
- const bearer = account?.apiKey ?? account?.accessToken;
12
- if (account?.userId && bearer) {
13
- return { mode: 'account', ownerId: account.userId, bearer };
14
- }
15
- return {
16
- mode: 'error',
17
- reason: `No usable account credential. ${signInHint ?? 'Sign in (or unlock your session) before provisioning a database.'}`,
18
- };
19
- }
@@ -1,37 +0,0 @@
1
- export type EnvVars = Record<string, string>;
2
- /**
3
- * Upsert `vars` into the existing `.env` `content`. Existing keys are replaced
4
- * in place (preserving surrounding lines/comments); new keys are appended.
5
- * Returns the new file body, trailing-newline-terminated.
6
- */
7
- export declare function mergeEnv(content: string, vars: EnvVars): string;
8
- /** The credential keys provision_database owns in a project's .env. */
9
- export declare const PROVISION_ENV_KEYS: readonly ["DATABASE_ID", "DATABASE_NAME", "OWNER_ID", "ACCESS_TOKEN", "PGDATABASE", "API_ENDPOINT", "MODULES_ENDPOINT"];
10
- /**
11
- * Keys archived as comments on reprovision — the binding identity: which
12
- * database, under whom, on which backend. ACCESS_TOKEN is the account login
13
- * token, identical across bindings, so it is overwritten in place rather than
14
- * archived.
15
- */
16
- export declare const ARCHIVED_BINDING_KEYS: readonly ["DATABASE_ID", "DATABASE_NAME", "OWNER_ID", "PGDATABASE", "API_ENDPOINT", "MODULES_ENDPOINT"];
17
- /**
18
- * Build the provisioning credential block written to a project's .env. The
19
- * endpoint pins record which backend owns this binding — outside-app consumers
20
- * (verify-feature.sh, skill scripts, dev-server shells) resolve them instead of
21
- * falling back to localhost defaults when the app targets a hosted backend.
22
- */
23
- export declare function provisionEnvVars(args: {
24
- databaseId: string;
25
- databaseName: string;
26
- ownerId: string;
27
- accessToken: string;
28
- apiEndpoint: string;
29
- modulesEndpoint: string;
30
- }): EnvVars;
31
- /**
32
- * Comment out the given keys in place (`# archived <date>: KEY=value`) so a
33
- * reprovision keeps the previous binding readable in the file instead of
34
- * silently overwriting it. Every other line — including existing comments and
35
- * already-archived entries — passes through untouched.
36
- */
37
- export declare function archiveBindingKeys(content: string, keys: readonly string[], archivedOn: string): string;