@feltdb/core 0.7.4 → 0.8.0

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 (106) hide show
  1. package/README.md +25 -0
  2. package/dist/application-development.d.ts +3 -1
  3. package/dist/application-development.d.ts.map +1 -1
  4. package/dist/application-development.js +16 -0
  5. package/dist/application-manifest.d.ts +1 -0
  6. package/dist/application-manifest.d.ts.map +1 -1
  7. package/dist/canonical-application.d.ts +1 -0
  8. package/dist/canonical-application.d.ts.map +1 -1
  9. package/dist/canonical-application.js +3 -2
  10. package/dist/cli/cli.js +1 -1
  11. package/dist/cli/commands.js +225 -42
  12. package/dist/cli/index.js +1 -1
  13. package/dist/cli/managed-environment.js +6 -0
  14. package/dist/cli/managed-project.js +6 -0
  15. package/dist/cli/proposal-client.js +12 -3
  16. package/dist/cli/publish-engine.js +7 -4
  17. package/dist/cli/source-sync.js +47 -3
  18. package/dist/create/cli.js +10 -1
  19. package/dist/create/create.js +10 -12
  20. package/dist/create/managed-account.js +71 -7
  21. package/dist/create/package-versions.js +1 -1
  22. package/dist/create/server-source/crates/feltdb/src/application.rs +10 -0
  23. package/dist/create/server-source/crates/feltdb/src/lib.rs +20 -2
  24. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +12 -24
  25. package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +11 -3
  26. package/dist/create/server-source/crates/feltdb-server/src/authenticated_principal.rs +0 -1
  27. package/dist/create/server-source/crates/feltdb-server/src/certification_harness.rs +23 -22
  28. package/dist/create/server-source/crates/feltdb-server/src/delegation_token.rs +5 -15
  29. package/dist/create/server-source/crates/feltdb-server/src/durable_operations.rs +13 -28
  30. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +79 -14
  31. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +28 -7
  32. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +5 -5
  33. package/dist/create/server-source/crates/feltdb-server/src/main.rs +1777 -259
  34. package/dist/create/server-source/crates/feltdb-server/src/managed_diagnostics.rs +10 -30
  35. package/dist/create/server-source/crates/feltdb-server/src/membership_policy.rs +12 -4
  36. package/dist/create/server-source/crates/feltdb-server/src/request_telemetry.rs +8 -6
  37. package/dist/create/server-source/crates/feltdb-server/src/snapshot_cursor.rs +10 -14
  38. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +183 -21
  39. package/dist/create/server-source/crates/feltdb-server/src/tenant_policies.rs +9 -21
  40. package/dist/create/server-source/crates/feltdb-server/src/transaction_idempotency.rs +5 -3
  41. package/dist/create/server-source/crates/feltdb-server/src/transaction_recovery.rs +9 -8
  42. package/dist/create/server-source/crates/feltdb-server/tests/revision_recovery_integration_test.rs +1 -4
  43. package/dist/create/template/default-project/feltdb.flow +5 -0
  44. package/dist/flowspec.d.ts +1 -0
  45. package/dist/flowspec.d.ts.map +1 -1
  46. package/dist/flowspec.js +46 -12
  47. package/dist/http-client.d.ts +135 -14
  48. package/dist/http-client.d.ts.map +1 -1
  49. package/dist/http-client.js +79 -31
  50. package/dist/index-core.d.ts +2 -0
  51. package/dist/index-core.d.ts.map +1 -1
  52. package/dist/index-core.js +2 -0
  53. package/dist/module-intent.d.ts +37 -0
  54. package/dist/module-intent.d.ts.map +1 -0
  55. package/dist/module-intent.js +81 -0
  56. package/dist/module.d.ts +76 -0
  57. package/dist/module.d.ts.map +1 -0
  58. package/dist/module.js +61 -0
  59. package/dist/proposal.d.ts +80 -4
  60. package/dist/proposal.d.ts.map +1 -1
  61. package/dist/proposal.js +132 -5
  62. package/dist/studio/app.d.ts +3 -1
  63. package/dist/studio/app.d.ts.map +1 -1
  64. package/dist/studio/components/AskFeltDB.d.ts +2 -1
  65. package/dist/studio/components/AskFeltDB.d.ts.map +1 -1
  66. package/dist/studio/components/ContractInspector.d.ts +7 -0
  67. package/dist/studio/components/ContractInspector.d.ts.map +1 -0
  68. package/dist/studio/components/ManagedInstancePanel.d.ts.map +1 -1
  69. package/dist/studio/components/ManagedInstancePanel.js +1 -5
  70. package/dist/studio/components/ModuleExplorer.d.ts +6 -0
  71. package/dist/studio/components/ModuleExplorer.d.ts.map +1 -0
  72. package/dist/studio/components/ProposalBrowser.d.ts +10 -1
  73. package/dist/studio/components/ProposalBrowser.d.ts.map +1 -1
  74. package/dist/studio/components/StateExplorer.d.ts +6 -7
  75. package/dist/studio/components/StateExplorer.d.ts.map +1 -1
  76. package/dist/studio/components/index.d.ts +2 -0
  77. package/dist/studio/components/index.d.ts.map +1 -1
  78. package/dist/studio/components/index.js +4 -4
  79. package/dist/studio/components-CNSNrmA9.js +2787 -0
  80. package/dist/studio/index.js +193 -147
  81. package/dist/studio/studio.css +1 -1
  82. package/dist/studio/utils/index.d.ts +2 -0
  83. package/dist/studio/utils/index.d.ts.map +1 -1
  84. package/dist/studio/utils/index.js +14 -12
  85. package/dist/studio/utils/managed-instance.d.ts +0 -4
  86. package/dist/studio/utils/managed-instance.d.ts.map +1 -1
  87. package/dist/studio/utils/managed-instance.js +0 -4
  88. package/dist/studio/utils/proposal-api.d.ts +53 -0
  89. package/dist/studio/utils/proposal-api.d.ts.map +1 -0
  90. package/dist/studio/utils/proposal-api.js +91 -0
  91. package/dist/studio/utils/service-api.d.ts +81 -0
  92. package/dist/studio/utils/service-api.d.ts.map +1 -0
  93. package/dist/studio/utils/service-api.js +51 -0
  94. package/dist/studio-app/assets/{dist-DA1MYSzp.js → dist-CCOk39Uc.js} +1 -1
  95. package/dist/studio-app/assets/{feltdb_wasm-B-Ccgccb.js → feltdb_wasm-CSB6KVgw.js} +1 -1
  96. package/dist/studio-app/assets/feltdb_wasm_bg-Dd1fnO9U.wasm +0 -0
  97. package/dist/studio-app/assets/index-Bncji3aN.js +43 -0
  98. package/dist/studio-app/assets/{index-BRrmvaKw.css → index-yazaWMUN.css} +1 -1
  99. package/dist/studio-app/index.html +2 -2
  100. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  101. package/dist/workspace/local-development-authority.d.ts.map +1 -1
  102. package/dist/workspace/local-development-authority.js +64 -3
  103. package/package.json +1 -1
  104. package/dist/studio/components-Bm4sjhcX.js +0 -2162
  105. package/dist/studio-app/assets/feltdb_wasm_bg-wH3enJIA.wasm +0 -0
  106. package/dist/studio-app/assets/index-CEIqowCh.js +0 -42
@@ -8,16 +8,19 @@ import net from 'net';
8
8
  import { createRequire } from 'module';
9
9
  import { spawn, spawnSync } from 'child_process';
10
10
  import { randomBytes } from 'crypto';
11
- import { createFeltDB, diffFlowSpec, formatFlowSpec, InvestigationLifecycleManager, parseFlowSpec, planFlowSpecMigration, startLocalDevelopmentAuthority, validateFlowSpec } from '@feltdb/core';
11
+ import { createInterface } from 'node:readline/promises';
12
+ import { createFeltDB, createModuleImplementationProposal, diffFlowSpec, formatFlowSpec, InvestigationLifecycleManager, listFeltDBModules, parseFlowSpec, planFlowSpecMigration, renderProposalDiagnostic, resolveFeltDBModule, resolveModuleIntent, startLocalDevelopmentAuthority, validateFlowSpec } from '@feltdb/core';
12
13
  import { discoverWorkspace, ensureWorkspaceGitIgnored, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace, startPairingDiscoveryServer } from './workspace-integration.js';
13
14
  import { applicationUrlCandidate, detectApplication, discoverApplicationUrl } from './application.js';
14
15
  import { resolveApplicationLifecycle, startManagedApplication, waitForManagedApplication } from './application-lifecycle.js';
15
16
  import { createDevelopmentSession, developmentSessionEnvironment, developmentSessionSummary, transitionDevelopmentSession } from './development-session.js';
16
17
  import { openApplication, openStudio } from './browser-opening.js';
17
18
  import { publishApplication } from './publish-engine.js';
18
- import { applyFlowProposal, describeProposal, loadLocalDevelopmentContract, reviewFlowProposal, syncGeneratedSource } from './source-sync.js';
19
+ import { applyFlowProposalAtomic, assertProposalSourcePlan, describeProposal, loadLocalDevelopmentContract, repositoryContext, reviewFlowProposal, syncGeneratedSource } from './source-sync.js';
19
20
  import { configureAiModel, generateConfiguredFlowProposal, publicAiModelConfiguration } from './ai-model-config.js';
20
21
  import { proposalService } from './proposal-client.js';
22
+ import { resolveManagedAuthorityEnvironment } from './managed-environment.js';
23
+ import { readManagedProjectState } from './managed-project.js';
21
24
  function loadProjectEnvironment(file = path.resolve('.env.local')) {
22
25
  if (!fs.existsSync(file))
23
26
  return;
@@ -87,6 +90,9 @@ export async function handleCommand(command, args) {
87
90
  return handleAi(args);
88
91
  case 'proposals':
89
92
  return handleProposals(args);
93
+ case 'module':
94
+ case 'modules':
95
+ return handleModules(args);
90
96
  case 'sync':
91
97
  return handleSourceSync(args);
92
98
  case 'help':
@@ -94,6 +100,24 @@ export async function handleCommand(command, args) {
94
100
  return handleHelp();
95
101
  }
96
102
  }
103
+ function handleModules(args) {
104
+ const action = args[0] || 'list';
105
+ if (action === 'list') {
106
+ for (const module of listFeltDBModules())
107
+ console.log(`${module.id}\t${module.version}\t${module.compatibility.join(', ')}`);
108
+ return;
109
+ }
110
+ if (action === 'inspect' && args[1]) {
111
+ const flowPath = path.resolve('feltdb.flow');
112
+ const spec = args.includes('--application') && fs.existsSync(flowPath) ? parseFlowSpec(fs.readFileSync(flowPath, 'utf8')) : undefined;
113
+ const declaration = spec?.modules?.find(value => value.name === args[1]);
114
+ const module = resolveFeltDBModule(declaration ?? { name: args[1], provider: args.find((value, index) => index > 1 && !value.startsWith('--')) || 'stripe-compatible' });
115
+ const usage = spec?.collections.flatMap(collection => collection.fields.filter(field => field.type.startsWith(`ref ${module.id}.`)).map(field => `${collection.name}.${field.name} -> ${field.type.slice(4)}`)) ?? [];
116
+ console.log(JSON.stringify({ ...module, application_usage: usage }, null, 2));
117
+ return;
118
+ }
119
+ throw new Error('Usage: feltdb module [list|inspect <id> [provider]]');
120
+ }
97
121
  function proposalFile(args) {
98
122
  const value = args.find(argument => !argument.startsWith('--') && !['review', 'apply'].includes(argument));
99
123
  if (!value)
@@ -103,12 +127,18 @@ function proposalFile(args) {
103
127
  function validateRepositoryBuild(args) {
104
128
  const packagePath = path.resolve('package.json');
105
129
  const projectPackage = fs.existsSync(packagePath) ? JSON.parse(fs.readFileSync(packagePath, 'utf8')) : {};
106
- if (!projectPackage.scripts?.build || args.includes('--no-build'))
130
+ if (args.includes('--no-build'))
131
+ return;
132
+ const verification = ['typecheck', 'test', 'build'].filter(name => projectPackage.scripts?.[name]);
133
+ if (!verification.length)
107
134
  return;
108
135
  console.log('Validating the updated application before recording applied status…');
109
- const built = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', 'build'], { cwd: process.cwd(), stdio: 'inherit' });
110
- if (built.status !== 0)
111
- throw new Error(`APPLICATION_VALIDATION_FAILED: build exited ${built.status ?? 'without a status'}; proposal remains approved for recovery`);
136
+ for (const name of verification) {
137
+ const checked = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', name], { cwd: process.cwd(), stdio: 'inherit' });
138
+ if (checked.status !== 0)
139
+ throw new Error(`APPLICATION_VALIDATION_FAILED: ${name} exited ${checked.status ?? 'without a status'}; repository restored and proposal remains approved`);
140
+ console.log(`✓ ${name} passed`);
141
+ }
112
142
  }
113
143
  async function handleAi(args) {
114
144
  loadProjectEnvironment();
@@ -141,6 +171,44 @@ async function handleAi(args) {
141
171
  return handleStudio(args, studioUrl => console.log(`Ask FeltDB: ${studioUrl}`));
142
172
  }
143
173
  const action = args[0];
174
+ if (action === 'proposals')
175
+ return handleProposals(['list', ...args.slice(1)]);
176
+ if (action === 'proposal') {
177
+ const id = args[1];
178
+ if (!id)
179
+ throw new Error('Usage: feltdb ai proposal <id> [--history]');
180
+ return handleProposals(['get', id, ...args.slice(2)]);
181
+ }
182
+ if (action === 'approve') {
183
+ const id = args[1];
184
+ if (!id)
185
+ throw new Error('Usage: feltdb ai approve <id> [--yes]');
186
+ const client = proposalService();
187
+ const proposal = await client.get(id);
188
+ const readiness = await client.status(id);
189
+ if (proposal.status !== 'previewed')
190
+ throw new Error(`Cannot approve Proposal ${id}. Current status: ${proposal.status}. A current preview is required.`);
191
+ if (readiness.blockers.length)
192
+ throw new Error(`Cannot approve Proposal ${id}. ${readiness.blockers.join(', ')}. Regenerate the Proposal before approval.`);
193
+ if (!proposal.preview_id)
194
+ throw new Error(`Cannot approve Proposal ${id}. Preview is missing.`);
195
+ await client.getPreview(proposal.preview_id);
196
+ console.log(renderProposalDiagnostic(proposal, { readiness }).join('\n'));
197
+ console.log('Repository source will NOT be modified by approval.');
198
+ let approved = args.includes('--yes');
199
+ if (!approved && process.stdin.isTTY) {
200
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
201
+ approved = /^y(?:es)?$/i.test(await prompt.question('Approve this proposal? [y/N] '));
202
+ prompt.close();
203
+ }
204
+ if (!approved) {
205
+ console.log('Approval cancelled.');
206
+ return;
207
+ }
208
+ const value = await client.approve(id, 'cli-developer', args.includes('--approve-authorization'));
209
+ console.log(`✓ Proposal ${value.id} approved`);
210
+ return;
211
+ }
144
212
  if (action === 'context') {
145
213
  const context = await loadLocalDevelopmentContract();
146
214
  console.log(JSON.stringify({ snapshot: context.snapshot, flowHash: context.flowHash }, null, 2));
@@ -166,7 +234,7 @@ async function handleAi(args) {
166
234
  proposedFlow: stored.proposed_flow, summary: stored.summary, changes: stored.contract_diff, warnings: stored.warnings }
167
235
  : JSON.parse(fs.readFileSync(path.resolve(target), 'utf8'));
168
236
  if (stored && stored.status !== 'approved')
169
- throw new Error(`Proposal must be approved before apply; current status is ${stored.status}`);
237
+ throw new Error(`Cannot apply Proposal ${stored.id}. Current status: ${stored.status}. Approval is required. Run: feltdb ai approve ${stored.id}`);
170
238
  if (!stored && !args.includes('--approve'))
171
239
  throw new Error('Explicit approval is required: rerun with --approve');
172
240
  const current = await loadLocalDevelopmentContract();
@@ -177,12 +245,26 @@ async function handleAi(args) {
177
245
  if (alreadyWritten) {
178
246
  console.log('Detected an approved proposal whose flow is already present; reconciling the interrupted application.');
179
247
  const recoveredReview = await reviewFlowProposal({ ...proposal, baseContractHash: current.snapshot.contract_hash, baseFlowHash: current.flowHash });
180
- syncGeneratedSource(recoveredReview, process.cwd(), { replace: args.includes('--replace-generated') });
181
- validateRepositoryBuild(args);
248
+ await applyFlowProposalAtomic({ ...proposal, baseContractHash: current.snapshot.contract_hash, baseFlowHash: current.flowHash, proposedFlow: current.source }, process.cwd(), {
249
+ approveAuthorization: true, replaceGenerated: args.includes('--replace-generated'), verify: () => validateRepositoryBuild(args),
250
+ });
182
251
  await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
183
252
  console.log(`✓ Reconciled and marked ${stored.id} applied`);
184
253
  return;
185
254
  }
255
+ if (stored) {
256
+ const client = proposalService();
257
+ const readiness = await client.status(stored.id);
258
+ if (readiness.blockers.length)
259
+ throw new Error(`Cannot apply Proposal ${stored.id}. Proposal is stale: ${readiness.blockers.join(', ')}. Proposal contract: ${stored.base_contract_hash}. Current contract: ${readiness.current_contract_hash}. Regenerate the Proposal before applying.`);
260
+ if (!stored.preview_id)
261
+ throw new Error(`Cannot apply Proposal ${stored.id}. A current preview is required.`);
262
+ await client.getPreview(stored.preview_id);
263
+ assertProposalSourcePlan(stored.source_plan);
264
+ const repository = await repositoryContext(process.cwd(), stored.source_plan.files.map(value => value.path));
265
+ if (repository.dirty.length && !args.includes('--allow-dirty'))
266
+ throw new Error(`Cannot apply Proposal ${stored.id}. Repository conflict: ${repository.dirty.join(', ')}. Resolve the conflict and retry.`);
267
+ }
186
268
  const reviewOnly = await reviewFlowProposal(proposal);
187
269
  console.log(`\n${proposal.summary || 'FeltDB change proposal'}`);
188
270
  for (const line of describeProposal(reviewOnly))
@@ -192,20 +274,67 @@ async function handleAi(args) {
192
274
  console.log('✓ Preview complete. No files, flow, contract, or published state were changed.');
193
275
  return;
194
276
  }
195
- const review = await applyFlowProposal(proposal, process.cwd(), {
277
+ let confirmed = args.includes('--yes');
278
+ if (!confirmed && process.stdin.isTTY) {
279
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
280
+ confirmed = /^y(?:es)?$/i.test(await prompt.question(`Apply ${stored?.id || 'this proposal'} to repository source? [y/N] `));
281
+ prompt.close();
282
+ }
283
+ if (!confirmed)
284
+ throw new Error('Apply cancelled. Re-run with --yes for non-interactive application.');
285
+ const review = await applyFlowProposalAtomic(proposal, process.cwd(), {
196
286
  allowDirty: args.includes('--allow-dirty'),
197
287
  approveAuthorization: args.includes('--approve-authorization'),
198
288
  replaceGenerated: args.includes('--replace-generated'),
289
+ verify: () => validateRepositoryBuild(args),
199
290
  });
200
291
  console.log(`✓ Applied validated flow proposal (${review.proposedContract.hashes.application})`);
201
292
  for (const file of review.written)
202
293
  console.log(` ${file}`);
203
- validateRepositoryBuild(args);
204
294
  if (stored)
205
295
  await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
206
296
  return;
207
297
  }
208
- throw new Error('Usage: feltdb ai [context|review <proposal.json>|apply <proposal.json>]');
298
+ const developerIntent = args.filter(value => !value.startsWith('--')).join(' ').trim();
299
+ if (developerIntent) {
300
+ const resolved = resolveModuleIntent(developerIntent);
301
+ if (resolved) {
302
+ console.log('Understanding application...');
303
+ const current = await loadLocalDevelopmentContract();
304
+ console.log('✓ Application contract loaded');
305
+ const proposal = await createModuleImplementationProposal({ intent: developerIntent, snapshot: current.snapshot, currentFlow: current.source });
306
+ console.log(`✓ ${proposal.implementation?.module.name} ${proposal.implementation?.module.version} resolved`);
307
+ console.log(`✓ Billing owner: ${proposal.implementation?.owner}`);
308
+ const review = await reviewFlowProposal(proposal);
309
+ if (proposal.implementation?.alreadyInstalled) {
310
+ console.log(`${proposal.implementation.module.name} is already installed.`);
311
+ const missing = [...review.diff.added, ...review.diff.changed];
312
+ if (!missing.length) {
313
+ console.log('Current implementation is complete. No proposal created.');
314
+ return;
315
+ }
316
+ console.log('Missing:');
317
+ for (const value of missing)
318
+ console.log(` ${value}`);
319
+ console.log('Creating a completion proposal.');
320
+ }
321
+ console.log('Generating implementation proposal...');
322
+ const client = proposalService();
323
+ const parent = (await client.list({ status: 'applied', limit: 100 })).proposals.find(value => value.metadata?.module === resolved.module);
324
+ let stored = await client.create({ application_id: current.snapshot.application.application_id, environment: current.snapshot.application.environment,
325
+ base_contract_hash: proposal.baseContractHash, base_flow_hash: proposal.baseFlowHash, proposed_flow: proposal.proposedFlow,
326
+ proposal_version: 1, contract_diff: [], source_plan: { files: [] },
327
+ summary: proposal.summary, warnings: proposal.warnings, actor: 'cli-developer', parent_proposal_id: parent?.id, metadata: { created_from: 'module-intent', module: resolved.module, module_version: resolved.version } });
328
+ await client.validate(stored.id, 'feltdb-validator', review.proposedContract.manifest);
329
+ const preview = await client.preview(stored.id, 'cli-developer');
330
+ stored = preview.proposal;
331
+ const readiness = await client.status(stored.id);
332
+ console.log(renderProposalDiagnostic(stored, { readiness, preview: preview }).join('\n'));
333
+ console.log(`Use:\n feltdb ai proposal ${stored.id}`);
334
+ return;
335
+ }
336
+ }
337
+ throw new Error('Usage: feltdb ai ["implement <module>"|context|proposals|proposal <id>|approve <id>|review <proposal.json>|apply <id>]');
209
338
  }
210
339
  async function handleProposals(args) {
211
340
  loadProjectEnvironment();
@@ -213,9 +342,18 @@ async function handleProposals(args) {
213
342
  const action = args[0] || 'list';
214
343
  const id = args[1];
215
344
  if (action === 'list') {
216
- const { proposals } = await client.list();
217
- for (const value of proposals.sort((a, b) => String(b.created_at).localeCompare(String(a.created_at))))
218
- console.log(`${value.id}\t${value.status}\t${value.summary}`);
345
+ const option = (name) => args.includes(name) ? args[args.indexOf(name) + 1] : undefined;
346
+ const result = await client.list({ status: option('--status'), environment: option('--environment'), limit: option('--limit') ? Number(option('--limit')) : undefined, cursor: option('--cursor') });
347
+ if (args.includes('--json')) {
348
+ console.log(JSON.stringify(result, null, 2));
349
+ return;
350
+ }
351
+ const { proposals } = result;
352
+ console.log('PROPOSALS');
353
+ for (const value of proposals)
354
+ console.log(`${value.id}\t${value.status.toUpperCase()}\t${value.summary}\t${value.created_at}`);
355
+ if (result.pagination?.nextCursor)
356
+ console.log(`Next: --cursor ${result.pagination.nextCursor}`);
219
357
  if (!proposals.length)
220
358
  console.log('No proposals.');
221
359
  return;
@@ -223,7 +361,14 @@ async function handleProposals(args) {
223
361
  if (!id)
224
362
  throw new Error(`feltdb proposals ${action} requires a proposal id`);
225
363
  if (action === 'get') {
226
- console.log(JSON.stringify({ proposal: await client.get(id), history: (await client.history(id)).events }, null, 2));
364
+ const proposal = await client.get(id);
365
+ const [readiness, history] = await Promise.all([client.status(id), client.history(id)]);
366
+ const preview = proposal.preview_id ? await client.getPreview(proposal.preview_id).catch(() => undefined) : undefined;
367
+ const result = { proposal, readiness, preview, history: history.events };
368
+ if (args.includes('--json'))
369
+ console.log(JSON.stringify(result, null, 2));
370
+ else
371
+ console.log(renderProposalDiagnostic(proposal, { readiness, history: history.events, preview: preview }).join('\n'));
227
372
  return;
228
373
  }
229
374
  if (action === 'diff') {
@@ -233,7 +378,7 @@ async function handleProposals(args) {
233
378
  return;
234
379
  }
235
380
  if (action === 'approve' || action === 'reject') {
236
- const value = await client.transition(id, action === 'approve' ? 'approved' : 'rejected', 'local-developer', false, args.includes('--approve-authorization'));
381
+ const value = action === 'approve' ? await client.approve(id, 'local-developer', args.includes('--approve-authorization')) : await client.reject(id, 'local-developer');
237
382
  console.log(`✓ ${value.id} ${value.status}`);
238
383
  return;
239
384
  }
@@ -617,9 +762,11 @@ For more info, visit: https://github.com/rkendel1/feltdb
617
762
  }
618
763
  async function handleConnect(args) {
619
764
  loadProjectEnvironment();
765
+ const managed = readManagedProjectState();
620
766
  const projectFile = path.resolve('feltdb.config.json');
621
767
  const project = fs.existsSync(projectFile) ? JSON.parse(fs.readFileSync(projectFile, 'utf8')) : {};
622
768
  const url = (args[0]
769
+ || managed.url
623
770
  || process.env.VITE_FELTDB_MANAGED_URL
624
771
  || process.env.VITE_FELTDB_URL
625
772
  || project.server?.url
@@ -634,8 +781,8 @@ async function handleConnect(args) {
634
781
  let health;
635
782
  try {
636
783
  const response = await fetch(`${url}/health`, {
637
- headers: process.env.VITE_FELTDB_MANAGED_API_KEY || process.env.VITE_FELTDB_API_KEY
638
- ? { Authorization: `Bearer ${process.env.VITE_FELTDB_MANAGED_API_KEY || process.env.VITE_FELTDB_API_KEY}` }
784
+ headers: process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY
785
+ ? { Authorization: `Bearer ${process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY}` }
639
786
  : {},
640
787
  signal: controller.signal,
641
788
  });
@@ -658,14 +805,14 @@ async function handleConnect(args) {
658
805
  fs.writeFileSync(connFile, JSON.stringify({
659
806
  url,
660
807
  connected: new Date().toISOString(),
661
- authenticated: Boolean(process.env.VITE_FELTDB_MANAGED_API_KEY || process.env.VITE_FELTDB_API_KEY),
662
- namespace: project.namespace || process.env.VITE_FELTDB_MANAGED_NAMESPACE || 'default',
808
+ authenticated: Boolean(process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY),
809
+ namespace: managed.namespace || project.namespace || 'default',
663
810
  runtime: project.runtime || 'self-hosted',
664
811
  }, null, 2));
665
812
  console.log('✅ Connected to FeltDB\n');
666
813
  console.log('Endpoint: ' + url);
667
- console.log(`Namespace: ${project.namespace || process.env.VITE_FELTDB_MANAGED_NAMESPACE || 'default'}`);
668
- console.log(`Authenticated: ${process.env.VITE_FELTDB_MANAGED_API_KEY || process.env.VITE_FELTDB_API_KEY ? 'yes' : 'no'}`);
814
+ console.log(`Namespace: ${managed.namespace || project.namespace || 'default'}`);
815
+ console.log(`Authenticated: ${process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY ? 'yes' : 'no'}`);
669
816
  console.log(`Runtime: ${project.runtime || 'self-hosted'}`);
670
817
  console.log(`Health: ${health.status || 'healthy'}\n`);
671
818
  console.log('Use: feltdb status (to check connection)\n');
@@ -780,7 +927,7 @@ async function handleWorkspaceConnect(args) {
780
927
  console.log(' (when @feltdb/client workspace APIs are available)\n');
781
928
  }
782
929
  async function handleDev(args) {
783
- var _a, _b, _c;
930
+ var _a;
784
931
  const projectDir = process.cwd();
785
932
  if (args.includes('--help')) {
786
933
  console.log('Usage: feltdb dev [--app-url URL] [--studio-port 7701] [--authority-port 7700] [--discovery-port 7799] [--no-open]');
@@ -833,14 +980,10 @@ async function handleDev(args) {
833
980
  ? { ...configDefaults, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) }
834
981
  : configDefaults;
835
982
  if (config.runtime === 'managed') {
836
- (_a = process.env).VITE_FELTDB_URL || (_a.VITE_FELTDB_URL = process.env.VITE_FELTDB_MANAGED_URL);
837
- (_b = process.env).VITE_FELTDB_API_KEY || (_b.VITE_FELTDB_API_KEY = process.env.VITE_FELTDB_MANAGED_API_KEY);
838
- }
839
- if (config.runtime === 'managed' && (!process.env.VITE_FELTDB_URL || !process.env.VITE_FELTDB_API_KEY)) {
840
- throw new Error('Managed runtime requires VITE_FELTDB_MANAGED_URL and VITE_FELTDB_MANAGED_API_KEY in .env.local. Re-run managed setup or add your managed connection.');
983
+ resolveManagedAuthorityEnvironment(process.env);
841
984
  }
842
985
  const runtimeNamespace = config.runtime === 'managed'
843
- ? process.env.VITE_FELTDB_MANAGED_NAMESPACE || config.namespace
986
+ ? readManagedProjectState(projectDir).namespace || config.namespace
844
987
  : config.namespace;
845
988
  const applicationLifecycle = resolveApplicationLifecycle(config);
846
989
  // Application URL precedence:
@@ -929,7 +1072,7 @@ async function handleDev(args) {
929
1072
  if (compose.status !== 0)
930
1073
  throw new Error(`docker compose failed with status ${compose.status ?? 'unknown'}`);
931
1074
  selfHostedStarted = true;
932
- (_c = process.env).VITE_FELTDB_URL || (_c.VITE_FELTDB_URL = 'http://127.0.0.1:7700');
1075
+ (_a = process.env).VITE_FELTDB_URL || (_a.VITE_FELTDB_URL = 'http://127.0.0.1:7700');
933
1076
  }
934
1077
  if (!process.env.VITE_FELTDB_URL) {
935
1078
  const authorityPort = Number(args.includes('--authority-port') ? args[args.indexOf('--authority-port') + 1] || '7700' : '7700');
@@ -1403,10 +1546,11 @@ async function handleStudio(args, onReady) {
1403
1546
  loadProjectEnvironment();
1404
1547
  const projectConfigPath = path.resolve('feltdb.config.json');
1405
1548
  const projectConfig = fs.existsSync(projectConfigPath) ? JSON.parse(fs.readFileSync(projectConfigPath, 'utf8')) : {};
1549
+ const managedProject = readManagedProjectState();
1406
1550
  const projectIsManaged = projectConfig.runtime === 'managed';
1407
1551
  const connectUrl = args.includes('--connect')
1408
1552
  ? args[args.indexOf('--connect') + 1]
1409
- : projectIsManaged ? process.env.VITE_FELTDB_MANAGED_URL : undefined;
1553
+ : projectIsManaged ? process.env.VITE_FELTDB_MANAGED_URL || managedProject.url : undefined;
1410
1554
  const port = args.includes('--port')
1411
1555
  ? args[args.indexOf('--port') + 1] || '7701'
1412
1556
  : '7701';
@@ -1416,10 +1560,16 @@ async function handleStudio(args, onReady) {
1416
1560
  const open = !args.includes('--no-open');
1417
1561
  const namespace = args.includes('--namespace')
1418
1562
  ? args[args.indexOf('--namespace') + 1] || 'default'
1419
- : projectIsManaged ? process.env.VITE_FELTDB_MANAGED_NAMESPACE || projectConfig.namespace || 'default' : 'default';
1563
+ : projectIsManaged ? managedProject.namespace || projectConfig.namespace || 'default' : 'default';
1420
1564
  const runtime = args.includes('--runtime')
1421
1565
  ? args[args.indexOf('--runtime') + 1] || 'browser'
1422
1566
  : projectIsManaged ? 'managed' : connectUrl ? 'remote' : 'browser';
1567
+ const applicationId = args.includes('--application')
1568
+ ? args[args.indexOf('--application') + 1] || ''
1569
+ : process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managedProject.applicationId || '';
1570
+ const environment = args.includes('--environment')
1571
+ ? args[args.indexOf('--environment') + 1] || 'production'
1572
+ : managedProject.environment || 'production';
1423
1573
  const appUrl = args.includes('--app-url')
1424
1574
  ? args[args.indexOf('--app-url') + 1]
1425
1575
  : undefined;
@@ -1465,14 +1615,14 @@ async function handleStudio(args, onReady) {
1465
1615
  response.setHeader('Cache-Control', 'no-store');
1466
1616
  response.setHeader('Pragma', 'no-cache');
1467
1617
  const managedPublish = runtime === 'managed' ? {
1468
- url: process.env.VITE_FELTDB_MANAGED_URL || connectUrl || '',
1469
- namespace: process.env.VITE_FELTDB_MANAGED_NAMESPACE || namespace,
1470
- tenantId: process.env.VITE_FELTDB_MANAGED_TENANT_ID || '',
1471
- applicationId: process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || '',
1472
- environment: process.env.VITE_FELTDB_MANAGED_ENVIRONMENT || 'production',
1618
+ url: connectUrl || managedProject.url || '',
1619
+ namespace: managedProject.namespace || namespace,
1620
+ tenantId: managedProject.tenantId || '',
1621
+ applicationId: process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managedProject.applicationId || '',
1622
+ environment: managedProject.environment || 'production',
1473
1623
  publishEndpoint: '/_feltdb/publish',
1474
1624
  } : undefined;
1475
- response.end(JSON.stringify({ token: connectUrl ? process.env.VITE_FELTDB_API_KEY || process.env.VITE_FELTDB_MANAGED_API_KEY || '' : '', managedPublish, session, sourceSync: { enabled: true, webllmEnabled, previewOnly, token: studioMutationToken, aiModel: publicAiModelConfiguration() } }));
1625
+ response.end(JSON.stringify({ token: connectUrl ? process.env.VITE_FELTDB_API_KEY || '' : '', managedPublish, session, sourceSync: { enabled: true, webllmEnabled, previewOnly, token: studioMutationToken, aiModel: publicAiModelConfiguration() } }));
1476
1626
  return;
1477
1627
  }
1478
1628
  if (pathname === '/_feltdb/contract') {
@@ -1484,12 +1634,12 @@ async function handleStudio(args, onReady) {
1484
1634
  ? JSON.parse(fs.readFileSync(path.resolve('.feltdb/last-published.json'), 'utf8')) : undefined;
1485
1635
  let runningContract;
1486
1636
  let runningSnapshot;
1487
- const applicationId = process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || lastPublished?.applicationId;
1637
+ const applicationId = process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managedProject.applicationId || lastPublished?.applicationId;
1488
1638
  if (connectUrl && applicationId) {
1489
1639
  try {
1490
1640
  const endpoint = new URL('/v1/application', connectUrl);
1491
1641
  endpoint.searchParams.set('application_id', applicationId);
1492
- endpoint.searchParams.set('environment', process.env.VITE_FELTDB_MANAGED_ENVIRONMENT || lastPublished?.environment || 'production');
1642
+ endpoint.searchParams.set('environment', managedProject.environment || lastPublished?.environment || 'production');
1493
1643
  const headers = {};
1494
1644
  const credential = process.env.FELTDB_MANAGED_CONTROL_API_KEY || process.env.VITE_FELTDB_API_KEY;
1495
1645
  if (credential)
@@ -1514,6 +1664,29 @@ async function handleStudio(args, onReady) {
1514
1664
  }
1515
1665
  return;
1516
1666
  }
1667
+ if (pathname === '/_feltdb/repository-readiness' && request.method === 'GET') {
1668
+ response.setHeader('Content-Type', 'application/json; charset=utf-8');
1669
+ response.setHeader('Cache-Control', 'no-store');
1670
+ try {
1671
+ const proposalId = new URL(request.url || '/', 'http://localhost').searchParams.get('proposal');
1672
+ if (!proposalId)
1673
+ throw new Error('proposal is required');
1674
+ const proposal = await proposalClient.get(proposalId);
1675
+ assertProposalSourcePlan(proposal.source_plan);
1676
+ const local = await loadLocalDevelopmentContract();
1677
+ const repository = await repositoryContext(process.cwd(), proposal.source_plan.files.map(value => value.path));
1678
+ const contractMatches = local.snapshot.contract_hash === proposal.base_contract_hash;
1679
+ const flowMatches = local.flowHash === proposal.base_flow_hash;
1680
+ response.end(JSON.stringify({ ready: contractMatches && flowMatches && repository.dirty.length === 0,
1681
+ contractMatches, flowMatches, gitClean: repository.dirty.length === 0, conflicts: repository.dirty,
1682
+ branch: repository.branch, commit: repository.commit }));
1683
+ }
1684
+ catch (error) {
1685
+ response.statusCode = 409;
1686
+ response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
1687
+ }
1688
+ return;
1689
+ }
1517
1690
  if (pathname === '/_feltdb/proposals/list' && request.method === 'GET') {
1518
1691
  response.setHeader('Content-Type', 'application/json; charset=utf-8');
1519
1692
  response.setHeader('Cache-Control', 'no-store');
@@ -1578,6 +1751,7 @@ async function handleStudio(args, onReady) {
1578
1751
  stored = await proposalClient.create({ application_id: current.snapshot.application.application_id,
1579
1752
  environment: current.snapshot.application.environment, base_contract_hash: proposal.baseContractHash,
1580
1753
  base_flow_hash: proposal.baseFlowHash, proposed_flow: proposal.proposedFlow, summary: proposal.summary,
1754
+ proposal_version: 1, contract_diff: [], source_plan: { files: [] },
1581
1755
  warnings: proposal.warnings, actor: 'studio-developer', metadata: { model: publicAiModelConfiguration().model, created_from: 'studio' } });
1582
1756
  await proposalClient.validate(stored.id, 'feltdb-validator', review.proposedContract.manifest);
1583
1757
  stored = (await proposalClient.preview(stored.id, 'studio-developer')).proposal;
@@ -1715,6 +1889,9 @@ async function handleStudio(args, onReady) {
1715
1889
  const parameters = new URLSearchParams({ namespace, runtime });
1716
1890
  if (connectUrl)
1717
1891
  parameters.set('connect', connectUrl);
1892
+ if (applicationId)
1893
+ parameters.set('application_id', applicationId);
1894
+ parameters.set('environment', environment);
1718
1895
  if (appUrl)
1719
1896
  parameters.set('app', appUrl);
1720
1897
  if (session.workspaceId)
@@ -1751,9 +1928,14 @@ Commands:
1751
1928
  ai model show Show the selected local or user-provided model
1752
1929
  ai model set Select WebLLM or an OpenAI-compatible model
1753
1930
  ai context Show the secret-free contract grounding snapshot
1931
+ ai proposals List bounded proposals [--status/--environment/--json]
1932
+ ai proposal <id> Show canonical proposal, readiness, preview, and history
1933
+ ai approve <id> Approve a current preview [--yes]
1754
1934
  ai review <file> Validate and review a WebLLM flow proposal
1755
- ai apply <id> Apply an approved proposal and synchronize source
1935
+ ai apply <id> Atomically apply an approved proposal [--yes]
1756
1936
  proposals List durable FeltDB application proposals
1937
+ module list List deterministic FeltDB modules
1938
+ module inspect <id> Inspect a resolved module semantic contract
1757
1939
  proposals get <id> Show a proposal and its lifecycle history
1758
1940
  proposals diff <id> Show contract and structured source impact
1759
1941
  proposals approve <id> Approve a previewed proposal
@@ -1776,6 +1958,7 @@ Commands:
1776
1958
  Studio:
1777
1959
  feltdb studio Launch local Studio
1778
1960
  feltdb studio --connect <url> Connect to remote instance
1961
+ --application <id> --environment <name> Select one application environment
1779
1962
  feltdb studio --port 7701 --no-open Start on port 7701 without opening browser
1780
1963
 
1781
1964
  Server Options:
package/dist/cli/index.js CHANGED
@@ -23,7 +23,7 @@ import * as path from 'path';
23
23
  import * as readline from 'readline';
24
24
  import { getClient } from './api-client.js';
25
25
  import { loadFeltDBConfig, createDefaultConfig, validateModel, } from './config.js';
26
- const VERSION = '0.7.4';
26
+ const VERSION = '0.8.0';
27
27
  function prompt(question) {
28
28
  const rl = readline.createInterface({
29
29
  input: process.stdin,
@@ -0,0 +1,6 @@
1
+ export function resolveManagedAuthorityEnvironment(environment) {
2
+ environment.VITE_FELTDB_URL || (environment.VITE_FELTDB_URL = environment.VITE_FELTDB_MANAGED_URL);
3
+ if (!environment.VITE_FELTDB_URL) {
4
+ throw new Error('Managed runtime requires VITE_FELTDB_MANAGED_URL in .env.local. Re-run managed setup or add your managed connection.');
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ export function readManagedProjectState(cwd = process.cwd()) {
4
+ const file = path.join(cwd, '.feltdb', 'managed.json');
5
+ return fs.existsSync(file) ? JSON.parse(fs.readFileSync(file, 'utf8')) : {};
6
+ }
@@ -9,10 +9,11 @@ export function developmentProposalConnection(cwd = process.cwd()) {
9
9
  throw new Error('No running FeltDB proposal service. Start feltdb dev.');
10
10
  const config = fs.existsSync(path.join(cwd, 'feltdb.config.json')) ? JSON.parse(fs.readFileSync(path.join(cwd, 'feltdb.config.json'), 'utf8')) : {};
11
11
  const published = fs.existsSync(path.join(cwd, '.feltdb', 'last-published.json')) ? JSON.parse(fs.readFileSync(path.join(cwd, '.feltdb', 'last-published.json'), 'utf8')) : {};
12
+ const managed = fs.existsSync(path.join(cwd, '.feltdb', 'managed.json')) ? JSON.parse(fs.readFileSync(path.join(cwd, '.feltdb', 'managed.json'), 'utf8')) : {};
12
13
  const flowApplication = fs.existsSync(path.join(cwd, 'feltdb.flow')) ? parseFlowSpec(fs.readFileSync(path.join(cwd, 'feltdb.flow'), 'utf8')).app : undefined;
13
14
  return { endpoint: endpoint.replace(/\/$/, ''), proposalAuthorityToken: stored?.proposalAuthorityToken,
14
- applicationId: process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || published.applicationId || config.applicationId || flowApplication,
15
- environment: process.env.VITE_FELTDB_MANAGED_ENVIRONMENT || published.environment || 'local' };
15
+ applicationId: process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managed.applicationId || published.applicationId || config.applicationId || flowApplication,
16
+ environment: managed.environment || published.environment || 'local' };
16
17
  }
17
18
  export class ProposalServiceClient {
18
19
  constructor(connection, token = process.env.FELTDB_MANAGED_CONTROL_API_KEY || '') {
@@ -38,11 +39,19 @@ export class ProposalServiceClient {
38
39
  }
39
40
  actor(input = {}) { return { application_id: this.connection.applicationId, environment: this.connection.environment, ...input }; }
40
41
  create(input) { return this.request('/v1/proposals', { method: 'POST', body: JSON.stringify(input) }); }
41
- list() { return this.request(this.scoped('/v1/proposals')); }
42
+ list(filters = {}) { const query = new URLSearchParams(); if (this.connection.applicationId)
43
+ query.set('application_id', this.connection.applicationId); query.set('environment', filters.environment || this.connection.environment || 'local'); if (filters.status)
44
+ query.set('status', filters.status); if (filters.limit)
45
+ query.set('limit', String(filters.limit)); if (filters.cursor)
46
+ query.set('cursor', filters.cursor); return this.request(`/v1/proposals?${query}`); }
42
47
  get(id) { return this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}`)); }
43
48
  history(id) { return this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}/history`)); }
49
+ status(id) { return this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}/status`)); }
44
50
  validate(id, actor = 'local-developer', proposedManifest) { return this.request(`/v1/proposals/${encodeURIComponent(id)}/validate`, { method: 'POST', body: JSON.stringify(this.actor({ actor, proposed_manifest: proposedManifest })) }); }
45
51
  preview(id, actor = 'local-developer') { return this.request(`/v1/proposals/${encodeURIComponent(id)}/preview`, { method: 'POST', body: JSON.stringify(this.actor({ actor })) }); }
52
+ getPreview(id) { return this.request(this.scoped(`/v1/previews/${encodeURIComponent(id)}`)); }
53
+ approve(id, actor = 'local-developer', approveAuthorization = false) { return this.request(`/v1/proposals/${encodeURIComponent(id)}/approve`, { method: 'POST', body: JSON.stringify(this.actor({ actor, approve_authorization: approveAuthorization })) }); }
54
+ reject(id, actor = 'local-developer', reason) { return this.request(`/v1/proposals/${encodeURIComponent(id)}/reject`, { method: 'POST', body: JSON.stringify(this.actor({ actor, reason })) }); }
46
55
  transition(id, status, actor = 'local-developer', repositoryAuthority = false, approveAuthorization = false) {
47
56
  return this.request(`/v1/proposals/${encodeURIComponent(id)}/status`, { method: 'PATCH', headers: repositoryAuthority && this.connection.proposalAuthorityToken
48
57
  ? { 'FeltDB-Proposal-Authority': this.connection.proposalAuthorityToken, 'FeltDB-Repository-Apply': '1' } : {}, body: JSON.stringify(this.actor({ status, actor, approve_authorization: approveAuthorization })) });
@@ -25,12 +25,14 @@ export function discoverPublishConfiguration(cwd = process.cwd(), environmentOve
25
25
  throw new Error('feltdb.config.json is required for publish');
26
26
  const project = JSON.parse(fs.readFileSync(configFile, 'utf8'));
27
27
  const file = loadEnv(path.join(cwd, '.env.local'));
28
+ const managedFile = path.join(cwd, '.feltdb', 'managed.json');
29
+ const managed = fs.existsSync(managedFile) ? JSON.parse(fs.readFileSync(managedFile, 'utf8')) : {};
28
30
  const value = (name) => process.env[name] || file[name];
29
31
  const configuration = {
30
- url: value('VITE_FELTDB_MANAGED_URL'), token: value('FELTDB_MANAGED_CONTROL_API_KEY'),
31
- tenantId: value('VITE_FELTDB_MANAGED_TENANT_ID'), applicationId: value('VITE_FELTDB_MANAGED_APPLICATION_ID'),
32
- namespace: value('VITE_FELTDB_MANAGED_NAMESPACE') || project.namespace,
33
- environment: environmentOverride || value('VITE_FELTDB_MANAGED_ENVIRONMENT') || 'production',
32
+ url: value('VITE_FELTDB_MANAGED_URL') || managed.url, token: value('FELTDB_MANAGED_CONTROL_API_KEY'),
33
+ tenantId: managed.tenantId || value('VITE_FELTDB_MANAGED_TENANT_ID'), applicationId: value('VITE_FELTDB_MANAGED_APPLICATION_ID') || managed.applicationId,
34
+ namespace: managed.namespace || value('VITE_FELTDB_MANAGED_NAMESPACE') || project.namespace,
35
+ environment: environmentOverride || managed.environment || value('VITE_FELTDB_MANAGED_ENVIRONMENT') || 'production',
34
36
  };
35
37
  const missing = Object.entries(configuration).filter(([, value]) => !value).map(([name]) => name);
36
38
  if (missing.length)
@@ -75,6 +77,7 @@ export function buildPublishBundle(cwd, configuration) {
75
77
  'feltdb.agent_hash': contractHash(manifest.agents),
76
78
  'feltdb.capability_hash': contractHash(manifest.capabilities),
77
79
  'feltdb.contract_hash': contractHash({ dsl_version: spec.version, manifest }),
80
+ 'feltdb.flow_hash': `sha256:${sha256(flowSource.replace(/\r\n/g, '\n'))}`,
78
81
  'feltdb.artifact_hash': artifactHash,
79
82
  'feltdb.namespace': configuration.namespace,
80
83
  };