@agentic-kit/pi 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/context.d.ts +24 -1
- package/context.js +58 -29
- package/esm/context.d.ts +24 -1
- package/esm/context.js +55 -29
- package/esm/index.d.ts +1 -1
- package/esm/index.js +1 -1
- package/index.d.ts +1 -1
- package/index.js +5 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -38,6 +38,36 @@ const dbTools = createDbTools({
|
|
|
38
38
|
pi.use(dbTools);
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
## Project context
|
|
42
|
+
|
|
43
|
+
The tools need a bound database: an access key, a database id, and optionally endpoint/name pins. Where those values come from is the host's choice — `resolveProjectContext` takes values, not a directory:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import { fromEnvironment, resolveProjectContext } from '@agentic-kit/pi';
|
|
47
|
+
|
|
48
|
+
// headless: container, Job, CI — nothing on disk, nothing to commit
|
|
49
|
+
await resolveProjectContext(fromEnvironment());
|
|
50
|
+
|
|
51
|
+
// local project: read <cwd>/.env (what Desktop and the CLI do)
|
|
52
|
+
await resolveProjectContext(cwd);
|
|
53
|
+
|
|
54
|
+
// anything else: a record, or a lookup function into a secret store
|
|
55
|
+
await resolveProjectContext((name) => vault.get(name));
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Injected variables carry a `CONSTRUCTIVE_` prefix; the bare names are also accepted (that is what the scaffolder writes into a project `.env`), with the prefixed spelling winning:
|
|
59
|
+
|
|
60
|
+
| Variable | Required | Purpose |
|
|
61
|
+
| --- | --- | --- |
|
|
62
|
+
| `CONSTRUCTIVE_ACCESS_TOKEN` | yes | project data-plane key |
|
|
63
|
+
| `CONSTRUCTIVE_DATABASE_ID` | yes | bound database |
|
|
64
|
+
| `CONSTRUCTIVE_DATABASE_NAME` | no | derives the per-db data endpoint |
|
|
65
|
+
| `CONSTRUCTIVE_API_ENDPOINT` | no | data-plane api pin |
|
|
66
|
+
| `CONSTRUCTIVE_MODULES_ENDPOINT` | no | data-plane modules pin |
|
|
67
|
+
| `CONSTRUCTIVE_OWNER_ID` | no | owner fallback when the probe omits it |
|
|
68
|
+
|
|
69
|
+
Endpoint pins from the source apply to the **data plane only**. The control plane (binding probe, schema resolution, blueprint/schema tools) always uses the host's `backendConfig()` and the account bearer, so an untrusted cloned project cannot redirect it.
|
|
70
|
+
|
|
41
71
|
## Provisioning
|
|
42
72
|
|
|
43
73
|
`provision_database` requests a database through the `requestDatabase` mutation on the api endpoint. When the requested module set matches a cataloged preset, the backend claims a warm pre-baked database in seconds. Otherwise a background job provisions the database cold. The tool polls the provision ticket on the modules endpoint until the database and its owner bootstrap are complete. Then it writes the credentials to the project `.env` and returns.
|
package/context.d.ts
CHANGED
|
@@ -19,6 +19,21 @@ export type ResolveResult = {
|
|
|
19
19
|
reason: string;
|
|
20
20
|
code?: ProjectContextFailureCode;
|
|
21
21
|
};
|
|
22
|
+
export declare const CONTEXT_ENV_KEYS: readonly ["ACCESS_TOKEN", "DATABASE_ID", "API_ENDPOINT", "MODULES_ENDPOINT", "DATABASE_NAME", "OWNER_ID"];
|
|
23
|
+
export type ContextEnvKey = (typeof CONTEXT_ENV_KEYS)[number];
|
|
24
|
+
/** Prefix for injected variables: `ACCESS_TOKEN` is far too generic to claim in
|
|
25
|
+
* a shared process environment, `CONSTRUCTIVE_ACCESS_TOKEN` is not. Inside a
|
|
26
|
+
* project `.env` the bare names stay readable (and are what the scaffolder
|
|
27
|
+
* writes), so both spellings resolve, prefixed winning. */
|
|
28
|
+
export declare const CONTEXT_ENV_PREFIX = "CONSTRUCTIVE_";
|
|
29
|
+
/** Values keyed by name, or a lookup function (a host with a secret store). */
|
|
30
|
+
export type ContextSource = Record<string, string | undefined> | ((name: string) => string | undefined);
|
|
31
|
+
/** Use the process environment (or any injected record) as the source. */
|
|
32
|
+
export declare function fromEnvironment(environment?: Record<string, string | undefined>): ContextSource;
|
|
33
|
+
/** Read a project `.env` as the source. The file is authoritative here — the
|
|
34
|
+
* key in it belongs to whatever backend wrote it, so it is not merged under
|
|
35
|
+
* the ambient environment. Returns null when the file is absent. */
|
|
36
|
+
export declare function fromEnvFile(cwd: string): Promise<ContextSource | null>;
|
|
22
37
|
export type ResolveOptions = {
|
|
23
38
|
/** Which plane the context is for. Ownership gates the control plane only:
|
|
24
39
|
* schema editing and provisioning act under the signed-in account, so a
|
|
@@ -28,7 +43,15 @@ export type ResolveOptions = {
|
|
|
28
43
|
* the app itself does in the Preview. */
|
|
29
44
|
plane?: 'control' | 'data';
|
|
30
45
|
};
|
|
31
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the project context from injected values, or from a project folder.
|
|
48
|
+
*
|
|
49
|
+
* Passing a `cwd` string keeps the original behavior (read `<cwd>/.env`) so
|
|
50
|
+
* existing hosts — Constructive Desktop, the confirm gate — are unchanged.
|
|
51
|
+
* Headless hosts pass values instead: `fromEnvironment()` for a container or
|
|
52
|
+
* Job, an explicit record for anything else.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveProjectContext(input: string | ContextSource, options?: ResolveOptions): Promise<ResolveResult>;
|
|
32
55
|
export type DataTokenResult = {
|
|
33
56
|
token?: string;
|
|
34
57
|
userId?: string;
|
package/context.js
CHANGED
|
@@ -3,6 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.CONTEXT_ENV_PREFIX = exports.CONTEXT_ENV_KEYS = void 0;
|
|
7
|
+
exports.fromEnvironment = fromEnvironment;
|
|
8
|
+
exports.fromEnvFile = fromEnvFile;
|
|
6
9
|
exports.resolveProjectContext = resolveProjectContext;
|
|
7
10
|
exports.resolveDataToken = resolveDataToken;
|
|
8
11
|
exports.deriveSubdomainEndpoint = deriveSubdomainEndpoint;
|
|
@@ -10,52 +13,78 @@ exports.resolveOrgName = resolveOrgName;
|
|
|
10
13
|
const promises_1 = require("node:fs/promises");
|
|
11
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
12
15
|
const sdk_1 = require("@constructive-io/sdk");
|
|
16
|
+
const dotenv_1 = require("12factor-env/dotenv");
|
|
13
17
|
const db_probe_1 = require("./db-probe");
|
|
14
18
|
const host_1 = require("./host");
|
|
15
19
|
const DEFAULT_API_ENDPOINT = 'http://api.localhost:3000/graphql';
|
|
16
20
|
const DEFAULT_MODULES_ENDPOINT = 'http://modules.localhost:3000/graphql';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
// The project values pi needs, and where they come from. A scaffolded project
|
|
22
|
+
// folder carries them in `.env` (desktop); a headless host — a container, a
|
|
23
|
+
// Job, CI — injects them as real environment variables, so no credential is
|
|
24
|
+
// ever written into a git clone the agent could commit. Both lanes produce the
|
|
25
|
+
// same record, which is why the resolver takes VALUES, not a directory.
|
|
26
|
+
exports.CONTEXT_ENV_KEYS = [
|
|
27
|
+
'ACCESS_TOKEN',
|
|
28
|
+
'DATABASE_ID',
|
|
29
|
+
'API_ENDPOINT',
|
|
30
|
+
'MODULES_ENDPOINT',
|
|
31
|
+
'DATABASE_NAME',
|
|
32
|
+
'OWNER_ID',
|
|
33
|
+
];
|
|
34
|
+
/** Prefix for injected variables: `ACCESS_TOKEN` is far too generic to claim in
|
|
35
|
+
* a shared process environment, `CONSTRUCTIVE_ACCESS_TOKEN` is not. Inside a
|
|
36
|
+
* project `.env` the bare names stay readable (and are what the scaffolder
|
|
37
|
+
* writes), so both spellings resolve, prefixed winning. */
|
|
38
|
+
exports.CONTEXT_ENV_PREFIX = 'CONSTRUCTIVE_';
|
|
39
|
+
const lookup = (source) => typeof source === 'function' ? source : (name) => source[name];
|
|
40
|
+
const readContextValue = (source, key) => {
|
|
41
|
+
const get = lookup(source);
|
|
42
|
+
const value = get(`${exports.CONTEXT_ENV_PREFIX}${key}`) ?? get(key);
|
|
43
|
+
return value ? value : undefined;
|
|
44
|
+
};
|
|
45
|
+
/** Use the process environment (or any injected record) as the source. */
|
|
46
|
+
function fromEnvironment(environment = process.env) {
|
|
47
|
+
return environment;
|
|
35
48
|
}
|
|
36
|
-
|
|
37
|
-
|
|
49
|
+
/** Read a project `.env` as the source. The file is authoritative here — the
|
|
50
|
+
* key in it belongs to whatever backend wrote it, so it is not merged under
|
|
51
|
+
* the ambient environment. Returns null when the file is absent. */
|
|
52
|
+
async function fromEnvFile(cwd) {
|
|
38
53
|
try {
|
|
39
|
-
|
|
54
|
+
return (0, dotenv_1.parseDotenv)(await (0, promises_1.readFile)(node_path_1.default.join(cwd, '.env'), 'utf8'));
|
|
40
55
|
}
|
|
41
56
|
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the project context from injected values, or from a project folder.
|
|
62
|
+
*
|
|
63
|
+
* Passing a `cwd` string keeps the original behavior (read `<cwd>/.env`) so
|
|
64
|
+
* existing hosts — Constructive Desktop, the confirm gate — are unchanged.
|
|
65
|
+
* Headless hosts pass values instead: `fromEnvironment()` for a container or
|
|
66
|
+
* Job, an explicit record for anything else.
|
|
67
|
+
*/
|
|
68
|
+
async function resolveProjectContext(input, options = {}) {
|
|
69
|
+
const source = typeof input === 'string' ? await fromEnvFile(input) : input;
|
|
70
|
+
if (!source) {
|
|
42
71
|
return {
|
|
43
72
|
context: null,
|
|
44
73
|
reason: 'No .env found in the project. Provision a Constructive database first (scaffold the project), then retry.',
|
|
45
74
|
code: 'no-env',
|
|
46
75
|
};
|
|
47
76
|
}
|
|
48
|
-
const env =
|
|
77
|
+
const env = Object.fromEntries(exports.CONTEXT_ENV_KEYS.map((key) => [key, readContextValue(source, key)]));
|
|
49
78
|
const accessToken = env.ACCESS_TOKEN;
|
|
50
79
|
const databaseId = env.DATABASE_ID;
|
|
51
80
|
if (!accessToken || !databaseId) {
|
|
52
81
|
return {
|
|
53
82
|
context: null,
|
|
54
|
-
reason:
|
|
83
|
+
reason: `Project is not connected to a Constructive database yet (missing ${exports.CONTEXT_ENV_PREFIX}ACCESS_TOKEN/${exports.CONTEXT_ENV_PREFIX}DATABASE_ID in the environment, or ACCESS_TOKEN/DATABASE_ID in .env). Provision the database first, then retry.`,
|
|
55
84
|
code: 'missing-credentials',
|
|
56
85
|
};
|
|
57
86
|
}
|
|
58
|
-
// A
|
|
87
|
+
// A source-supplied pin wins for the DATA plane (the access key belongs to
|
|
59
88
|
// whatever backend wrote it); otherwise fall back to the app's backend-config
|
|
60
89
|
// store (environment-aware) so app + harness share one endpoint source.
|
|
61
90
|
const host = (0, host_1.getHost)();
|
|
@@ -65,9 +94,9 @@ async function resolveProjectContext(cwd, options = {}) {
|
|
|
65
94
|
// The whole control plane (binding probe, schema resolution, blueprint/schema
|
|
66
95
|
// tools) authenticates with the ACCOUNT bearer: the platform api rejects
|
|
67
96
|
// per-database keys, so the project ACCESS_TOKEN can never act on metaschema
|
|
68
|
-
// surfaces. The
|
|
69
|
-
// bearer is only ever sent to the app-configured backend — never a
|
|
70
|
-
//
|
|
97
|
+
// surfaces. The source's key stays in the context for the data plane only.
|
|
98
|
+
// The bearer is only ever sent to the app-configured backend — never a
|
|
99
|
+
// source-pinned endpoint, which an untrusted cloned project controls.
|
|
71
100
|
const controlApiEndpoint = backend?.apiEndpoint || DEFAULT_API_ENDPOINT;
|
|
72
101
|
const controlModulesEndpoint = backend?.modulesEndpoint || DEFAULT_MODULES_ENDPOINT;
|
|
73
102
|
const account = host.account();
|
|
@@ -81,7 +110,7 @@ async function resolveProjectContext(cwd, options = {}) {
|
|
|
81
110
|
}
|
|
82
111
|
const controlHeaders = { Authorization: `Bearer ${accountBearer}` };
|
|
83
112
|
const apiClient = sdk_1.api.createClient({ endpoint: controlApiEndpoint, headers: controlHeaders });
|
|
84
|
-
// Always probe the bound database — the
|
|
113
|
+
// Always probe the bound database — the DATABASE_ID stamp proves it WAS
|
|
85
114
|
// bound, never that the database still exists (backend refresh, deletion,
|
|
86
115
|
// revoked key). The probe is the single source of binding health.
|
|
87
116
|
const probe = await (0, db_probe_1.probeDatabase)({
|
|
@@ -108,7 +137,7 @@ async function resolveProjectContext(cwd, options = {}) {
|
|
|
108
137
|
// never shown in the Schemas tab or written to by the agent. The project's
|
|
109
138
|
// local blueprint survives account changes, so recovery is a reprovision
|
|
110
139
|
// under the signed-in account. Owner truth comes from the probe (backend),
|
|
111
|
-
// not the
|
|
140
|
+
// not the source's stamp. Data-plane resolution skips this gate — see
|
|
112
141
|
// ResolveOptions.
|
|
113
142
|
const sessionUserId = account?.userId;
|
|
114
143
|
if (options.plane !== 'data' &&
|
package/esm/context.d.ts
CHANGED
|
@@ -19,6 +19,21 @@ export type ResolveResult = {
|
|
|
19
19
|
reason: string;
|
|
20
20
|
code?: ProjectContextFailureCode;
|
|
21
21
|
};
|
|
22
|
+
export declare const CONTEXT_ENV_KEYS: readonly ["ACCESS_TOKEN", "DATABASE_ID", "API_ENDPOINT", "MODULES_ENDPOINT", "DATABASE_NAME", "OWNER_ID"];
|
|
23
|
+
export type ContextEnvKey = (typeof CONTEXT_ENV_KEYS)[number];
|
|
24
|
+
/** Prefix for injected variables: `ACCESS_TOKEN` is far too generic to claim in
|
|
25
|
+
* a shared process environment, `CONSTRUCTIVE_ACCESS_TOKEN` is not. Inside a
|
|
26
|
+
* project `.env` the bare names stay readable (and are what the scaffolder
|
|
27
|
+
* writes), so both spellings resolve, prefixed winning. */
|
|
28
|
+
export declare const CONTEXT_ENV_PREFIX = "CONSTRUCTIVE_";
|
|
29
|
+
/** Values keyed by name, or a lookup function (a host with a secret store). */
|
|
30
|
+
export type ContextSource = Record<string, string | undefined> | ((name: string) => string | undefined);
|
|
31
|
+
/** Use the process environment (or any injected record) as the source. */
|
|
32
|
+
export declare function fromEnvironment(environment?: Record<string, string | undefined>): ContextSource;
|
|
33
|
+
/** Read a project `.env` as the source. The file is authoritative here — the
|
|
34
|
+
* key in it belongs to whatever backend wrote it, so it is not merged under
|
|
35
|
+
* the ambient environment. Returns null when the file is absent. */
|
|
36
|
+
export declare function fromEnvFile(cwd: string): Promise<ContextSource | null>;
|
|
22
37
|
export type ResolveOptions = {
|
|
23
38
|
/** Which plane the context is for. Ownership gates the control plane only:
|
|
24
39
|
* schema editing and provisioning act under the signed-in account, so a
|
|
@@ -28,7 +43,15 @@ export type ResolveOptions = {
|
|
|
28
43
|
* the app itself does in the Preview. */
|
|
29
44
|
plane?: 'control' | 'data';
|
|
30
45
|
};
|
|
31
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Resolve the project context from injected values, or from a project folder.
|
|
48
|
+
*
|
|
49
|
+
* Passing a `cwd` string keeps the original behavior (read `<cwd>/.env`) so
|
|
50
|
+
* existing hosts — Constructive Desktop, the confirm gate — are unchanged.
|
|
51
|
+
* Headless hosts pass values instead: `fromEnvironment()` for a container or
|
|
52
|
+
* Job, an explicit record for anything else.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveProjectContext(input: string | ContextSource, options?: ResolveOptions): Promise<ResolveResult>;
|
|
32
55
|
export type DataTokenResult = {
|
|
33
56
|
token?: string;
|
|
34
57
|
userId?: string;
|
package/esm/context.js
CHANGED
|
@@ -1,52 +1,78 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { api, auth, modules } from '@constructive-io/sdk';
|
|
4
|
+
import { parseDotenv } from '12factor-env/dotenv';
|
|
4
5
|
import { probeDatabase } from './db-probe';
|
|
5
6
|
import { DEFAULT_DATA_TOKEN_SKEW_MS, getHost } from './host';
|
|
6
7
|
const DEFAULT_API_ENDPOINT = 'http://api.localhost:3000/graphql';
|
|
7
8
|
const DEFAULT_MODULES_ENDPOINT = 'http://modules.localhost:3000/graphql';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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;
|
|
26
36
|
}
|
|
27
|
-
|
|
28
|
-
|
|
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) {
|
|
29
41
|
try {
|
|
30
|
-
|
|
42
|
+
return parseDotenv(await readFile(path.join(cwd, '.env'), 'utf8'));
|
|
31
43
|
}
|
|
32
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) {
|
|
33
59
|
return {
|
|
34
60
|
context: null,
|
|
35
61
|
reason: 'No .env found in the project. Provision a Constructive database first (scaffold the project), then retry.',
|
|
36
62
|
code: 'no-env',
|
|
37
63
|
};
|
|
38
64
|
}
|
|
39
|
-
const env =
|
|
65
|
+
const env = Object.fromEntries(CONTEXT_ENV_KEYS.map((key) => [key, readContextValue(source, key)]));
|
|
40
66
|
const accessToken = env.ACCESS_TOKEN;
|
|
41
67
|
const databaseId = env.DATABASE_ID;
|
|
42
68
|
if (!accessToken || !databaseId) {
|
|
43
69
|
return {
|
|
44
70
|
context: null,
|
|
45
|
-
reason:
|
|
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.`,
|
|
46
72
|
code: 'missing-credentials',
|
|
47
73
|
};
|
|
48
74
|
}
|
|
49
|
-
// A
|
|
75
|
+
// A source-supplied pin wins for the DATA plane (the access key belongs to
|
|
50
76
|
// whatever backend wrote it); otherwise fall back to the app's backend-config
|
|
51
77
|
// store (environment-aware) so app + harness share one endpoint source.
|
|
52
78
|
const host = getHost();
|
|
@@ -56,9 +82,9 @@ export async function resolveProjectContext(cwd, options = {}) {
|
|
|
56
82
|
// The whole control plane (binding probe, schema resolution, blueprint/schema
|
|
57
83
|
// tools) authenticates with the ACCOUNT bearer: the platform api rejects
|
|
58
84
|
// per-database keys, so the project ACCESS_TOKEN can never act on metaschema
|
|
59
|
-
// surfaces. The
|
|
60
|
-
// bearer is only ever sent to the app-configured backend — never a
|
|
61
|
-
//
|
|
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.
|
|
62
88
|
const controlApiEndpoint = backend?.apiEndpoint || DEFAULT_API_ENDPOINT;
|
|
63
89
|
const controlModulesEndpoint = backend?.modulesEndpoint || DEFAULT_MODULES_ENDPOINT;
|
|
64
90
|
const account = host.account();
|
|
@@ -72,7 +98,7 @@ export async function resolveProjectContext(cwd, options = {}) {
|
|
|
72
98
|
}
|
|
73
99
|
const controlHeaders = { Authorization: `Bearer ${accountBearer}` };
|
|
74
100
|
const apiClient = api.createClient({ endpoint: controlApiEndpoint, headers: controlHeaders });
|
|
75
|
-
// Always probe the bound database — the
|
|
101
|
+
// Always probe the bound database — the DATABASE_ID stamp proves it WAS
|
|
76
102
|
// bound, never that the database still exists (backend refresh, deletion,
|
|
77
103
|
// revoked key). The probe is the single source of binding health.
|
|
78
104
|
const probe = await probeDatabase({
|
|
@@ -99,7 +125,7 @@ export async function resolveProjectContext(cwd, options = {}) {
|
|
|
99
125
|
// never shown in the Schemas tab or written to by the agent. The project's
|
|
100
126
|
// local blueprint survives account changes, so recovery is a reprovision
|
|
101
127
|
// under the signed-in account. Owner truth comes from the probe (backend),
|
|
102
|
-
// not the
|
|
128
|
+
// not the source's stamp. Data-plane resolution skips this gate — see
|
|
103
129
|
// ResolveOptions.
|
|
104
130
|
const sessionUserId = account?.userId;
|
|
105
131
|
if (options.plane !== 'data' &&
|
package/esm/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare const dbTools: ExtensionFactory;
|
|
|
4
4
|
/** Configure the host and get the extension in one call. */
|
|
5
5
|
export declare function createDbTools(host: PiToolsHost): ExtensionFactory;
|
|
6
6
|
export { type ConfirmGate, type ConfirmGateDeps, type ConfirmGateOptions, createConfirmGate, } from './confirm-gate';
|
|
7
|
-
export { deriveSubdomainEndpoint, type ModulesClient, type ProjectContext, resolveDataToken, resolveProjectContext, } from './context';
|
|
7
|
+
export { CONTEXT_ENV_KEYS, CONTEXT_ENV_PREFIX, type ContextEnvKey, type ContextSource, deriveSubdomainEndpoint, fromEnvFile, fromEnvironment, type ModulesClient, type ProjectContext, type ProjectContextFailureCode, resolveDataToken, resolveProjectContext, } from './context';
|
|
8
8
|
export { type ActiveDataToken, configureHost, type DataAuthBroker, getHost, type HostAccount, type HostBackendConfig, type HostProvisionOverlay, type PiToolsHost, type PreviewToken, } from './host';
|
|
9
9
|
export { loadProvisionManifest, parseProvisionManifest, PROVISION_MANIFEST_FILE, type ProvisionManifest, } from './provision-database/manifest';
|
|
10
10
|
export { allModulePresets, DEFAULT_PROVISION_PRESET, getModulePreset, type ModulePreset, type ProvisionModule, } from './provision-database/presets';
|
package/esm/index.js
CHANGED
|
@@ -45,7 +45,7 @@ export function createDbTools(host) {
|
|
|
45
45
|
return dbTools;
|
|
46
46
|
}
|
|
47
47
|
export { createConfirmGate, } from './confirm-gate';
|
|
48
|
-
export { deriveSubdomainEndpoint, resolveDataToken, resolveProjectContext, } from './context';
|
|
48
|
+
export { CONTEXT_ENV_KEYS, CONTEXT_ENV_PREFIX, deriveSubdomainEndpoint, fromEnvFile, fromEnvironment, resolveDataToken, resolveProjectContext, } from './context';
|
|
49
49
|
export { configureHost, getHost, } from './host';
|
|
50
50
|
export { loadProvisionManifest, parseProvisionManifest, PROVISION_MANIFEST_FILE, } from './provision-database/manifest';
|
|
51
51
|
export { allModulePresets, DEFAULT_PROVISION_PRESET, getModulePreset, } from './provision-database/presets';
|
package/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare const dbTools: ExtensionFactory;
|
|
|
4
4
|
/** Configure the host and get the extension in one call. */
|
|
5
5
|
export declare function createDbTools(host: PiToolsHost): ExtensionFactory;
|
|
6
6
|
export { type ConfirmGate, type ConfirmGateDeps, type ConfirmGateOptions, createConfirmGate, } from './confirm-gate';
|
|
7
|
-
export { deriveSubdomainEndpoint, type ModulesClient, type ProjectContext, resolveDataToken, resolveProjectContext, } from './context';
|
|
7
|
+
export { CONTEXT_ENV_KEYS, CONTEXT_ENV_PREFIX, type ContextEnvKey, type ContextSource, deriveSubdomainEndpoint, fromEnvFile, fromEnvironment, type ModulesClient, type ProjectContext, type ProjectContextFailureCode, resolveDataToken, resolveProjectContext, } from './context';
|
|
8
8
|
export { type ActiveDataToken, configureHost, type DataAuthBroker, getHost, type HostAccount, type HostBackendConfig, type HostProvisionOverlay, type PiToolsHost, type PreviewToken, } from './host';
|
|
9
9
|
export { loadProvisionManifest, parseProvisionManifest, PROVISION_MANIFEST_FILE, type ProvisionManifest, } from './provision-database/manifest';
|
|
10
10
|
export { allModulePresets, DEFAULT_PROVISION_PRESET, getModulePreset, type ModulePreset, type ProvisionModule, } from './provision-database/presets';
|
package/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createTemplatePreviewTables = exports.toolSchema = exports.resolveProvisionModules = exports.moduleKey = exports.getModulePreset = exports.DEFAULT_PROVISION_PRESET = exports.allModulePresets = exports.PROVISION_MANIFEST_FILE = exports.parseProvisionManifest = exports.loadProvisionManifest = exports.getHost = exports.configureHost = exports.resolveProjectContext = exports.resolveDataToken = exports.deriveSubdomainEndpoint = exports.createConfirmGate = exports.dbTools = void 0;
|
|
3
|
+
exports.createTemplatePreviewTables = exports.toolSchema = exports.resolveProvisionModules = exports.moduleKey = exports.getModulePreset = exports.DEFAULT_PROVISION_PRESET = exports.allModulePresets = exports.PROVISION_MANIFEST_FILE = exports.parseProvisionManifest = exports.loadProvisionManifest = exports.getHost = exports.configureHost = exports.resolveProjectContext = exports.resolveDataToken = exports.fromEnvironment = exports.fromEnvFile = exports.deriveSubdomainEndpoint = exports.CONTEXT_ENV_PREFIX = exports.CONTEXT_ENV_KEYS = exports.createConfirmGate = exports.dbTools = void 0;
|
|
4
4
|
exports.createDbTools = createDbTools;
|
|
5
5
|
const confirm_gate_1 = require("./confirm-gate");
|
|
6
6
|
const context_1 = require("./context");
|
|
@@ -52,7 +52,11 @@ function createDbTools(host) {
|
|
|
52
52
|
var confirm_gate_2 = require("./confirm-gate");
|
|
53
53
|
Object.defineProperty(exports, "createConfirmGate", { enumerable: true, get: function () { return confirm_gate_2.createConfirmGate; } });
|
|
54
54
|
var context_2 = require("./context");
|
|
55
|
+
Object.defineProperty(exports, "CONTEXT_ENV_KEYS", { enumerable: true, get: function () { return context_2.CONTEXT_ENV_KEYS; } });
|
|
56
|
+
Object.defineProperty(exports, "CONTEXT_ENV_PREFIX", { enumerable: true, get: function () { return context_2.CONTEXT_ENV_PREFIX; } });
|
|
55
57
|
Object.defineProperty(exports, "deriveSubdomainEndpoint", { enumerable: true, get: function () { return context_2.deriveSubdomainEndpoint; } });
|
|
58
|
+
Object.defineProperty(exports, "fromEnvFile", { enumerable: true, get: function () { return context_2.fromEnvFile; } });
|
|
59
|
+
Object.defineProperty(exports, "fromEnvironment", { enumerable: true, get: function () { return context_2.fromEnvironment; } });
|
|
56
60
|
Object.defineProperty(exports, "resolveDataToken", { enumerable: true, get: function () { return context_2.resolveDataToken; } });
|
|
57
61
|
Object.defineProperty(exports, "resolveProjectContext", { enumerable: true, get: function () { return context_2.resolveProjectContext; } });
|
|
58
62
|
var host_2 = require("./host");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentic-kit/pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
5
5
|
"description": "Pi adapter for agentic-kit — the Constructive typed db tools (provision, blueprint, codegen, records, policies, templates) and confirm gate as a pi coding-agent extension",
|
|
6
6
|
"main": "index.js",
|
|
@@ -29,9 +29,10 @@
|
|
|
29
29
|
"test:watch": "jest --watch"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"12factor-env": "^1.29.0",
|
|
32
33
|
"@agentic-kit/harness": "^0.12.0",
|
|
33
|
-
"@constructive-io/graphql-query": "^4.12.
|
|
34
|
-
"@constructive-io/sdk": "^1.11.
|
|
34
|
+
"@constructive-io/graphql-query": "^4.12.12",
|
|
35
|
+
"@constructive-io/sdk": "^1.11.13",
|
|
35
36
|
"node-type-registry": "^1.16.0",
|
|
36
37
|
"zod": "^4.4.3"
|
|
37
38
|
},
|
|
@@ -48,5 +49,5 @@
|
|
|
48
49
|
"coding-agent",
|
|
49
50
|
"constructive"
|
|
50
51
|
],
|
|
51
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "50a5ab31f64974a079bfbef6714ffa4b0044ffee"
|
|
52
53
|
}
|