@chatlychat/cli 0.4.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/commands/auth.d.ts +1 -1
  2. package/dist/commands/auth.d.ts.map +1 -1
  3. package/dist/commands/auth.js +40 -3
  4. package/dist/commands/auth.js.map +1 -1
  5. package/dist/commands/deliveries.d.ts +18 -0
  6. package/dist/commands/deliveries.d.ts.map +1 -0
  7. package/dist/commands/deliveries.js +46 -0
  8. package/dist/commands/deliveries.js.map +1 -0
  9. package/dist/commands/init.d.ts +29 -0
  10. package/dist/commands/init.d.ts.map +1 -0
  11. package/dist/commands/init.js +176 -0
  12. package/dist/commands/init.js.map +1 -0
  13. package/dist/commands/integrations.d.ts +27 -0
  14. package/dist/commands/integrations.d.ts.map +1 -1
  15. package/dist/commands/integrations.js +145 -4
  16. package/dist/commands/integrations.js.map +1 -1
  17. package/dist/commands/platform-as-code.d.ts +54 -0
  18. package/dist/commands/platform-as-code.d.ts.map +1 -0
  19. package/dist/commands/platform-as-code.js +185 -0
  20. package/dist/commands/platform-as-code.js.map +1 -0
  21. package/dist/commands/workspace-as-code.d.ts +52 -11
  22. package/dist/commands/workspace-as-code.d.ts.map +1 -1
  23. package/dist/commands/workspace-as-code.js +70 -189
  24. package/dist/commands/workspace-as-code.js.map +1 -1
  25. package/dist/help.d.ts +1 -1
  26. package/dist/help.d.ts.map +1 -1
  27. package/dist/help.js +36 -1
  28. package/dist/help.js.map +1 -1
  29. package/dist/init/shared.d.ts +61 -0
  30. package/dist/init/shared.d.ts.map +1 -0
  31. package/dist/init/shared.js +110 -0
  32. package/dist/init/shared.js.map +1 -0
  33. package/dist/init/templates.d.ts +29 -0
  34. package/dist/init/templates.d.ts.map +1 -0
  35. package/dist/init/templates.js +362 -0
  36. package/dist/init/templates.js.map +1 -0
  37. package/dist/pac/platform-document.d.ts +65 -0
  38. package/dist/pac/platform-document.d.ts.map +1 -0
  39. package/dist/pac/platform-document.js +149 -0
  40. package/dist/pac/platform-document.js.map +1 -0
  41. package/dist/pac/platform-live.d.ts +65 -0
  42. package/dist/pac/platform-live.d.ts.map +1 -0
  43. package/dist/pac/platform-live.js +263 -0
  44. package/dist/pac/platform-live.js.map +1 -0
  45. package/dist/pac/platform-resources.d.ts +141 -0
  46. package/dist/pac/platform-resources.d.ts.map +1 -0
  47. package/dist/pac/platform-resources.js +509 -0
  48. package/dist/pac/platform-resources.js.map +1 -0
  49. package/dist/run.d.ts.map +1 -1
  50. package/dist/run.js +26 -2
  51. package/dist/run.js.map +1 -1
  52. package/dist/wac/apply.d.ts.map +1 -1
  53. package/dist/wac/apply.js +56 -16
  54. package/dist/wac/apply.js.map +1 -1
  55. package/dist/wac/diff-warnings.d.ts +35 -0
  56. package/dist/wac/diff-warnings.d.ts.map +1 -0
  57. package/dist/wac/diff-warnings.js +88 -0
  58. package/dist/wac/diff-warnings.js.map +1 -0
  59. package/dist/wac/diff.d.ts +40 -70
  60. package/dist/wac/diff.d.ts.map +1 -1
  61. package/dist/wac/diff.js +114 -91
  62. package/dist/wac/diff.js.map +1 -1
  63. package/dist/wac/document-key.d.ts +30 -0
  64. package/dist/wac/document-key.d.ts.map +1 -0
  65. package/dist/wac/document-key.js +87 -0
  66. package/dist/wac/document-key.js.map +1 -0
  67. package/dist/wac/document.d.ts +75 -65
  68. package/dist/wac/document.d.ts.map +1 -1
  69. package/dist/wac/document.js +60 -46
  70. package/dist/wac/document.js.map +1 -1
  71. package/dist/wac/execute.d.ts +25 -0
  72. package/dist/wac/execute.d.ts.map +1 -0
  73. package/dist/wac/execute.js +126 -0
  74. package/dist/wac/execute.js.map +1 -0
  75. package/dist/wac/load.d.ts +17 -0
  76. package/dist/wac/load.d.ts.map +1 -0
  77. package/dist/wac/load.js +92 -0
  78. package/dist/wac/load.js.map +1 -0
  79. package/dist/wac/resource-def.d.ts +89 -1
  80. package/dist/wac/resource-def.d.ts.map +1 -1
  81. package/dist/wac/resource-def.js +4 -0
  82. package/dist/wac/resource-def.js.map +1 -1
  83. package/dist/wac/resources.d.ts +9 -11
  84. package/dist/wac/resources.d.ts.map +1 -1
  85. package/dist/wac/resources.js +93 -27
  86. package/dist/wac/resources.js.map +1 -1
  87. package/dist/wac/values.d.ts +20 -0
  88. package/dist/wac/values.d.ts.map +1 -1
  89. package/dist/wac/values.js +46 -0
  90. package/dist/wac/values.js.map +1 -1
  91. package/package.json +4 -3
@@ -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":"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"}
@@ -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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"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * The resource table `chatly platform plan` and `chatly platform apply` work
3
+ * from — the Platform API as a file (CHT-F04).
4
+ *
5
+ * ═══ WHY THIS IS A SECOND TABLE AND NOT MORE ROWS IN `wac/resources.ts` ═══
6
+ *
7
+ * A different CREDENTIAL and a different SHAPE, and either one on its own
8
+ * would be enough.
9
+ *
10
+ * The credential: every route here is behind `PlatformGuard`
11
+ * (`apps/api/src/modules/platform/platform.guard.ts`), which resolves a `cp_`
12
+ * platform credential and refuses anything else. A workspace `ck_` key
13
+ * presented to `/v1/platform/*` is not merely unauthorised — that guard's own
14
+ * header spells out that there is no code path which turns a platform
15
+ * credential into a membership, and none which turns a key into a platform
16
+ * principal. So one document cannot describe both surfaces: it would be one
17
+ * file needing two credentials, and the first thing it would do is fail half
18
+ * its reads.
19
+ *
20
+ * The shape: a workspace-level resource lives at a flat collection path and
21
+ * names its parent in the BODY. Every resource here lives under
22
+ * `/v1/platform/workspaces/{workspaceId}/…` and names its parent in the PATH,
23
+ * and a platform document describes SEVERAL workspaces at once — which is the
24
+ * whole point of it, and the reason `naturalKey` below is a synthetic
25
+ * `address` rather than an email or a name. `a@b.c` identifies a member
26
+ * inside one tenant and nothing at all across a document that provisions
27
+ * four.
28
+ *
29
+ * ═══ VERIFIED AGAINST HANDLERS ════════════════════════════════════════════
30
+ *
31
+ * Every path, key, flag and field-set below was read off
32
+ * `apps/api/src/modules/platform/**` on 2026-10-25. CLAUDE.md records why
33
+ * that is not optional — the SDK manifest's first version listed two routes
34
+ * that do not exist and had its `paged` flag wrong in both directions — and
35
+ * `packages/sdk-spec/surface.json` holds no `/v1/platform` path at all, so
36
+ * there was no manifest to read even if one were trusted. The citations:
37
+ *
38
+ * workspaces platform.controller.ts:199 (POST),
39
+ * platform-workspace-read.controller.ts:115 (GET, PAGED)
40
+ * users platform.controller.ts:229 (POST), :249 (DELETE);
41
+ * read from the DETAIL route's `users[]`,
42
+ * platform-workspace-read.controller.ts:160
43
+ * apiKeys platform.controller.ts:422 (POST),
44
+ * platform-api-keys.controller.ts:129 (GET), :159 (DELETE)
45
+ * botCredentials platform.controller.ts:483 (POST), :465 (GET), :524 (DELETE)
46
+ * knowledgeSources platform-knowledge.controller.ts:117 (POST), :161 (GET),
47
+ * :197 (DELETE)
48
+ *
49
+ * ═══ FIVE THINGS THE HANDLERS DECIDED, EACH OF WHICH WOULD BE A LIVE BUG ══
50
+ *
51
+ * 1. **NOTHING on this surface has an update route.** There is no PATCH on a
52
+ * platform workspace, a membership, a key, a bot credential or a
53
+ * knowledge source — grep the five controllers and the only `@Patch` in
54
+ * the module is on `platform-admin` (the operator console) and
55
+ * `platform-bot` (a message's blocks). So every `update` below is `[]`
56
+ * with an `updateBlocked` sentence, and a document that changes a field
57
+ * on an existing resource is reported as un-appliable rather than sent
58
+ * somewhere that would strip it and answer 200. This is the single most
59
+ * important fact about planning this surface and it is why the table
60
+ * reads as thin as it does.
61
+ *
62
+ * 2. **A revoked credential stays in the list.** `GET …/api_keys` says so in
63
+ * its own route description — "a revoked key stays listed, with
64
+ * `revokedAt` set" — and `listBots` orders BY `revokedAt` precisely
65
+ * because revoked rows are in the answer. Matching a document row to one
66
+ * would report `noop` for a credential that authenticates nothing: the
67
+ * plan would say the tenant matches the file while the pipeline the file
68
+ * describes is locked out. Hence `ignoreLive` on both.
69
+ *
70
+ * 3. **A knowledge source is written as `url` and read back as `seedUrl`.**
71
+ * `knowledgeSourceSchema` takes `url`; `KNOWLEDGE_SOURCE_FIELDS` returns
72
+ * `seedUrl`. Without the projection below, `url` compares against a field
73
+ * that does not exist, reports a change on every run, and — since there
74
+ * is no update route — is reported as permanently un-appliable. Exactly
75
+ * the `externalAgent` shape in the workspace table.
76
+ *
77
+ * 4. **`POST /v1/platform/workspaces` 409s on a taken slug.** It is a strict
78
+ * create and not an upsert (`createWorkspace` reads the slug and throws
79
+ * `SlugTakenError` before it writes anything), and `workspaces.slug` is
80
+ * unique ACROSS the installation rather than per reseller. So a slug is a
81
+ * real natural key, and `plan` can tell a create from a noop without a
82
+ * uuid — which is the property that makes a portable document possible.
83
+ *
84
+ * 5. **A soft-deleted workspace is not in the default list** and a SUSPENDED
85
+ * one is (`platform-workspace-read.controller.ts`: "omitting `?status=`
86
+ * still answers every workspace that is not soft-deleted — suspended ones
87
+ * included"). A suspended tenant therefore matches its document row and
88
+ * reports `noop`, which is correct: it exists, and there is no platform
89
+ * route that un-suspends it. `status` is `unwritable` so nobody can try.
90
+ *
91
+ * ═══ WHAT IS DELIBERATELY NOT HERE ════════════════════════════════════════
92
+ *
93
+ * `REFUSED_KEYS` below, by name, each with the reason. Login links are a
94
+ * mint, not a declaration. Channel config, IDV mode, business hours and
95
+ * routing WERE refused here because no platform route read them back; since
96
+ * `GET`/`POST /v1/platform/workspaces/{id}/settings` they are the `settings`
97
+ * row, and their old names point there.
98
+ */
99
+ import { type ResourceDef } from '../wac/resource-def.js';
100
+ export { addressOf } from '../wac/resource-def.js';
101
+ /**
102
+ * The synthetic natural key, on both sides. Never authored, never diffed.
103
+ *
104
+ * `document.ts`'s rule is that a field the server returns and no write path
105
+ * accepts must be refused by name, because zod strips it and answers 200. The
106
+ * address is not that — it exists on neither side of the wire — but a
107
+ * document that sets it is still a document whose author has misunderstood
108
+ * something, and the refusal is free. So it is in every `unwritable` list.
109
+ */
110
+ export declare const ADDRESS = "address";
111
+ /**
112
+ * Dependency order, and it is the array order.
113
+ *
114
+ * `workspaces` first and everything else after, because every other resource
115
+ * here names a workspace in its request PATH and a single `apply` may be
116
+ * about to create that workspace. `diff.ts` resolves the reference where it
117
+ * can and notes "this plan creates it first" where it cannot; `apply.ts`
118
+ * fills the path from the id the create actually returned, and skips the
119
+ * child outright if the parent failed.
120
+ */
121
+ export declare const PLATFORM_RESOURCES: ReadonlyArray<ResourceDef>;
122
+ export declare const PLATFORM_RESOURCE_TYPES: ReadonlyArray<string>;
123
+ export declare function platformResourceDef(type: string): ResourceDef | undefined;
124
+ /**
125
+ * Keys a platform document may NOT declare, refused by name with the reason
126
+ * rather than reported as "unknown resource type", which would send the
127
+ * reader hunting for a spelling mistake (`wac/document.ts` does the same for
128
+ * `channels:`).
129
+ *
130
+ * Three are CHT-F04's old top-level names, kept as pointers to the `settings`
131
+ * row that now plans them. The fourth is real: **a login link is a mint, not
132
+ * a declaration.** Its plaintext exists in one response and nowhere else,
133
+ * `GET /v1/platform/login_links` returns no token and no hash, and a document
134
+ * that "declared" one would mint a new single-use credential on every apply.
135
+ */
136
+ export declare const REFUSED_KEYS: Readonly<Record<string, string>>;
137
+ /** Every key `resources:` accepts, including the ones it refuses by name. */
138
+ export declare const PLATFORM_RESOURCE_KEYS: ReadonlyArray<string>;
139
+ /** The address of one child row, for a message or a key. */
140
+ export declare function childAddress(parentKey: string, childKey: string): string;
141
+ //# sourceMappingURL=platform-resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform-resources.d.ts","sourceRoot":"","sources":["../../src/pac/platform-resources.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiGG;AAEH,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAKnD;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,YAAY,CAAC;AAQjC;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAAa,CAAC,WAAW,CA6VzD,CAAC;AAEF,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC,MAAM,CAAyC,CAAC;AAIpG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEzE;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBzD,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,sBAAsB,EAAE,aAAa,CAAC,MAAM,CAGxD,CAAC;AAEF,4DAA4D;AAC5D,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAExE"}