@descix/cli 1.0.7 → 1.0.9

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 (43) hide show
  1. package/README.md +1 -1
  2. package/agent-assets/guides/README.md +1 -1
  3. package/agent-assets/guides/dynamic-site-pattern.md +7 -16
  4. package/agent-assets/instructions/app-asset-validation.md +1 -1
  5. package/agent-assets/reference/architecture.md +20 -25
  6. package/agent-assets/reference/local-dev.md +1 -6
  7. package/agent-assets/reference/mcp-integration.md +15 -9
  8. package/agent-assets/reference/platform-api-consumer.md +11 -5
  9. package/agent-assets/reference/workspace-config.md +2 -2
  10. package/bin/descix.js +211 -688
  11. package/lib/command-visibility.js +213 -0
  12. package/lib/commands/corpus.js +2 -5
  13. package/lib/commands/kb.js +2 -2
  14. package/lib/commands/media-upload.js +176 -0
  15. package/lib/commands/retired-kb-sync.js +5 -27
  16. package/lib/commands/status.js +0 -9
  17. package/lib/output.js +31 -0
  18. package/lib/sdk-assets.js +28 -0
  19. package/lib/verb-invokes.generated.json +233 -0
  20. package/lib/workspace-config.js +9 -44
  21. package/package.json +6 -2
  22. package/templates/agent-claude.md +1 -3
  23. package/templates/agent-cline.md +1 -2
  24. package/templates/agent-copilot.md +1 -2
  25. package/templates/agent-cursor.md +1 -2
  26. package/templates/scaffolds/microservice/SERVICE_README_sdk.md +1 -1
  27. package/templates/scaffolds/microservice/templates/SERVICE_README_TEMPLATE.md +2 -3
  28. package/agent-assets/rules/descix_mcp.template.mdc +0 -243
  29. package/lib/commands/briefer/errors.js +0 -88
  30. package/lib/commands/briefer/index.js +0 -322
  31. package/lib/commands/briefer/sources/canonical-sources.js +0 -129
  32. package/lib/commands/briefer/sources/entitlements.js +0 -220
  33. package/lib/commands/briefer/sources/environments.js +0 -205
  34. package/lib/commands/briefer/sources/identifiers.js +0 -214
  35. package/lib/commands/briefer/sources/microservice-deploy.js +0 -267
  36. package/lib/commands/briefer/sources/routing.js +0 -275
  37. package/lib/commands/briefer/sources/what-is-not.js +0 -223
  38. package/lib/commands/briefer/stitcher.js +0 -134
  39. package/lib/commands/briefer/util/source-reader.js +0 -502
  40. package/lib/commands/mcp.js +0 -273
  41. package/lib/commands/update.js +0 -581
  42. package/lib/wizard/setup.js +0 -585
  43. package/templates/descix_mcp.mdc +0 -122
@@ -1,129 +0,0 @@
1
- /**
2
- * §7 — Where to look for canonical answers extractor (M2 implementation).
3
- *
4
- * Per WS-DESCIX-BRIEFER-CLI scope doc §2.2 §7, this section is PARTIALLY
5
- * DERIVED, PARTIALLY EDITORIAL:
6
- * - "Question → Canonical source" rows verify each cited path resolves.
7
- * - The "Do NOT trust" list is editorial cruft inventory, optionally read
8
- * from a JSON sidecar (.briefer-cruft-inventory.json) — if absent, a
9
- * minimal default list (the docs called out in the canonical briefer
10
- * yesterday) is rendered.
11
- *
12
- * Files cited as canonical sources (all must exist at extract time):
13
- * - provision-platform-lb.js (routing source)
14
- * - serviceManifestManager.js (dispatch source)
15
- * - entitlements/index.js, communityManagement.js, bootstrap.js (Products)
16
- * - dev/gateway.js, createViteProxyConfig.js (local dev)
17
- * - descix-cloud-core/src/config.js (bootstrap precedence)
18
- * - platform-runtime-mental-model.md (env parallelism + cutover)
19
- */
20
- import fs from 'fs/promises';
21
- import path from 'path';
22
- import {
23
- readSourceFile,
24
- makeCitation,
25
- resolveRepoPath
26
- } from '../util/source-reader.js';
27
- import { BrieferExtractorError, BRIEFER_ERROR_CODES } from '../errors.js';
28
-
29
- export const SECTION = {
30
- number: 7,
31
- heading: '7. Where to look for canonical answers',
32
- sourceFiles: [
33
- 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/provision-platform-lb.js',
34
- 'DeSciX/DeSciX_Cloud/microservice/services/serviceManifestManager.js',
35
- 'DeSciX/DeSciX_Core/descix-platform-api/src/entitlements/index.js',
36
- 'DeSciX/DeSciX_Cloud/microservice/services/communityManagement.js',
37
- 'DeSciX/DeSciX_Cloud/microservice/admin/bootstrap.js',
38
- 'DeSciX/DeSciX_Core/descix-app-sdk/src/dev/gateway.js',
39
- 'DeSciX/DeSciX_Core/descix-app-sdk/src/dev/createViteProxyConfig.js',
40
- 'DeSciX/DeSciX_Core/descix-cloud-core/src/config.js',
41
- 'DeSciX/V2_docs/architecture/platform-runtime-mental-model.md'
42
- ]
43
- };
44
-
45
- const CRUFT_SIDECAR = 'DeSciX/V2_docs/architecture/.briefer-cruft-inventory.json';
46
-
47
- export async function extract({ env, cliPaths } = {}) {
48
- if (!env) {
49
- throw new BrieferExtractorError({
50
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
51
- section: `§${SECTION.number} ${SECTION.heading}`,
52
- source: 'extractor argument',
53
- expected: 'env (dev|demo|prod) provided by caller',
54
- recovery: 'Pass {env} from the briefer entry point.'
55
- });
56
- }
57
- if (!cliPaths) {
58
- throw new BrieferExtractorError({
59
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
60
- section: `§${SECTION.number} ${SECTION.heading}`,
61
- source: 'extractor argument',
62
- expected: 'cliPaths provided by caller',
63
- recovery: 'Pass {cliPaths} from the briefer entry point.'
64
- });
65
- }
66
-
67
- // Verify each cited canonical source actually exists. HARD-FAIL with
68
- // BRIEFER-SRC-NOT-FOUND on the FIRST missing file — surfaces drift early.
69
- const citations = [];
70
- for (const rel of SECTION.sourceFiles) {
71
- const file = await readSourceFile({
72
- cliPaths,
73
- relPath: rel,
74
- section: `§${SECTION.number} ${SECTION.heading}`
75
- });
76
- citations.push(makeCitation({
77
- file: rel,
78
- lines: `1-${file.lines.length}`,
79
- anchor: 'canonical source',
80
- fileLines: file.lines
81
- }));
82
- }
83
-
84
- // Cruft sidecar — optional. If present, render the entries; if absent, render
85
- // the default minimal cruft list from yesterday's briefer.
86
- let cruftRows;
87
- let cruftSource = null;
88
- try {
89
- const sidecarAbs = resolveRepoPath(cliPaths, CRUFT_SIDECAR);
90
- const raw = await fs.readFile(sidecarAbs, 'utf-8');
91
- const data = JSON.parse(raw);
92
- cruftRows = (data.entries || []).map(e => `- ${e.path || e.file}${e.note ? ` — ${e.note}` : ''}`);
93
- cruftSource = CRUFT_SIDECAR;
94
- } catch (err) {
95
- // Sidecar absent or malformed — fall back to the canonical-briefer's list
96
- // for prose continuity. This is NOT a value-fallback (per
97
- // feedback_no-hardcoded-fallbacks); it's the documented default rendering
98
- // until the sidecar lands.
99
- cruftRows = [
100
- '- `unified-product-registry.md` routing section',
101
- '- `cloud-lb-decentralized-routing.md` table without host-dim context',
102
- '- Any cloud-KB chunk that claims runtime-Gateway / runtime-Firestore-lookup-for-routing'
103
- ];
104
- }
105
-
106
- const markdown = [
107
- `Trust order for any new architectural question:`,
108
- ``,
109
- `1. **Code with \`file:line\` cite.** Always wins.`,
110
- `2. \`DeSciX/V2_docs/architecture/platform-runtime-mental-model.md\` — closest to a code-grounded reference doc.`,
111
- `3. \`DeSciX/CLAUDE.md\` — mostly right (note: any "load balancers look up by app_id" phrasing is misleading; the LB's host rules + path matchers do the routing, not a runtime lookup).`,
112
- `4. \`DeSciX/V2_docs/architecture/split-stack-production-architecture.md\` — signal.`,
113
- ``,
114
- `**Do NOT trust** (cruft inventory${cruftSource ? `, from \`${cruftSource}\`` : ', default list — install `.briefer-cruft-inventory.json` to override'}):`,
115
- ``,
116
- cruftRows.join('\n'),
117
- ``,
118
- `| Question | Canonical source (in this order) | Notes |`,
119
- `|---|---|---|`,
120
- `| How does prod LB route? | \`DeSciX_Cloud/microservice/admin/scripts/deploy/provision-platform-lb.js\`; verify with \`gcloud compute url-maps describe descix-discord-app-lb\` | Code = signal |`,
121
- `| How does the env command-broker dispatch external commands? | \`services/serviceManifestManager.js\` (\`proxyToExternalService\`) and \`services/apiFront.js\` | Code = signal |`,
122
- `| What is Products for? | \`descix-platform-api/src/entitlements/index.js\`, \`services/communityManagement.js\`, \`admin/bootstrap.js\` | Code = signal |`,
123
- `| How does local \`descix serve\` work? | \`descix-app-sdk/src/dev/gateway.js\` + \`createViteProxyConfig.js\` | Code = signal |`,
124
- `| Bootstrap & config precedence? | \`descix-cloud-core/src/config.js\` | Code = signal |`,
125
- `| Environment parallelism & PROD cutover? | \`platform-runtime-mental-model.md\` | Distilled architectural doc |`
126
- ].join('\n');
127
-
128
- return { markdown, citations };
129
- }
@@ -1,220 +0,0 @@
1
- /**
2
- * §5 — Entitlements & Products Firestore extractor (M2 implementation).
3
- *
4
- * Per WS-DESCIX-BRIEFER-CLI scope doc §2.2 §5 + M2 dispatch §6.7 extension:
5
- * - Products schema fields + readers — cite hydrateCommunityIdFromProducts
6
- * in @descix/platform-api/src/entitlements/index.js + the apiFront
7
- * middleware that invokes it (communityManagement.js re-export site).
8
- * - Writers — grep DeSciX_Cloud/microservice/admin/ for Products write
9
- * patterns (bootstrap.js hydrate-env + confirm_site_deploy).
10
- * - App-level config layer (§6.7 extension) — cite the resolver chain
11
- * in geminiInteractions.js::resolveModelThinkingPair: request level → kb_model_override
12
- * → default_app_model → levelConfig → DEFAULT_AI_MODEL.
13
- *
14
- * NO direct @google-cloud/firestore or @pinecone-database/pinecone imports
15
- * (AC-7 anti-regression). All Firestore touches in the briefer code go
16
- * through apifront or @descix/cloud-core wrappers.
17
- */
18
- import {
19
- readSourceFile,
20
- findInLines,
21
- makeCitation,
22
- grepFiles,
23
- probeFirestoreRest
24
- } from '../util/source-reader.js';
25
- import { BrieferExtractorError, BRIEFER_ERROR_CODES } from '../errors.js';
26
- // The (model, thinking) precedence chain in words, from its ONE owner. The briefer
27
- // QUOTES the served contract rather than paraphrasing it — a briefer paraphrase of a
28
- // chain is exactly how the old five-step list outlived the code it described.
29
- import { MODEL_THINKING_CHAIN } from '@descix/platform-api/mcp-tools';
30
-
31
- export const SECTION = {
32
- number: 5,
33
- heading: '5. Entitlements & Products Firestore',
34
- sourceFiles: [
35
- 'DeSciX/DeSciX_Core/descix-platform-api/src/entitlements/index.js',
36
- 'DeSciX/DeSciX_Cloud/microservice/services/communityManagement.js',
37
- 'DeSciX/DeSciX_Cloud/microservice/admin/bootstrap.js',
38
- 'DeSciX/DeSciX_Cloud/microservice/services/geminiInteractions.js'
39
- ]
40
- };
41
-
42
- const ENTITLEMENTS_FILE = 'DeSciX/DeSciX_Core/descix-platform-api/src/entitlements/index.js';
43
- const CM_FILE = 'DeSciX/DeSciX_Cloud/microservice/services/communityManagement.js';
44
- const BOOTSTRAP_FILE = 'DeSciX/DeSciX_Cloud/microservice/admin/bootstrap.js';
45
- const GEMINI_FILE = 'DeSciX/DeSciX_Cloud/microservice/services/geminiInteractions.js';
46
-
47
- export async function extract({ env, cliPaths } = {}) {
48
- if (!env) {
49
- throw new BrieferExtractorError({
50
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
51
- section: `§${SECTION.number} ${SECTION.heading}`,
52
- source: 'extractor argument',
53
- expected: 'env (dev|demo|prod) provided by caller',
54
- recovery: 'Pass {env} from the briefer entry point.'
55
- });
56
- }
57
- if (!cliPaths) {
58
- throw new BrieferExtractorError({
59
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
60
- section: `§${SECTION.number} ${SECTION.heading}`,
61
- source: 'extractor argument',
62
- expected: 'cliPaths provided by caller',
63
- recovery: 'Pass {cliPaths} from the briefer entry point.'
64
- });
65
- }
66
-
67
- // ── Source 1: entitlements/index.js — hydrateCommunityIdFromProducts ──
68
- const ent = await readSourceFile({
69
- cliPaths,
70
- relPath: ENTITLEMENTS_FILE,
71
- section: `§${SECTION.number} ${SECTION.heading}`
72
- });
73
- const hydrateMatch = findInLines({
74
- lines: ent.lines,
75
- regex: /export\s+async\s+function\s+hydrateCommunityIdFromProducts\b/,
76
- section: `§${SECTION.number} ${SECTION.heading}`,
77
- source: `${ENTITLEMENTS_FILE} (hydrateCommunityIdFromProducts)`,
78
- expected: 'export async function hydrateCommunityIdFromProducts(params)',
79
- recovery: `Re-locate hydrateCommunityIdFromProducts in ${ENTITLEMENTS_FILE}.`
80
- });
81
-
82
- // ── Source 2: communityManagement.js — re-export site ──
83
- const cm = await readSourceFile({
84
- cliPaths,
85
- relPath: CM_FILE,
86
- section: `§${SECTION.number} ${SECTION.heading}`
87
- });
88
- // The symbol is matched WITHIN an export list rather than as the list's sole member. The old
89
- // pattern anchored on the closing brace immediately after the name, so it broke the moment a
90
- // second symbol was re-exported beside it — the extractor reported the re-export missing when
91
- // it was present and one token wider.
92
- //
93
- // HONEST LIMITATION, stated rather than implied: this is still textual matching, so it remains a
94
- // derivation of a fact the module itself owns. The robust fix is to consume the owner's actual
95
- // exports, but this extractor cannot import communityManagement.js — that would pull the whole
96
- // Cloud service graph (config bootstrap, Firestore) into the CLI. Matching the identifier inside
97
- // any export list is what survives a re-ordering; a move to `export * from` would still need
98
- // this line changed.
99
- const reExportMatch = findInLines({
100
- lines: cm.lines,
101
- regex: /export\s*\{[^}]*\bhydrateCommunityIdFromProducts\b/,
102
- section: `§${SECTION.number} ${SECTION.heading}`,
103
- source: `${CM_FILE} (hydrate re-export)`,
104
- expected: 'hydrateCommunityIdFromProducts named in an export list',
105
- recovery: `Verify ${CM_FILE} still re-exports hydrateCommunityIdFromProducts.`
106
- });
107
-
108
- // ── Source 3: bootstrap.js — hydrate-env writer ──
109
- const bootstrap = await readSourceFile({
110
- cliPaths,
111
- relPath: BOOTSTRAP_FILE,
112
- section: `§${SECTION.number} ${SECTION.heading}`
113
- });
114
- const hydrateEnvMatch = findInLines({
115
- lines: bootstrap.lines,
116
- regex: /hydrate-env|hydrateEnv|hydrate_env/,
117
- section: `§${SECTION.number} ${SECTION.heading}`,
118
- source: `${BOOTSTRAP_FILE} (hydrate-env writer)`,
119
- expected: 'hydrate-env subcommand or hydrateEnv function declaration',
120
- recovery: `Re-locate the hydrate-env writer in ${BOOTSTRAP_FILE}.`
121
- });
122
-
123
- // ── Source 4: geminiInteractions.js — the (model, thinking) pair owner (§6.7 link) ──
124
- const gemini = await readSourceFile({
125
- cliPaths,
126
- relPath: GEMINI_FILE,
127
- section: `§${SECTION.number} ${SECTION.heading}`
128
- });
129
- const resolverMatch = findInLines({
130
- lines: gemini.lines,
131
- regex: /export\s+function\s+resolveModelThinkingPair\b/,
132
- section: `§${SECTION.number} ${SECTION.heading}`,
133
- source: `${GEMINI_FILE} (resolveModelThinkingPair)`,
134
- expected: 'export function resolveModelThinkingPair(...)',
135
- recovery: `Re-locate resolveModelThinkingPair in ${GEMINI_FILE}.`
136
- });
137
-
138
- // ── Optional grep: count Products readers across services/ ──
139
- let runtimeReadersList = '';
140
- const readers = await grepFiles({
141
- repoRoot: cliPaths.repoRoot,
142
- relPaths: ['DeSciX/DeSciX_Cloud/microservice/services'],
143
- regex: /Products\.doc\(|collection\(['"]Products['"]\)|getProductContext/,
144
- excludePaths: new Set(['/node_modules/', '/.git/'])
145
- });
146
- if (readers.length > 0) {
147
- const unique = [...new Set(readers.map(r => r.file))].slice(0, 8);
148
- runtimeReadersList = unique.map(f => ` - \`${f}\``).join('\n');
149
- }
150
-
151
- // ── M3: live Firestore Products probe (HARD-FAIL on demo/prod, skipped on dev) ──
152
- // Uses Firestore REST API via gcloud-derived access token. Per AC-7
153
- // anti-regression: no @google-cloud/firestore import — this is a different
154
- // API surface (REST + gcloud-CLI token), explicitly permitted.
155
- const probeCitations = [];
156
- let productsLiveNote = '';
157
- if (env === 'dev') {
158
- productsLiveNote = '\n_DEV: Firestore probe skipped (local dev — no cloud Firestore for the descix-dev database in this regen scope)._\n';
159
- } else {
160
- const productsProbe = await probeFirestoreRest({
161
- dbPath: `descix-${env}/documents/Products`,
162
- query: { pageSize: 200, 'mask.fieldPaths': 'app_id' },
163
- env,
164
- section: `§${SECTION.number} ${SECTION.heading}`,
165
- anchor: 'products-collection-list',
166
- expected: `Firestore Products list for env=${env}`,
167
- recovery: `Run 'gcloud auth login' and ensure the account has Firestore read scope. The Products collection cross-check is required for env=${env}.`
168
- });
169
- const docs = (productsProbe.json && productsProbe.json.documents) || [];
170
- const names = docs
171
- .map(d => (d.name || '').split('/').pop())
172
- .filter(Boolean)
173
- .sort();
174
- const sample = names.slice(0, 8);
175
- productsLiveNote = [
176
- '',
177
- `**Live Products collection** (probed \`Firestore REST listDocuments\` against \`descix-${env}/documents/Products\`):`,
178
- `- Docs returned: \`${names.length}\` (pageSize=200; further pages not enumerated this milestone)`,
179
- `- Sample app_ids: ${sample.length > 0 ? sample.map(n => '\`' + n + '\`').join(', ') : '_(none)_'}${names.length > sample.length ? ` (+ ${names.length - sample.length} more)` : ''}`,
180
- ''
181
- ].join('\n');
182
- probeCitations.push(productsProbe.citation);
183
- }
184
-
185
- // ── Render markdown ──
186
- const markdown = [
187
- `\`Products/{app_id}\` in \`descix-{env}\` Firestore holds:`,
188
- `- \`productPath\`: \`Community/{community_id}/Apps/{app_id}\` (operational path)`,
189
- `- \`gcsPath\`: \`gs://descix-assets-public/{env}/{app_id}/\``,
190
- `- \`ip_site_gcs_path_url\`: subdomain URL (set by \`confirm_site_deploy\` AFTER site deploy)`,
191
- `- \`community_id\` (operational metadata)`,
192
- `- \`type\`, \`price\`, \`metadata\``,
193
- ``,
194
- `**Readers at runtime:** \`hydrateCommunityIdFromProducts()\` middleware (canonical at \`${ENTITLEMENTS_FILE}:${hydrateMatch.lineNumber}\`, re-exported at \`${CM_FILE}:${reExportMatch.lineNumber}\`), purchase / entitlement handlers, app-metadata handlers. **ALL AT THE APIFRONT-OR-DOWNSTREAM LAYER.** **NEVER AT THE LB.**`,
195
- ...(runtimeReadersList ? ['', `_Grep of \`services/\` for Products readers:_`, runtimeReadersList] : []),
196
- ``,
197
- `**Writers:** \`${BOOTSTRAP_FILE}\` \`hydrate-env\` (\`:${hydrateEnvMatch.lineNumber}\`), \`confirm_site_deploy\`, migration scripts.`,
198
- ``,
199
- `**Consumer at deploy time:** Products drives bootstrap; platform peer hosts are configured in \`provision-platform-lb.js\` (\`PLATFORM_PEER_APPS\`), not read from Products at deploy time.`,
200
- ``,
201
- `### App-level config layer (§6.7 — added 2026-05-26)`,
202
- ``,
203
- `App-level and KB-level config flows from Firestore (\`Community/{c}/Apps/{a}\` for \`default_app_model\`, \`Community/{c}/Apps/{a}/KnowledgeBases/{k}\` for \`kb_model_override\`) into \`resolveModelThinkingPair()\` at \`${GEMINI_FILE}:${resolverMatch.lineNumber}\` — the ONE owner of the (model, thinking) pair. Both fields are **opt-in** — null means the tier below applies.`,
204
- ``,
205
- MODEL_THINKING_CHAIN,
206
- ``,
207
- `\`app.default_app_model\` no longer shadows an intelligence_level named on the request: a level names a (model, thinking) PAIR and outranks it. It still outranks the KB's own configured level and the platform default. Apps that want per-level routing leave \`default_app_model = null/absent\`.`,
208
- productsLiveNote
209
- ].join('\n');
210
-
211
- const citations = [
212
- makeCitation({ file: ENTITLEMENTS_FILE, lines: String(hydrateMatch.lineNumber), anchor: 'hydrateCommunityIdFromProducts (canonical)', fileLines: ent.lines }),
213
- makeCitation({ file: CM_FILE, lines: String(reExportMatch.lineNumber), anchor: 'hydrate re-export', fileLines: cm.lines }),
214
- makeCitation({ file: BOOTSTRAP_FILE, lines: String(hydrateEnvMatch.lineNumber), anchor: 'hydrate-env writer', fileLines: bootstrap.lines }),
215
- makeCitation({ file: GEMINI_FILE, lines: String(resolverMatch.lineNumber), anchor: 'resolveModelThinkingPair (app-level config bridge)', fileLines: gemini.lines }),
216
- ...probeCitations
217
- ];
218
-
219
- return { markdown, citations };
220
- }
@@ -1,205 +0,0 @@
1
- /**
2
- * §2 — Environments extractor (M2 implementation).
3
- *
4
- * Per WS-DESCIX-BRIEFER-CLI scope doc §2.2 §2, this extractor reads:
5
- * - DEV/DEMO/PROD parallelism — architectural fact distilled from
6
- * V2_docs/architecture/platform-runtime-mental-model.md (cite the doc).
7
- * - ENV_CONFIG block from provision-platform-lb.js:32-47.
8
- * - DEPLOY_ENV-bound-once hard-fail from descix-cloud-core/src/config.js
9
- * (locate dynamically; canonical briefer line range is allowed to drift).
10
- * - Secret Manager config name pattern from DeSciX_Cloud/CLAUDE.md (cite the
11
- * doc since the values are env-keyed and live in env-vars/secrets, not
12
- * code) — locate the "Secret Manager — Per-Environment Isolation" table.
13
- *
14
- * Optional gcloud verification: list Cloud Function names matching `apiFront-http`
15
- * and Firestore database names matching `descix-{env}` / `descix`.
16
- */
17
- import {
18
- readSourceFile,
19
- findInLines,
20
- makeCitation,
21
- sliceRange,
22
- probeGcloudJson
23
- } from '../util/source-reader.js';
24
- import { BrieferExtractorError, BRIEFER_ERROR_CODES } from '../errors.js';
25
-
26
- export const SECTION = {
27
- number: 2,
28
- heading: '2. Environments',
29
- sourceFiles: [
30
- 'DeSciX/V2_docs/architecture/platform-runtime-mental-model.md',
31
- 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/provision-platform-lb.js',
32
- 'DeSciX/DeSciX_Core/descix-cloud-core/src/config.js',
33
- 'DeSciX/DeSciX_Cloud/CLAUDE.md'
34
- ]
35
- };
36
-
37
- const RUNTIME_MM = 'DeSciX/V2_docs/architecture/platform-runtime-mental-model.md';
38
- const MESH_FILE = 'DeSciX/DeSciX_Cloud/microservice/admin/scripts/deploy/provision-platform-lb.js';
39
- const CONFIG_FILE = 'DeSciX/DeSciX_Core/descix-cloud-core/src/config.js';
40
- const CLOUD_CLAUDE = 'DeSciX/DeSciX_Cloud/CLAUDE.md';
41
-
42
- export async function extract({ env, cliPaths } = {}) {
43
- if (!env) {
44
- throw new BrieferExtractorError({
45
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
46
- section: `§${SECTION.number} ${SECTION.heading}`,
47
- source: 'extractor argument',
48
- expected: 'env (dev|demo|prod) provided by caller',
49
- recovery: 'Pass {env} from the briefer entry point.'
50
- });
51
- }
52
- if (!cliPaths) {
53
- throw new BrieferExtractorError({
54
- code: BRIEFER_ERROR_CODES.NOT_IMPLEMENTED,
55
- section: `§${SECTION.number} ${SECTION.heading}`,
56
- source: 'extractor argument',
57
- expected: 'cliPaths provided by caller',
58
- recovery: 'Pass {cliPaths} from the briefer entry point.'
59
- });
60
- }
61
-
62
- // ── Source 1: ENV_CONFIG in provision-platform-lb.js ──
63
- const mesh = await readSourceFile({
64
- cliPaths,
65
- relPath: MESH_FILE,
66
- section: `§${SECTION.number} ${SECTION.heading}`
67
- });
68
- const envConfigMatch = findInLines({
69
- lines: mesh.lines,
70
- regex: /const\s+ENV_CONFIG\s*=\s*\{/,
71
- section: `§${SECTION.number} ${SECTION.heading}`,
72
- source: `${MESH_FILE} (ENV_CONFIG)`,
73
- expected: 'const ENV_CONFIG = { prod: {...}, demo: {...}, dev: {...} }',
74
- recovery: `Re-locate the ENV_CONFIG construct in ${MESH_FILE}.`,
75
- expectedRange: [20, 60]
76
- });
77
- const envConfigEnd = findObjectEnd(mesh.lines, envConfigMatch.lineNumber);
78
- const envConfigBlock = sliceRange(mesh.lines, envConfigMatch.lineNumber, envConfigEnd);
79
-
80
- // ── Source 2: config.js — DEPLOY_ENV-bound-once hard-fail ──
81
- const config = await readSourceFile({
82
- cliPaths,
83
- relPath: CONFIG_FILE,
84
- section: `§${SECTION.number} ${SECTION.heading}`
85
- });
86
- const deployEnvBoundMatch = findInLines({
87
- lines: config.lines,
88
- regex: /FATAL:\s*DEPLOY_ENV\s+not\s+set/,
89
- section: `§${SECTION.number} ${SECTION.heading}`,
90
- source: `${CONFIG_FILE} (DEPLOY_ENV bound-once hard-fail)`,
91
- expected: 'throw with message starting "[CloudConfig] FATAL: DEPLOY_ENV not set"',
92
- recovery: `Re-locate the DEPLOY_ENV hard-fail in ${CONFIG_FILE}.`
93
- });
94
-
95
- // ── Source 3: runtime-mental-model.md doc cite (distilled architectural fact) ──
96
- // We just verify the doc exists + record SHA of the canonical range. The doc
97
- // is the source for "DEV/DEMO/PROD are fully parallel" because that's an
98
- // assertion spread across many code paths — too noisy to grep.
99
- const rmm = await readSourceFile({
100
- cliPaths,
101
- relPath: RUNTIME_MM,
102
- section: `§${SECTION.number} ${SECTION.heading}`
103
- });
104
-
105
- // ── Source 4: Cloud CLAUDE.md Secret Manager table ──
106
- const claude = await readSourceFile({
107
- cliPaths,
108
- relPath: CLOUD_CLAUDE,
109
- section: `§${SECTION.number} ${SECTION.heading}`
110
- });
111
- const secretTableMatch = findInLines({
112
- lines: claude.lines,
113
- regex: /descix_config_demo/,
114
- section: `§${SECTION.number} ${SECTION.heading}`,
115
- source: `${CLOUD_CLAUDE} (Secret Manager Per-Environment Isolation table)`,
116
- expected: 'descix_config_demo row in the per-env secret table',
117
- recovery: `Re-locate the Secret Manager isolation table in ${CLOUD_CLAUDE}.`
118
- });
119
-
120
- // ── M3: live gcloud probes (HARD-FAIL on demo/prod, skipped on dev) ──
121
- const probeCitations = [];
122
- let gcloudCfNote = '';
123
- let gcloudFsNote = '';
124
-
125
- if (env === 'dev') {
126
- // DEV is local. Probe is intentionally skipped; emit a stanza so the
127
- // operator knows live state was not verified for this regen.
128
- gcloudCfNote = '\n_DEV: gcloud probes skipped (local dev environment — no Cloud Functions or Firestore to probe)._\n';
129
- } else {
130
- const cfProbe = await probeGcloudJson({
131
- command: ['functions', 'list', '--v2', '--format=json'],
132
- env,
133
- section: `§${SECTION.number} ${SECTION.heading}`,
134
- anchor: 'cloud-functions-list',
135
- expected: 'JSON array of gen2 Cloud Functions for project descix',
136
- recovery: `Run 'gcloud auth login' and 'gcloud config set project descix'. The Cloud Functions list cross-check is required for env=${env}.`
137
- });
138
- const apiFrontCfs = (cfProbe.json || [])
139
- .filter(f => /apiFront-http/.test(f.name || ''))
140
- .map(f => (f.name || '').split('/').pop());
141
- gcloudCfNote = `\n_Live Cloud Functions matching \`apiFront-http\` (probed \`${cfProbe.citation.probe}\`, env=\`${env}\`):_ ${apiFrontCfs.length > 0 ? apiFrontCfs.map(n => '\`' + n + '\`').join(', ') : '_(none found)_'}\n`;
142
- probeCitations.push(cfProbe.citation);
143
-
144
- const fsProbe = await probeGcloudJson({
145
- command: ['firestore', 'databases', 'list', '--format=json'],
146
- env,
147
- section: `§${SECTION.number} ${SECTION.heading}`,
148
- anchor: 'firestore-databases-list',
149
- expected: 'JSON array of Firestore databases for project descix',
150
- recovery: `Run 'gcloud auth login' and 'gcloud config set project descix'. The Firestore databases cross-check is required for env=${env}.`
151
- });
152
- const dbs = (fsProbe.json || [])
153
- .map(d => (d.name || '').split('/').pop())
154
- .filter(n => /^descix/.test(n));
155
- gcloudFsNote = `\n_Live Firestore databases matching \`descix*\` (probed \`${fsProbe.citation.probe}\`, env=\`${env}\`):_ ${dbs.length > 0 ? dbs.map(n => '\`' + n + '\`').join(', ') : '_(none found)_'}\n`;
156
- probeCitations.push(fsProbe.citation);
157
- }
158
-
159
- // ── Render markdown ──
160
- const markdown = [
161
- `DEV / DEMO / PROD are **fully parallel cloud deployments.** They co-exist; PROD does not replace DEMO. — \`${RUNTIME_MM}\``,
162
- ``,
163
- `Per env, isolated:`,
164
- `- Cloud Function: \`apiFront-http-{env}\` (and \`apiFront-http\` for PROD)`,
165
- `- Firestore DB: \`descix-{env}\` (and \`descix\` for PROD)`,
166
- `- Secret Manager config: \`descix_config_{env}\` (DEV/DEMO) or \`descix_config\` with version aliases (PROD/PREVIEW) — \`${CLOUD_CLAUDE}:${secretTableMatch.lineNumber}\``,
167
- `- GCS bucket prefix: \`{env}/\``,
168
- `- LB host suffix: \`.{env}.descix.net\` for DEV/DEMO, \`.descix.net\` for PROD`,
169
- ``,
170
- `ENV_CONFIG (the deploy-time env table — \`${MESH_FILE}:${envConfigMatch.lineNumber}-${envConfigEnd}\`):`,
171
- ``,
172
- '```js',
173
- envConfigBlock,
174
- '```',
175
- ``,
176
- `One process serves exactly one env, determined by \`DEPLOY_ENV\`. The cloud-core bootstrap **hard-fails** at construct time if \`DEPLOY_ENV\` is unset — there is no default. — \`${CONFIG_FILE}:${deployEnvBoundMatch.lineNumber}\``,
177
- gcloudCfNote,
178
- gcloudFsNote
179
- ].join('\n');
180
-
181
- const citations = [
182
- makeCitation({ file: MESH_FILE, lines: `${envConfigMatch.lineNumber}-${envConfigEnd}`, anchor: 'ENV_CONFIG', fileLines: mesh.lines }),
183
- makeCitation({ file: CONFIG_FILE, lines: String(deployEnvBoundMatch.lineNumber), anchor: 'DEPLOY_ENV bound-once', fileLines: config.lines }),
184
- makeCitation({ file: RUNTIME_MM, lines: `1-${rmm.lines.length}`, anchor: 'DEV/DEMO/PROD parallelism', fileLines: rmm.lines }),
185
- makeCitation({ file: CLOUD_CLAUDE, lines: String(secretTableMatch.lineNumber), anchor: 'Secret Manager per-env isolation', fileLines: claude.lines }),
186
- ...probeCitations
187
- ];
188
-
189
- return { markdown, citations };
190
- }
191
-
192
- function findObjectEnd(lines, startLine) {
193
- const MAX = Math.min(lines.length, startLine + 50);
194
- let depth = 0;
195
- for (let i = startLine - 1; i < MAX; i++) {
196
- for (const ch of lines[i]) {
197
- if (ch === '{') depth++;
198
- else if (ch === '}') {
199
- depth--;
200
- if (depth === 0) return i + 1;
201
- }
202
- }
203
- }
204
- return Math.min(lines.length, startLine + 30);
205
- }