@chatlychat/cli 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/auth.d.ts +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +40 -3
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/init.d.ts +29 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +176 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/integrations.d.ts +27 -0
- package/dist/commands/integrations.d.ts.map +1 -1
- package/dist/commands/integrations.js +133 -3
- package/dist/commands/integrations.js.map +1 -1
- package/dist/commands/platform-as-code.d.ts +54 -0
- package/dist/commands/platform-as-code.d.ts.map +1 -0
- package/dist/commands/platform-as-code.js +185 -0
- package/dist/commands/platform-as-code.js.map +1 -0
- package/dist/commands/workspace-as-code.d.ts +65 -11
- package/dist/commands/workspace-as-code.d.ts.map +1 -1
- package/dist/commands/workspace-as-code.js +35 -177
- package/dist/commands/workspace-as-code.js.map +1 -1
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +30 -1
- package/dist/help.js.map +1 -1
- package/dist/init/shared.d.ts +61 -0
- package/dist/init/shared.d.ts.map +1 -0
- package/dist/init/shared.js +110 -0
- package/dist/init/shared.js.map +1 -0
- package/dist/init/templates.d.ts +29 -0
- package/dist/init/templates.d.ts.map +1 -0
- package/dist/init/templates.js +362 -0
- package/dist/init/templates.js.map +1 -0
- package/dist/pac/platform-document.d.ts +65 -0
- package/dist/pac/platform-document.d.ts.map +1 -0
- package/dist/pac/platform-document.js +149 -0
- package/dist/pac/platform-document.js.map +1 -0
- package/dist/pac/platform-live.d.ts +65 -0
- package/dist/pac/platform-live.d.ts.map +1 -0
- package/dist/pac/platform-live.js +263 -0
- package/dist/pac/platform-live.js.map +1 -0
- package/dist/pac/platform-resources.d.ts +141 -0
- package/dist/pac/platform-resources.d.ts.map +1 -0
- package/dist/pac/platform-resources.js +509 -0
- package/dist/pac/platform-resources.js.map +1 -0
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +23 -2
- package/dist/run.js.map +1 -1
- package/dist/wac/apply.d.ts.map +1 -1
- package/dist/wac/apply.js +52 -15
- package/dist/wac/apply.js.map +1 -1
- package/dist/wac/diff-warnings.d.ts +35 -0
- package/dist/wac/diff-warnings.d.ts.map +1 -0
- package/dist/wac/diff-warnings.js +87 -0
- package/dist/wac/diff-warnings.js.map +1 -0
- package/dist/wac/diff.d.ts +40 -70
- package/dist/wac/diff.d.ts.map +1 -1
- package/dist/wac/diff.js +90 -91
- package/dist/wac/diff.js.map +1 -1
- package/dist/wac/document-key.d.ts +30 -0
- package/dist/wac/document-key.d.ts.map +1 -0
- package/dist/wac/document-key.js +80 -0
- package/dist/wac/document-key.js.map +1 -0
- package/dist/wac/document.d.ts +75 -65
- package/dist/wac/document.d.ts.map +1 -1
- package/dist/wac/document.js +60 -46
- package/dist/wac/document.js.map +1 -1
- package/dist/wac/execute.d.ts +25 -0
- package/dist/wac/execute.d.ts.map +1 -0
- package/dist/wac/execute.js +126 -0
- package/dist/wac/execute.js.map +1 -0
- package/dist/wac/load.d.ts +15 -0
- package/dist/wac/load.d.ts.map +1 -0
- package/dist/wac/load.js +91 -0
- package/dist/wac/load.js.map +1 -0
- package/dist/wac/resource-def.d.ts +85 -1
- package/dist/wac/resource-def.d.ts.map +1 -1
- package/dist/wac/resource-def.js +4 -0
- package/dist/wac/resource-def.js.map +1 -1
- package/dist/wac/values.d.ts +7 -0
- package/dist/wac/values.d.ts.map +1 -1
- package/dist/wac/values.js +16 -0
- package/dist/wac/values.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chatly.platform.json` — the platform document's schema.
|
|
3
|
+
*
|
|
4
|
+
* ═══ WHAT IT LOOKS LIKE ═══════════════════════════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* {
|
|
7
|
+
* "version": 1,
|
|
8
|
+
* "platform": { "prefix": "cp_live_a1b2c3d4" },
|
|
9
|
+
* "resources": {
|
|
10
|
+
* "workspaces": [{ "slug": "acme", "name": "Acme Inc" }],
|
|
11
|
+
* "users": [
|
|
12
|
+
* { "workspace": "acme", "email": "ops@acme.test", "name": "Ops", "role": "admin" }
|
|
13
|
+
* ],
|
|
14
|
+
* "apiKeys": [
|
|
15
|
+
* { "workspace": "acme", "name": "deploy", "scopes": ["conversations:read"] }
|
|
16
|
+
* ],
|
|
17
|
+
* "botCredentials": [{ "workspace": "acme", "name": "assistant" }],
|
|
18
|
+
* "knowledgeSources": [
|
|
19
|
+
* { "workspace": "acme", "label": "Docs", "url": "https://acme.test/docs" }
|
|
20
|
+
* ]
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* ═══ WHY FLAT SECTIONS AND NOT WORKSPACES THAT NEST THEIR CHILDREN ════════
|
|
25
|
+
*
|
|
26
|
+
* A nested shape reads better for one tenant and worse for the job this file
|
|
27
|
+
* exists for. The ask behind CHT-F04 is "four products were provisioned four
|
|
28
|
+
* slightly different ways", which is a question about CONSISTENCY ACROSS
|
|
29
|
+
* tenants: what every workspace's deploy key is scoped to, which of them has
|
|
30
|
+
* a bot credential nobody uses, whether the same two people are admins
|
|
31
|
+
* everywhere. Flat sections put each of those on one screen and make a
|
|
32
|
+
* diff-of-the-file readable; nesting buries them one indent deeper per
|
|
33
|
+
* tenant.
|
|
34
|
+
*
|
|
35
|
+
* It also means the engine is the same engine. `wac/diff.ts` and
|
|
36
|
+
* `wac/apply.ts` are untouched by this feature except for the path-parameter
|
|
37
|
+
* mechanism, and the dependency ordering a nested document would have had to
|
|
38
|
+
* re-invent (create the workspace, then everything that names it, and skip a
|
|
39
|
+
* child whose parent's create failed) is what `references` and `dependsOn`
|
|
40
|
+
* already do.
|
|
41
|
+
*
|
|
42
|
+
* ═══ THE GUARD ════════════════════════════════════════════════════════════
|
|
43
|
+
*
|
|
44
|
+
* `platform.prefix` against `GET /v1/platform/whoami`'s `prefix`, which is
|
|
45
|
+
* the identifying head of the `cp_` token. The workspace document guards on a
|
|
46
|
+
* workspace id for the same reason and the stakes here are higher: this
|
|
47
|
+
* document creates TENANTS, and running a staging file against a production
|
|
48
|
+
* reseller credential provisions real customers. Optional, because a document
|
|
49
|
+
* meant to be portable across two platform credentials cannot name one —
|
|
50
|
+
* and checked on `plan` as well as `apply`, because a plan is what somebody
|
|
51
|
+
* reads before they approve.
|
|
52
|
+
*
|
|
53
|
+
* It is the PREFIX and not the whole token: the prefix is published on every
|
|
54
|
+
* `whoami` and on every key row, it is not a credential, and a document in
|
|
55
|
+
* git must not contain one. `${env:…}` interpolation works here as it does
|
|
56
|
+
* everywhere else if somebody wants it out of the file entirely.
|
|
57
|
+
*/
|
|
58
|
+
import { addressOf } from '../wac/resource-def.js';
|
|
59
|
+
import type { DocumentSchema } from '../wac/document.js';
|
|
60
|
+
export { addressOf };
|
|
61
|
+
/** The default filenames, in the order `--file`-less runs look for them. */
|
|
62
|
+
export declare const PLATFORM_DEFAULT_FILES: string[];
|
|
63
|
+
/** The platform document: six resource types, a `cp_` credential. */
|
|
64
|
+
export declare const PLATFORM_SCHEMA: DocumentSchema;
|
|
65
|
+
//# sourceMappingURL=platform-document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-document.d.ts","sourceRoot":"","sources":["../../src/pac/platform-document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAQH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,OAAO,KAAK,EAAE,cAAc,EAAS,MAAM,oBAAoB,CAAC;AAEhE,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,UAIlC,CAAC;AAiFF,qEAAqE;AACrE,eAAO,MAAM,eAAe,EAAE,cAO7B,CAAC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chatly.platform.json` — the platform document's schema.
|
|
3
|
+
*
|
|
4
|
+
* ═══ WHAT IT LOOKS LIKE ═══════════════════════════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* {
|
|
7
|
+
* "version": 1,
|
|
8
|
+
* "platform": { "prefix": "cp_live_a1b2c3d4" },
|
|
9
|
+
* "resources": {
|
|
10
|
+
* "workspaces": [{ "slug": "acme", "name": "Acme Inc" }],
|
|
11
|
+
* "users": [
|
|
12
|
+
* { "workspace": "acme", "email": "ops@acme.test", "name": "Ops", "role": "admin" }
|
|
13
|
+
* ],
|
|
14
|
+
* "apiKeys": [
|
|
15
|
+
* { "workspace": "acme", "name": "deploy", "scopes": ["conversations:read"] }
|
|
16
|
+
* ],
|
|
17
|
+
* "botCredentials": [{ "workspace": "acme", "name": "assistant" }],
|
|
18
|
+
* "knowledgeSources": [
|
|
19
|
+
* { "workspace": "acme", "label": "Docs", "url": "https://acme.test/docs" }
|
|
20
|
+
* ]
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* ═══ WHY FLAT SECTIONS AND NOT WORKSPACES THAT NEST THEIR CHILDREN ════════
|
|
25
|
+
*
|
|
26
|
+
* A nested shape reads better for one tenant and worse for the job this file
|
|
27
|
+
* exists for. The ask behind CHT-F04 is "four products were provisioned four
|
|
28
|
+
* slightly different ways", which is a question about CONSISTENCY ACROSS
|
|
29
|
+
* tenants: what every workspace's deploy key is scoped to, which of them has
|
|
30
|
+
* a bot credential nobody uses, whether the same two people are admins
|
|
31
|
+
* everywhere. Flat sections put each of those on one screen and make a
|
|
32
|
+
* diff-of-the-file readable; nesting buries them one indent deeper per
|
|
33
|
+
* tenant.
|
|
34
|
+
*
|
|
35
|
+
* It also means the engine is the same engine. `wac/diff.ts` and
|
|
36
|
+
* `wac/apply.ts` are untouched by this feature except for the path-parameter
|
|
37
|
+
* mechanism, and the dependency ordering a nested document would have had to
|
|
38
|
+
* re-invent (create the workspace, then everything that names it, and skip a
|
|
39
|
+
* child whose parent's create failed) is what `references` and `dependsOn`
|
|
40
|
+
* already do.
|
|
41
|
+
*
|
|
42
|
+
* ═══ THE GUARD ════════════════════════════════════════════════════════════
|
|
43
|
+
*
|
|
44
|
+
* `platform.prefix` against `GET /v1/platform/whoami`'s `prefix`, which is
|
|
45
|
+
* the identifying head of the `cp_` token. The workspace document guards on a
|
|
46
|
+
* workspace id for the same reason and the stakes here are higher: this
|
|
47
|
+
* document creates TENANTS, and running a staging file against a production
|
|
48
|
+
* reseller credential provisions real customers. Optional, because a document
|
|
49
|
+
* meant to be portable across two platform credentials cannot name one —
|
|
50
|
+
* and checked on `plan` as well as `apply`, because a plan is what somebody
|
|
51
|
+
* reads before they approve.
|
|
52
|
+
*
|
|
53
|
+
* It is the PREFIX and not the whole token: the prefix is published on every
|
|
54
|
+
* `whoami` and on every key row, it is not a credential, and a document in
|
|
55
|
+
* git must not contain one. `${env:…}` interpolation works here as it does
|
|
56
|
+
* everywhere else if somebody wants it out of the file entirely.
|
|
57
|
+
*/
|
|
58
|
+
import { PLATFORM_RESOURCE_KEYS, PLATFORM_RESOURCE_TYPES, platformResourceDef, REFUSED_KEYS, } from './platform-resources.js';
|
|
59
|
+
import { addressOf } from '../wac/resource-def.js';
|
|
60
|
+
export { addressOf };
|
|
61
|
+
/** The default filenames, in the order `--file`-less runs look for them. */
|
|
62
|
+
export const PLATFORM_DEFAULT_FILES = [
|
|
63
|
+
'chatly.platform.json',
|
|
64
|
+
'chatly.platform.yaml',
|
|
65
|
+
'chatly.platform.yml',
|
|
66
|
+
];
|
|
67
|
+
const TOP_LEVEL_KEYS = ['version', 'platform', 'resources'];
|
|
68
|
+
const PLATFORM_KEYS = ['prefix'];
|
|
69
|
+
/**
|
|
70
|
+
* `platform: { prefix }`, validated.
|
|
71
|
+
*
|
|
72
|
+
* A nested object rather than a bare `platform: "cp_live_…"` string, because
|
|
73
|
+
* the two things that might reasonably join it later — a restriction echo and
|
|
74
|
+
* an environment name — are properties OF the credential rather than
|
|
75
|
+
* alternatives to it, and widening a string to an object afterwards is a
|
|
76
|
+
* breaking change to a file format. An unknown key inside it is refused by
|
|
77
|
+
* name like every other key in this document.
|
|
78
|
+
*/
|
|
79
|
+
function readGuard(raw, issues) {
|
|
80
|
+
const block = raw['platform'];
|
|
81
|
+
if (block === undefined) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
if (typeof block !== 'object' || block === null || Array.isArray(block)) {
|
|
85
|
+
issues.push({
|
|
86
|
+
path: 'platform',
|
|
87
|
+
message: 'must be an object: `"platform": { "prefix": "cp_live_…" }`, or left out. It is a ' +
|
|
88
|
+
'guard — `apply` refuses to run against any other platform credential, which is what ' +
|
|
89
|
+
'stops a staging file provisioning real tenants.',
|
|
90
|
+
});
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const entries = block;
|
|
94
|
+
for (const key of Object.keys(entries)) {
|
|
95
|
+
if (!PLATFORM_KEYS.includes(key)) {
|
|
96
|
+
issues.push({
|
|
97
|
+
path: `platform.${key}`,
|
|
98
|
+
message: `unknown key "${key}". Valid: ${PLATFORM_KEYS.join(', ')}.`,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const prefix = entries['prefix'];
|
|
103
|
+
if (prefix === undefined) {
|
|
104
|
+
issues.push({
|
|
105
|
+
path: 'platform.prefix',
|
|
106
|
+
message: 'required when `platform` is present. It is the identifying head of the `cp_` token, ' +
|
|
107
|
+
'which `chatly api GET /v1/platform/whoami` reports as `prefix` — NOT the token ' +
|
|
108
|
+
'itself. Remove the whole `platform` block if this document is meant to be portable ' +
|
|
109
|
+
'across credentials.',
|
|
110
|
+
});
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
if (typeof prefix !== 'string' || prefix.length === 0) {
|
|
114
|
+
issues.push({ path: 'platform.prefix', message: 'must be a non-empty string.' });
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
/*
|
|
118
|
+
* A WHOLE token here is refused, loudly, rather than accepted and compared
|
|
119
|
+
* against the prefix it starts with.
|
|
120
|
+
*
|
|
121
|
+
* `platform-credentials.service.ts` mints `cp_<32 bytes base64url>` and
|
|
122
|
+
* stores only its hash and a short prefix. A document in git that carried
|
|
123
|
+
* the whole thing would be a reseller credential in a repository — the
|
|
124
|
+
* single worst secret this product has, since it creates tenants and mints
|
|
125
|
+
* their keys — and this file is the one file we KNOW is committed. Being
|
|
126
|
+
* tolerant here would be the convenience that makes that happen.
|
|
127
|
+
*/
|
|
128
|
+
if (prefix.length > 24) {
|
|
129
|
+
issues.push({
|
|
130
|
+
path: 'platform.prefix',
|
|
131
|
+
message: 'that looks like a whole token, not a prefix. Put the PREFIX here — the short ' +
|
|
132
|
+
'identifying head `GET /v1/platform/whoami` reports — and never the token: a `cp_` ' +
|
|
133
|
+
'credential creates tenants and mints their API keys, and this document is a file in ' +
|
|
134
|
+
'git. Pass the token with --key or CHATLY_API_KEY, or interpolate it nowhere at all.',
|
|
135
|
+
});
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
return prefix;
|
|
139
|
+
}
|
|
140
|
+
/** The platform document: six resource types, a `cp_` credential. */
|
|
141
|
+
export const PLATFORM_SCHEMA = {
|
|
142
|
+
topLevelKeys: TOP_LEVEL_KEYS,
|
|
143
|
+
resourceKeys: PLATFORM_RESOURCE_KEYS,
|
|
144
|
+
types: PLATFORM_RESOURCE_TYPES,
|
|
145
|
+
def: platformResourceDef,
|
|
146
|
+
refusedKeys: REFUSED_KEYS,
|
|
147
|
+
guard: readGuard,
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=platform-document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-document.js","sourceRoot":"","sources":["../../src/pac/platform-document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,YAAY,GACb,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAInD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,4EAA4E;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,sBAAsB;IACtB,sBAAsB;IACtB,qBAAqB;CACtB,CAAC;AAEF,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;AAE5D,MAAM,aAAa,GAAG,CAAC,QAAQ,CAAC,CAAC;AAEjC;;;;;;;;;GASG;AACH,SAAS,SAAS,CAAC,GAA4B,EAAE,MAAe;IAC9D,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC;IAC9B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,UAAU;YAChB,OAAO,EACL,mFAAmF;gBACnF,sFAAsF;gBACtF,iDAAiD;SACpD,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,OAAO,GAAG,KAAgC,CAAC;IACjD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,YAAY,GAAG,EAAE;gBACvB,OAAO,EAAE,gBAAgB,GAAG,aAAa,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;aACrE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,iBAAiB;YACvB,OAAO,EACL,sFAAsF;gBACtF,iFAAiF;gBACjF,qFAAqF;gBACrF,qBAAqB;SACxB,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC,CAAC;QACjF,OAAO,SAAS,CAAC;IACnB,CAAC;IACD;;;;;;;;;;OAUG;IACH,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;QACvB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,iBAAiB;YACvB,OAAO,EACL,+EAA+E;gBAC/E,oFAAoF;gBACpF,sFAAsF;gBACtF,qFAAqF;SACxF,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,MAAM,eAAe,GAAmB;IAC7C,YAAY,EAAE,cAAc;IAC5B,YAAY,EAAE,sBAAsB;IACpC,KAAK,EAAE,uBAAuB;IAC9B,GAAG,EAAE,mBAAmB;IACxB,WAAW,EAAE,YAAY;IACzB,KAAK,EAAE,SAAS;CACjB,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the live platform surface. Every call here is a GET.
|
|
3
|
+
*
|
|
4
|
+
* ═══ THE ONE DECISION THAT SHAPES THIS FILE ═══════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* **A platform document is a SUBSET of the tenants a credential owns, so a
|
|
7
|
+
* workspace the file does not mention is not drift.**
|
|
8
|
+
*
|
|
9
|
+
* `wac/diff.ts`'s first rule says the same thing one level down — "a server
|
|
10
|
+
* field the file says nothing about is unmanaged and is never mentioned" —
|
|
11
|
+
* and the consequence here is bigger. A reseller with four hundred tenants
|
|
12
|
+
* and a document describing one would otherwise get a plan whose first four
|
|
13
|
+
* hundred lines are "on the server, not in this document", and a plan nobody
|
|
14
|
+
* reads is worse than no plan because it gets approved. Worse, the shape
|
|
15
|
+
* would invite `--prune` at a tenant, which is why `workspaces.remove` is
|
|
16
|
+
* `null` as a second lock.
|
|
17
|
+
*
|
|
18
|
+
* So this reader filters the workspace list down to the slugs the document
|
|
19
|
+
* names, and says in a WARNING how many it left alone. That is the honest
|
|
20
|
+
* middle: the number is visible, and it is not presented as something to fix.
|
|
21
|
+
*
|
|
22
|
+
* It also bounds the request count. Children are nested under a workspace
|
|
23
|
+
* id, so reading them for every tenant a credential owns is four reads per
|
|
24
|
+
* tenant — twelve hundred GETs to plan a one-tenant document, against a
|
|
25
|
+
* `platform-provision` throttle of 20/minute on the writes and no reason to
|
|
26
|
+
* find out what the reads' limit is.
|
|
27
|
+
*
|
|
28
|
+
* ═══ WHICH SLUGS COUNT AS DECLARED ════════════════════════════════════════
|
|
29
|
+
*
|
|
30
|
+
* The union of the `workspaces:` section's keys AND every `workspace:` a
|
|
31
|
+
* child row names. The second half matters: adopting an existing tenant —
|
|
32
|
+
* adding a deploy key to a workspace somebody else created — is a document
|
|
33
|
+
* with an `apiKeys` row and no `workspaces` row at all, and that is a shape
|
|
34
|
+
* to support rather than refuse. The reference resolves against the live
|
|
35
|
+
* list, and if the slug is not there `diff.ts` blocks the row with "neither
|
|
36
|
+
* on the server nor created by this plan".
|
|
37
|
+
*
|
|
38
|
+
* ═══ WHY THE DETAIL ROUTE IS FETCHED ONCE ═════════════════════════════════
|
|
39
|
+
*
|
|
40
|
+
* `users` has no list route of its own: members come back as `users[]` on
|
|
41
|
+
* `GET /v1/platform/workspaces/{id}`. If a second resource ever reads from
|
|
42
|
+
* that same route, declaring it is enough — `GET`s are memoised by path
|
|
43
|
+
* within one call, so two resources over one route cost one request.
|
|
44
|
+
*/
|
|
45
|
+
import type { LiveSection } from '../wac/diff.js';
|
|
46
|
+
import type { WacDocument } from '../wac/document.js';
|
|
47
|
+
import type { ChatlyClient } from '@chatlychat/sdk-node';
|
|
48
|
+
export interface PlatformLive {
|
|
49
|
+
readonly live: Map<string, LiveSection>;
|
|
50
|
+
/** Observations about the surface, merged into the plan's own warnings. */
|
|
51
|
+
readonly warnings: ReadonlyArray<string>;
|
|
52
|
+
}
|
|
53
|
+
/** Every workspace slug this document refers to, declared or referenced. */
|
|
54
|
+
export declare function declaredSlugs(doc: WacDocument): Set<string>;
|
|
55
|
+
/**
|
|
56
|
+
* The live rows for every type the document mentions, and nothing else.
|
|
57
|
+
*
|
|
58
|
+
* All through `chatly.request` — the escape hatch `packages/sdk-spec/CONTRACT.md`
|
|
59
|
+
* defines — so auth, retries and the typed errors are the same ones every
|
|
60
|
+
* other command gets, and `apps/cli` still adds no HTTP of its own. There is
|
|
61
|
+
* no SDK method for any of these: `surface.json` holds no `/v1/platform` path
|
|
62
|
+
* at all, which the manifest's own note records.
|
|
63
|
+
*/
|
|
64
|
+
export declare function readPlatformLive(chatly: ChatlyClient, doc: WacDocument): Promise<PlatformLive>;
|
|
65
|
+
//# sourceMappingURL=platform-live.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-live.d.ts","sourceRoot":"","sources":["../../src/pac/platform-live.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAiBzD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACxC,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC1C;AASD,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,CA2B3D;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,YAAY,CAAC,CAgFvB"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading the live platform surface. Every call here is a GET.
|
|
3
|
+
*
|
|
4
|
+
* ═══ THE ONE DECISION THAT SHAPES THIS FILE ═══════════════════════════════
|
|
5
|
+
*
|
|
6
|
+
* **A platform document is a SUBSET of the tenants a credential owns, so a
|
|
7
|
+
* workspace the file does not mention is not drift.**
|
|
8
|
+
*
|
|
9
|
+
* `wac/diff.ts`'s first rule says the same thing one level down — "a server
|
|
10
|
+
* field the file says nothing about is unmanaged and is never mentioned" —
|
|
11
|
+
* and the consequence here is bigger. A reseller with four hundred tenants
|
|
12
|
+
* and a document describing one would otherwise get a plan whose first four
|
|
13
|
+
* hundred lines are "on the server, not in this document", and a plan nobody
|
|
14
|
+
* reads is worse than no plan because it gets approved. Worse, the shape
|
|
15
|
+
* would invite `--prune` at a tenant, which is why `workspaces.remove` is
|
|
16
|
+
* `null` as a second lock.
|
|
17
|
+
*
|
|
18
|
+
* So this reader filters the workspace list down to the slugs the document
|
|
19
|
+
* names, and says in a WARNING how many it left alone. That is the honest
|
|
20
|
+
* middle: the number is visible, and it is not presented as something to fix.
|
|
21
|
+
*
|
|
22
|
+
* It also bounds the request count. Children are nested under a workspace
|
|
23
|
+
* id, so reading them for every tenant a credential owns is four reads per
|
|
24
|
+
* tenant — twelve hundred GETs to plan a one-tenant document, against a
|
|
25
|
+
* `platform-provision` throttle of 20/minute on the writes and no reason to
|
|
26
|
+
* find out what the reads' limit is.
|
|
27
|
+
*
|
|
28
|
+
* ═══ WHICH SLUGS COUNT AS DECLARED ════════════════════════════════════════
|
|
29
|
+
*
|
|
30
|
+
* The union of the `workspaces:` section's keys AND every `workspace:` a
|
|
31
|
+
* child row names. The second half matters: adopting an existing tenant —
|
|
32
|
+
* adding a deploy key to a workspace somebody else created — is a document
|
|
33
|
+
* with an `apiKeys` row and no `workspaces` row at all, and that is a shape
|
|
34
|
+
* to support rather than refuse. The reference resolves against the live
|
|
35
|
+
* list, and if the slug is not there `diff.ts` blocks the row with "neither
|
|
36
|
+
* on the server nor created by this plan".
|
|
37
|
+
*
|
|
38
|
+
* ═══ WHY THE DETAIL ROUTE IS FETCHED ONCE ═════════════════════════════════
|
|
39
|
+
*
|
|
40
|
+
* `users` has no list route of its own: members come back as `users[]` on
|
|
41
|
+
* `GET /v1/platform/workspaces/{id}`. If a second resource ever reads from
|
|
42
|
+
* that same route, declaring it is enough — `GET`s are memoised by path
|
|
43
|
+
* within one call, so two resources over one route cost one request.
|
|
44
|
+
*/
|
|
45
|
+
import { addressOf } from '../wac/resource-def.js';
|
|
46
|
+
/**
|
|
47
|
+
* The cap `KbIngestService.list` applies, and the bound the two
|
|
48
|
+
* `@PaginationExempt`-shaped credential lists sit behind.
|
|
49
|
+
*
|
|
50
|
+
* `wac/workspace-as-code.ts` uses the same number for the same reason: a bare
|
|
51
|
+
* array that comes back at exactly its cap MAY be truncated, and a row past
|
|
52
|
+
* the cap is invisible rather than absent — which would make it an orphan and
|
|
53
|
+
* then, with `--prune`, a deletion. `diff.ts` refuses prune for a type in
|
|
54
|
+
* that state.
|
|
55
|
+
*/
|
|
56
|
+
const EXEMPT_LIST_CAP = 200;
|
|
57
|
+
/** How many pages of `GET /v1/platform/workspaces` to walk before stopping. */
|
|
58
|
+
const PAGE_GUARD = 200;
|
|
59
|
+
function asRows(value) {
|
|
60
|
+
return Array.isArray(value) ? value : [];
|
|
61
|
+
}
|
|
62
|
+
/** Every workspace slug this document refers to, declared or referenced. */
|
|
63
|
+
export function declaredSlugs(doc) {
|
|
64
|
+
const out = new Set();
|
|
65
|
+
for (const section of doc.sections) {
|
|
66
|
+
if (section.def.parent === undefined) {
|
|
67
|
+
for (const row of section.rows) {
|
|
68
|
+
out.add(row.key);
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const { field } = section.def.parent;
|
|
73
|
+
for (const row of section.rows) {
|
|
74
|
+
/*
|
|
75
|
+
* Off the ROW rather than off `row.refs`, because a row whose parent
|
|
76
|
+
* field was missing or malformed never reaches here — `parseDocument`
|
|
77
|
+
* throws before this is called. The ref map is keyed by the WIRE name
|
|
78
|
+
* and holds the same value; reading the source field keeps this
|
|
79
|
+
* readable against the document rather than against the request.
|
|
80
|
+
*/
|
|
81
|
+
const named = row.body[field] ?? row.refs[field];
|
|
82
|
+
const fromRefs = Object.values(row.refs).find((r) => r.type === section.def.parent?.type);
|
|
83
|
+
const slug = typeof named === 'string' ? named : fromRefs?.key;
|
|
84
|
+
if (typeof slug === 'string' && slug.length > 0) {
|
|
85
|
+
out.add(slug);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The live rows for every type the document mentions, and nothing else.
|
|
93
|
+
*
|
|
94
|
+
* All through `chatly.request` — the escape hatch `packages/sdk-spec/CONTRACT.md`
|
|
95
|
+
* defines — so auth, retries and the typed errors are the same ones every
|
|
96
|
+
* other command gets, and `apps/cli` still adds no HTTP of its own. There is
|
|
97
|
+
* no SDK method for any of these: `surface.json` holds no `/v1/platform` path
|
|
98
|
+
* at all, which the manifest's own note records.
|
|
99
|
+
*/
|
|
100
|
+
export async function readPlatformLive(chatly, doc) {
|
|
101
|
+
const warnings = [];
|
|
102
|
+
const live = new Map();
|
|
103
|
+
const wanted = declaredSlugs(doc);
|
|
104
|
+
const workspaces = await readWorkspaces(chatly, wanted, warnings);
|
|
105
|
+
// Always set: a document with no `workspaces:` section still resolves its
|
|
106
|
+
// children's `workspace:` against these rows. The command passes them to
|
|
107
|
+
// `buildPlan` as `referenced` in that case, so they are never planned.
|
|
108
|
+
live.set('workspaces', { rows: [...workspaces.values()] });
|
|
109
|
+
/** `GET`s already made in this call, by path. See the header. */
|
|
110
|
+
const fetched = new Map();
|
|
111
|
+
const get = async (path) => {
|
|
112
|
+
const cached = fetched.get(path);
|
|
113
|
+
if (cached !== undefined) {
|
|
114
|
+
return cached;
|
|
115
|
+
}
|
|
116
|
+
const answer = await chatly.request('GET', path);
|
|
117
|
+
fetched.set(path, answer);
|
|
118
|
+
return answer;
|
|
119
|
+
};
|
|
120
|
+
for (const section of doc.sections) {
|
|
121
|
+
const { def } = section;
|
|
122
|
+
if (def.parent === undefined) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const rows = [];
|
|
126
|
+
let truncated = false;
|
|
127
|
+
/*
|
|
128
|
+
* A SINGLETON child — one per workspace, named by the workspace itself
|
|
129
|
+
* (`settings`: `childKey` IS the parent field). Its key is `<slug>/<slug>`
|
|
130
|
+
* on both sides, and it is read only for the workspaces this section
|
|
131
|
+
* declares: a tenant's settings exist whether or not the file mentions
|
|
132
|
+
* them, so reading the others would report each one as an orphan.
|
|
133
|
+
*/
|
|
134
|
+
const singleton = def.parent.childKey === def.parent.field;
|
|
135
|
+
for (const [slug, workspace] of workspaces) {
|
|
136
|
+
const id = workspace['id'];
|
|
137
|
+
if (typeof id !== 'string' || id.length === 0) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (singleton && !section.rows.some((r) => r.key === addressOf(slug, slug))) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const path = def.list.path.replaceAll(`{${def.parent.wire}}`, encodeURIComponent(id));
|
|
144
|
+
const answer = await get(path);
|
|
145
|
+
const found = def.rowsFrom === undefined ? asRows(answer) : [...def.rowsFrom(answer)];
|
|
146
|
+
if (def.rowsFrom === undefined && found.length >= EXEMPT_LIST_CAP) {
|
|
147
|
+
truncated = true;
|
|
148
|
+
}
|
|
149
|
+
for (const row of found) {
|
|
150
|
+
/*
|
|
151
|
+
* The two stamps, and they are what let the shared engine plan a
|
|
152
|
+
* nested surface without knowing about one.
|
|
153
|
+
*
|
|
154
|
+
* `address` is the synthetic natural key — `<slug>/<child>` — which
|
|
155
|
+
* `indexLive` matches on and `diff.ts` never compares, because it
|
|
156
|
+
* skips the natural key. `workspaceId` is the parent id a nested
|
|
157
|
+
* write path needs; stamped onto every row so an ORPHAN, which has
|
|
158
|
+
* no document row to resolve a reference from, still carries the id
|
|
159
|
+
* its DELETE path requires.
|
|
160
|
+
*/
|
|
161
|
+
rows.push({
|
|
162
|
+
...row,
|
|
163
|
+
[def.naturalKey]: addressOf(slug, singleton ? slug : String(row[def.parent.childKey] ?? '')),
|
|
164
|
+
[def.parent.wire]: id,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
warnings.push(...expiryWarnings(def, slug, found));
|
|
168
|
+
}
|
|
169
|
+
live.set(def.type, truncated ? { rows, possiblyTruncated: true } : { rows });
|
|
170
|
+
}
|
|
171
|
+
return { live, warnings };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The workspace list, walked to the end, kept down to the declared slugs.
|
|
175
|
+
*
|
|
176
|
+
* Paged since 2026-10-23 — it was a bare array behind a `@PaginationExempt`
|
|
177
|
+
* whose own schema docblock now calls the exemption unsound. Walked by hand
|
|
178
|
+
* rather than through `autoPaginate` because the envelope matters: this one
|
|
179
|
+
* answers `{ items, nextCursor, hasMore }` while its `@PaginationExempt`
|
|
180
|
+
* neighbours on the same surface answer bare arrays, and conflating the two
|
|
181
|
+
* is the bug CLAUDE.md records two SDKs shipping.
|
|
182
|
+
*
|
|
183
|
+
* `?status=` is deliberately NOT passed. Omitting it answers everything that
|
|
184
|
+
* is not soft-deleted, suspended tenants included, which is the right set: a
|
|
185
|
+
* suspended workspace exists and there is no platform route that un-suspends
|
|
186
|
+
* it, so matching the document row to it and reporting `noop` is true. A
|
|
187
|
+
* soft-deleted one is absent, so its document row is a `create` — and that
|
|
188
|
+
* create 409s on the slug, which is a better answer than silently adopting a
|
|
189
|
+
* tenant that is counting down to purge.
|
|
190
|
+
*/
|
|
191
|
+
async function readWorkspaces(chatly, wanted, warnings) {
|
|
192
|
+
const out = new Map();
|
|
193
|
+
let total = 0;
|
|
194
|
+
let cursor;
|
|
195
|
+
for (let guard = 0; guard < PAGE_GUARD; guard += 1) {
|
|
196
|
+
const page = await chatly.request('GET', '/v1/platform/workspaces', { query: cursor === undefined ? {} : { cursor } });
|
|
197
|
+
const items = asRows(page.items);
|
|
198
|
+
total += items.length;
|
|
199
|
+
for (const row of items) {
|
|
200
|
+
const slug = row['slug'];
|
|
201
|
+
if (typeof slug === 'string' && wanted.has(slug)) {
|
|
202
|
+
out.set(slug, row);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const next = page.nextCursor;
|
|
206
|
+
if (typeof next !== 'string' || next.length === 0 || next === cursor) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
cursor = next;
|
|
210
|
+
}
|
|
211
|
+
const unmentioned = total - out.size;
|
|
212
|
+
if (unmentioned > 0) {
|
|
213
|
+
warnings.push(`${String(unmentioned)} workspace(s) this credential owns are not in this document. They ` +
|
|
214
|
+
'are not listed below and are not drift: a platform document describes a SUBSET of a ' +
|
|
215
|
+
"reseller's tenants, so `--prune` is never offered one — and `DELETE " +
|
|
216
|
+
'/v1/platform/workspaces/{id}` soft-deletes every conversation in it.');
|
|
217
|
+
}
|
|
218
|
+
return out;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* A credential in the list that has EXPIRED, named rather than matched on.
|
|
222
|
+
*
|
|
223
|
+
* `ignoreLive` drops a REVOKED key, because `revokedAt` is a fact on the row
|
|
224
|
+
* and needs no clock. Expiry does need one, and `buildPlan` is pure — no
|
|
225
|
+
* transport, no filesystem, no clock, which is half the proof that `plan`
|
|
226
|
+
* writes nothing. Putting `Date.now()` inside it would buy a slightly better
|
|
227
|
+
* match and spend that property, so the check lives here, in the reader,
|
|
228
|
+
* which is already doing HTTP.
|
|
229
|
+
*
|
|
230
|
+
* The consequence is honest and stated: an expired key still MATCHES its
|
|
231
|
+
* document row and still reports `noop`. The warning is what tells you the
|
|
232
|
+
* row the plan called unchanged authenticates nothing. `expiresAt` is
|
|
233
|
+
* readable, so a document that declares it sees the drift directly — this is
|
|
234
|
+
* for the common case of a document that omits it and takes the route's
|
|
235
|
+
* 365-day default.
|
|
236
|
+
*/
|
|
237
|
+
function expiryWarnings(def, slug, rows) {
|
|
238
|
+
if (!def.secrets.includes('token')) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
const now = Date.now();
|
|
242
|
+
const dead = rows
|
|
243
|
+
.filter((row) => {
|
|
244
|
+
if (def.ignoreLive?.(row) === true) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
const at = row['expiresAt'];
|
|
248
|
+
return typeof at === 'string' && Number.isFinite(Date.parse(at)) && Date.parse(at) <= now;
|
|
249
|
+
})
|
|
250
|
+
.map((row) => String(row['name'] ?? '(unnamed)'));
|
|
251
|
+
if (dead.length === 0) {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
return [
|
|
255
|
+
`${def.type}: ${dead.map((n) => `\`${slug}/${n}\``).join(', ')} ` +
|
|
256
|
+
`${dead.length === 1 ? 'has' : 'have'} expired and ${dead.length === 1 ? 'is' : 'are'} ` +
|
|
257
|
+
'still listed, so the plan below reports them as unchanged — the row exists and matches ' +
|
|
258
|
+
'the document. They authenticate nothing. Rename them in the document to mint ' +
|
|
259
|
+
'replacements and orphan these, or revoke them with ' +
|
|
260
|
+
`\`chatly api DELETE ${def.remove?.path ?? ''}\`.`,
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
//# sourceMappingURL=platform-live.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-live.js","sourceRoot":"","sources":["../../src/pac/platform-live.ts"],"names":[],"mappings":"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"}
|