@feltdb/core 0.7.4 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +25 -0
  2. package/dist/application-development.d.ts +10 -3
  3. package/dist/application-development.d.ts.map +1 -1
  4. package/dist/application-development.js +11 -1
  5. package/dist/application-manifest.d.ts +2 -0
  6. package/dist/application-manifest.d.ts.map +1 -1
  7. package/dist/application-manifest.js +1 -1
  8. package/dist/application-understanding.d.ts +76 -0
  9. package/dist/application-understanding.d.ts.map +1 -0
  10. package/dist/application-understanding.js +282 -0
  11. package/dist/canonical-application.d.ts +1 -0
  12. package/dist/canonical-application.d.ts.map +1 -1
  13. package/dist/canonical-application.js +3 -2
  14. package/dist/cli/ai-model-config.js +3 -3
  15. package/dist/cli/cli.js +1 -1
  16. package/dist/cli/commands.js +321 -45
  17. package/dist/cli/index.js +1 -1
  18. package/dist/cli/managed-environment.js +6 -0
  19. package/dist/cli/managed-project.js +6 -0
  20. package/dist/cli/proposal-client.js +16 -3
  21. package/dist/cli/publish-engine.js +8 -5
  22. package/dist/cli/repository-evidence.js +55 -0
  23. package/dist/cli/source-sync.js +220 -61
  24. package/dist/convergence.d.ts +55 -0
  25. package/dist/convergence.d.ts.map +1 -0
  26. package/dist/convergence.js +63 -0
  27. package/dist/create/cli.js +10 -1
  28. package/dist/create/create.js +10 -12
  29. package/dist/create/managed-account.js +72 -7
  30. package/dist/create/package-versions.js +1 -1
  31. package/dist/create/server-source/crates/feltdb/src/application.rs +14 -0
  32. package/dist/create/server-source/crates/feltdb/src/lib.rs +20 -2
  33. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +12 -24
  34. package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +11 -3
  35. package/dist/create/server-source/crates/feltdb-server/src/authenticated_principal.rs +0 -1
  36. package/dist/create/server-source/crates/feltdb-server/src/certification_harness.rs +23 -22
  37. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +14 -0
  38. package/dist/create/server-source/crates/feltdb-server/src/delegation_token.rs +5 -15
  39. package/dist/create/server-source/crates/feltdb-server/src/durable_operations.rs +13 -28
  40. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +79 -14
  41. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +28 -7
  42. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +5 -5
  43. package/dist/create/server-source/crates/feltdb-server/src/main.rs +1929 -261
  44. package/dist/create/server-source/crates/feltdb-server/src/managed_diagnostics.rs +10 -30
  45. package/dist/create/server-source/crates/feltdb-server/src/membership_policy.rs +12 -4
  46. package/dist/create/server-source/crates/feltdb-server/src/request_telemetry.rs +8 -6
  47. package/dist/create/server-source/crates/feltdb-server/src/snapshot_cursor.rs +10 -14
  48. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +191 -22
  49. package/dist/create/server-source/crates/feltdb-server/src/tenant_policies.rs +9 -21
  50. package/dist/create/server-source/crates/feltdb-server/src/transaction_idempotency.rs +5 -3
  51. package/dist/create/server-source/crates/feltdb-server/src/transaction_recovery.rs +9 -8
  52. package/dist/create/server-source/crates/feltdb-server/tests/revision_recovery_integration_test.rs +1 -4
  53. package/dist/create/template/default-project/feltdb.flow +5 -0
  54. package/dist/create/template/default-project/src/context/AuthContext.tsx +9 -1
  55. package/dist/create/template/default-project/src/pages/SignUp.tsx +1 -1
  56. package/dist/flowspec.d.ts +1 -0
  57. package/dist/flowspec.d.ts.map +1 -1
  58. package/dist/flowspec.js +51 -12
  59. package/dist/http-client.d.ts +166 -14
  60. package/dist/http-client.d.ts.map +1 -1
  61. package/dist/http-client.js +91 -31
  62. package/dist/http-db.d.ts +3 -0
  63. package/dist/http-db.d.ts.map +1 -1
  64. package/dist/http-db.js +28 -3
  65. package/dist/index-core.d.ts +5 -0
  66. package/dist/index-core.d.ts.map +1 -1
  67. package/dist/index-core.js +5 -0
  68. package/dist/intent.d.ts +98 -0
  69. package/dist/intent.d.ts.map +1 -0
  70. package/dist/intent.js +343 -0
  71. package/dist/module-intent.d.ts +51 -0
  72. package/dist/module-intent.d.ts.map +1 -0
  73. package/dist/module-intent.js +230 -0
  74. package/dist/module.d.ts +105 -0
  75. package/dist/module.d.ts.map +1 -0
  76. package/dist/module.js +566 -0
  77. package/dist/proposal.d.ts +92 -4
  78. package/dist/proposal.d.ts.map +1 -1
  79. package/dist/proposal.js +150 -6
  80. package/dist/studio/app.d.ts +3 -1
  81. package/dist/studio/app.d.ts.map +1 -1
  82. package/dist/studio/components/AskFeltDB.d.ts +2 -1
  83. package/dist/studio/components/AskFeltDB.d.ts.map +1 -1
  84. package/dist/studio/components/ContractInspector.d.ts +7 -0
  85. package/dist/studio/components/ContractInspector.d.ts.map +1 -0
  86. package/dist/studio/components/ManagedInstancePanel.d.ts.map +1 -1
  87. package/dist/studio/components/ManagedInstancePanel.js +1 -5
  88. package/dist/studio/components/ModuleExplorer.d.ts +6 -0
  89. package/dist/studio/components/ModuleExplorer.d.ts.map +1 -0
  90. package/dist/studio/components/ProposalBrowser.d.ts +10 -1
  91. package/dist/studio/components/ProposalBrowser.d.ts.map +1 -1
  92. package/dist/studio/components/StateExplorer.d.ts +6 -7
  93. package/dist/studio/components/StateExplorer.d.ts.map +1 -1
  94. package/dist/studio/components/index.d.ts +2 -0
  95. package/dist/studio/components/index.d.ts.map +1 -1
  96. package/dist/studio/components/index.js +4 -4
  97. package/dist/studio/components-Duq2xQWt.js +2845 -0
  98. package/dist/studio/index.js +193 -147
  99. package/dist/studio/studio.css +1 -1
  100. package/dist/studio/utils/index.d.ts +2 -0
  101. package/dist/studio/utils/index.d.ts.map +1 -1
  102. package/dist/studio/utils/index.js +14 -12
  103. package/dist/studio/utils/managed-instance.d.ts +0 -4
  104. package/dist/studio/utils/managed-instance.d.ts.map +1 -1
  105. package/dist/studio/utils/managed-instance.js +0 -4
  106. package/dist/studio/utils/proposal-api.d.ts +56 -0
  107. package/dist/studio/utils/proposal-api.d.ts.map +1 -0
  108. package/dist/studio/utils/proposal-api.js +94 -0
  109. package/dist/studio/utils/service-api.d.ts +81 -0
  110. package/dist/studio/utils/service-api.d.ts.map +1 -0
  111. package/dist/studio/utils/service-api.js +51 -0
  112. package/dist/studio-app/assets/{feltdb_wasm-B-Ccgccb.js → feltdb_wasm-bIqcRzAr.js} +1 -1
  113. package/dist/studio-app/assets/feltdb_wasm_bg-BE79okwX.wasm +0 -0
  114. package/dist/studio-app/assets/{index-BRrmvaKw.css → index-Cd_NPw14.css} +1 -1
  115. package/dist/studio-app/assets/index-XGYdlElN.js +29 -0
  116. package/dist/studio-app/index.html +2 -2
  117. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  118. package/dist/workspace/local-development-authority.d.ts +2 -0
  119. package/dist/workspace/local-development-authority.d.ts.map +1 -1
  120. package/dist/workspace/local-development-authority.js +128 -4
  121. package/package.json +1 -1
  122. package/dist/studio/components-Bm4sjhcX.js +0 -2162
  123. package/dist/studio-app/assets/dist-DA1MYSzp.js +0 -1
  124. package/dist/studio-app/assets/feltdb_wasm_bg-wH3enJIA.wasm +0 -0
  125. package/dist/studio-app/assets/index-CEIqowCh.js +0 -42
  126. package/dist/studio-app/assets/lib-B2_7fcn7.js +0 -69
  127. package/dist/studio-app/assets/worker-CARZ5g7K.js +0 -69
package/dist/cli/cli.js CHANGED
@@ -11,7 +11,7 @@ async function main() {
11
11
  await handleCommand(command, process.argv.slice(3));
12
12
  }
13
13
  catch (error) {
14
- console.error('Error:', error);
14
+ console.error('Error:', error instanceof Error ? error.message : String(error));
15
15
  process.exit(1);
16
16
  }
17
17
  }
@@ -8,16 +8,21 @@ 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, diffFlowSpec, formatFlowSpec, InvestigationLifecycleManager, listFeltDBModules, parseFlowSpec, planFlowSpecMigration, renderConvergenceDiagnostic, renderProposalDiagnostic, resolveFeltDBModule, resolveModuleIntent, startLocalDevelopmentAuthority, validateFlowSpec, verifyProposalConvergence } from '@feltdb/core';
13
+ import { collectApplicationUnderstanding } from '@feltdb/core';
14
+ import { collectRepositoryEvidence } from './repository-evidence.js';
12
15
  import { discoverWorkspace, ensureWorkspaceGitIgnored, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace, startPairingDiscoveryServer } from './workspace-integration.js';
13
16
  import { applicationUrlCandidate, detectApplication, discoverApplicationUrl } from './application.js';
14
17
  import { resolveApplicationLifecycle, startManagedApplication, waitForManagedApplication } from './application-lifecycle.js';
15
18
  import { createDevelopmentSession, developmentSessionEnvironment, developmentSessionSummary, transitionDevelopmentSession } from './development-session.js';
16
19
  import { openApplication, openStudio } from './browser-opening.js';
17
20
  import { publishApplication } from './publish-engine.js';
18
- import { applyFlowProposal, describeProposal, loadLocalDevelopmentContract, reviewFlowProposal, syncGeneratedSource } from './source-sync.js';
21
+ import { applyFlowProposalAtomic, assertProposalSourcePlan, describeProposal, loadLocalDevelopmentContract, repositoryContext, reviewFlowProposal, syncGeneratedSource } from './source-sync.js';
19
22
  import { configureAiModel, generateConfiguredFlowProposal, publicAiModelConfiguration } from './ai-model-config.js';
20
23
  import { proposalService } from './proposal-client.js';
24
+ import { resolveManagedAuthorityEnvironment } from './managed-environment.js';
25
+ import { readManagedProjectState } from './managed-project.js';
21
26
  function loadProjectEnvironment(file = path.resolve('.env.local')) {
22
27
  if (!fs.existsSync(file))
23
28
  return;
@@ -87,6 +92,9 @@ export async function handleCommand(command, args) {
87
92
  return handleAi(args);
88
93
  case 'proposals':
89
94
  return handleProposals(args);
95
+ case 'module':
96
+ case 'modules':
97
+ return handleModules(args);
90
98
  case 'sync':
91
99
  return handleSourceSync(args);
92
100
  case 'help':
@@ -94,6 +102,25 @@ export async function handleCommand(command, args) {
94
102
  return handleHelp();
95
103
  }
96
104
  }
105
+ function handleModules(args) {
106
+ const action = args[0] || 'list';
107
+ if (action === 'list') {
108
+ for (const module of listFeltDBModules())
109
+ console.log(`${module.id}\t${module.version}\t${module.compatibility.join(', ')}`);
110
+ return;
111
+ }
112
+ if (action === 'inspect' && args[1]) {
113
+ const flowPath = path.resolve('feltdb.flow');
114
+ const spec = args.includes('--application') && fs.existsSync(flowPath) ? parseFlowSpec(fs.readFileSync(flowPath, 'utf8')) : undefined;
115
+ const declaration = spec?.modules?.find(value => value.name === args[1]);
116
+ const registered = listFeltDBModules().find(value => value.id === args[1]);
117
+ const module = resolveFeltDBModule(declaration ?? { name: args[1], provider: args.find((value, index) => index > 1 && !value.startsWith('--')) || registered?.provider || '' });
118
+ 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)}`)) ?? [];
119
+ console.log(JSON.stringify({ ...module, application_usage: usage }, null, 2));
120
+ return;
121
+ }
122
+ throw new Error('Usage: feltdb module [list|inspect <id> [provider]]');
123
+ }
97
124
  function proposalFile(args) {
98
125
  const value = args.find(argument => !argument.startsWith('--') && !['review', 'apply'].includes(argument));
99
126
  if (!value)
@@ -103,12 +130,42 @@ function proposalFile(args) {
103
130
  function validateRepositoryBuild(args) {
104
131
  const packagePath = path.resolve('package.json');
105
132
  const projectPackage = fs.existsSync(packagePath) ? JSON.parse(fs.readFileSync(packagePath, 'utf8')) : {};
106
- if (!projectPackage.scripts?.build || args.includes('--no-build'))
133
+ if (args.includes('--no-build'))
134
+ return;
135
+ const verification = ['typecheck', 'test', 'build'].filter(name => projectPackage.scripts?.[name]);
136
+ if (!verification.length)
107
137
  return;
108
138
  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`);
139
+ for (const name of verification) {
140
+ const checked = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', name], { cwd: process.cwd(), stdio: 'inherit' });
141
+ if (checked.status !== 0)
142
+ throw new Error(`APPLICATION_VALIDATION_FAILED: ${name} exited ${checked.status ?? 'without a status'}; repository restored and proposal remains approved`);
143
+ console.log(`✓ ${name} passed`);
144
+ }
145
+ }
146
+ function verificationChecks(run = true) {
147
+ const packagePath = path.resolve('package.json');
148
+ const projectPackage = fs.existsSync(packagePath) ? JSON.parse(fs.readFileSync(packagePath, 'utf8')) : {};
149
+ const results = [];
150
+ for (const name of ['typecheck', 'test', 'build'].filter(value => projectPackage.scripts?.[value])) {
151
+ if (!run) {
152
+ results.push({ surface: name === 'test' ? 'test' : 'build', claim: `repository.${name}`, status: 'satisfied', evidence: [`npm run ${name}`] });
153
+ continue;
154
+ }
155
+ const checked = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['run', name], { cwd: process.cwd(), stdio: 'inherit' });
156
+ results.push({ surface: name === 'test' ? 'test' : 'build', claim: `repository.${name}`, status: checked.status === 0 ? 'satisfied' : 'missing', evidence: [`npm run ${name} exited ${checked.status ?? 'without status'}`] });
157
+ }
158
+ if (!results.length)
159
+ results.push({ surface: 'test', claim: 'repository.validation', status: 'unverified', evidence: ['No typecheck, test, or build script is declared.'] });
160
+ return results;
161
+ }
162
+ async function verifyAndRecord(proposal, runChecks = true) {
163
+ const current = await loadLocalDevelopmentContract();
164
+ const source = proposal.source_plan.files.map(file => ({ path: file.path, status: (fs.existsSync(path.resolve(file.path)) ? 'implemented' : 'missing'), evidence: [fs.existsSync(path.resolve(file.path)) ? `repository path ${file.path}` : `missing repository path ${file.path}`] }));
165
+ const result = await verifyProposalConvergence({ proposal, currentFlow: current.source, snapshot: current.snapshot, source, verification: verificationChecks(runChecks), observations: collectRepositoryEvidence() });
166
+ const stored = await proposalService().recordConvergence(proposal.id, result);
167
+ console.log(renderConvergenceDiagnostic(stored).join('\n'));
168
+ return stored;
112
169
  }
113
170
  async function handleAi(args) {
114
171
  loadProjectEnvironment();
@@ -141,6 +198,54 @@ async function handleAi(args) {
141
198
  return handleStudio(args, studioUrl => console.log(`Ask FeltDB: ${studioUrl}`));
142
199
  }
143
200
  const action = args[0];
201
+ if (action === 'verify' || action === 'converge') {
202
+ const id = args[1];
203
+ if (!id)
204
+ throw new Error(`Usage: feltdb ai ${action} <proposal-id>`);
205
+ const proposal = await proposalService().get(id);
206
+ if (proposal.status !== 'applied')
207
+ throw new Error(`Cannot verify Proposal ${id}. Current status: ${proposal.status}. Verification follows application.`);
208
+ await verifyAndRecord(proposal, !args.includes('--no-build'));
209
+ return;
210
+ }
211
+ if (action === 'proposals')
212
+ return handleProposals(['list', ...args.slice(1)]);
213
+ if (action === 'proposal') {
214
+ const id = args[1];
215
+ if (!id)
216
+ throw new Error('Usage: feltdb ai proposal <id> [--history]');
217
+ return handleProposals(['get', id, ...args.slice(2)]);
218
+ }
219
+ if (action === 'approve') {
220
+ const id = args[1];
221
+ if (!id)
222
+ throw new Error('Usage: feltdb ai approve <id> [--yes]');
223
+ const client = proposalService();
224
+ const proposal = await client.get(id);
225
+ const readiness = await currentProposalReadiness(client, proposal);
226
+ if (proposal.status !== 'previewed')
227
+ throw new Error(`Cannot approve Proposal ${id}. Current status: ${proposal.status}. A current preview is required.`);
228
+ if (readiness.blockers.length)
229
+ throw new Error(`Cannot approve Proposal ${id}. ${readiness.blockers.join(', ')}. Regenerate the Proposal before approval.`);
230
+ if (!proposal.preview_id)
231
+ throw new Error(`Cannot approve Proposal ${id}. Preview is missing.`);
232
+ await client.getPreview(proposal.preview_id);
233
+ console.log(renderProposalDiagnostic(proposal, { readiness }).join('\n'));
234
+ console.log('Repository source will NOT be modified by approval.');
235
+ let approved = args.includes('--yes');
236
+ if (!approved && process.stdin.isTTY) {
237
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
238
+ approved = /^y(?:es)?$/i.test(await prompt.question('Approve this proposal? [y/N] '));
239
+ prompt.close();
240
+ }
241
+ if (!approved) {
242
+ console.log('Approval cancelled.');
243
+ return;
244
+ }
245
+ const value = await client.approve(id, 'cli-developer', args.includes('--approve-authorization'));
246
+ console.log(`✓ Proposal ${value.id} approved`);
247
+ return;
248
+ }
144
249
  if (action === 'context') {
145
250
  const context = await loadLocalDevelopmentContract();
146
251
  console.log(JSON.stringify({ snapshot: context.snapshot, flowHash: context.flowHash }, null, 2));
@@ -163,10 +268,10 @@ async function handleAi(args) {
163
268
  throw new Error('A proposal id or JSON file is required');
164
269
  const stored = target.startsWith('p_') ? await proposalService().get(target) : undefined;
165
270
  const proposal = stored ? { baseContractHash: stored.base_contract_hash, baseFlowHash: stored.base_flow_hash,
166
- proposedFlow: stored.proposed_flow, summary: stored.summary, changes: stored.contract_diff, warnings: stored.warnings }
271
+ proposedFlow: stored.proposed_flow, summary: stored.summary, changes: stored.contract_diff, warnings: stored.warnings, source_plan: stored.source_plan }
167
272
  : JSON.parse(fs.readFileSync(path.resolve(target), 'utf8'));
168
273
  if (stored && stored.status !== 'approved')
169
- throw new Error(`Proposal must be approved before apply; current status is ${stored.status}`);
274
+ throw new Error(`Cannot apply Proposal ${stored.id}. Current status: ${stored.status}. Approval is required. Run: feltdb ai approve ${stored.id}`);
170
275
  if (!stored && !args.includes('--approve'))
171
276
  throw new Error('Explicit approval is required: rerun with --approve');
172
277
  const current = await loadLocalDevelopmentContract();
@@ -177,12 +282,27 @@ async function handleAi(args) {
177
282
  if (alreadyWritten) {
178
283
  console.log('Detected an approved proposal whose flow is already present; reconciling the interrupted application.');
179
284
  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);
285
+ await applyFlowProposalAtomic({ ...proposal, baseContractHash: current.snapshot.contract_hash, baseFlowHash: current.flowHash, proposedFlow: current.source }, process.cwd(), {
286
+ approveAuthorization: true, replaceGenerated: args.includes('--replace-generated'), verify: () => validateRepositoryBuild(args),
287
+ });
182
288
  await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
183
289
  console.log(`✓ Reconciled and marked ${stored.id} applied`);
290
+ await verifyAndRecord({ ...stored, status: 'applied' }, false);
184
291
  return;
185
292
  }
293
+ if (stored) {
294
+ const client = proposalService();
295
+ const readiness = await client.status(stored.id);
296
+ if (readiness.blockers.length)
297
+ 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.`);
298
+ if (!stored.preview_id)
299
+ throw new Error(`Cannot apply Proposal ${stored.id}. A current preview is required.`);
300
+ await client.getPreview(stored.preview_id);
301
+ assertProposalSourcePlan(stored.source_plan);
302
+ const repository = await repositoryContext(process.cwd(), stored.source_plan.files.map(value => value.path));
303
+ if (repository.dirty.length && !args.includes('--allow-dirty'))
304
+ throw new Error(`Cannot apply Proposal ${stored.id}. Repository conflict: ${repository.dirty.join(', ')}. Resolve the conflict and retry.`);
305
+ }
186
306
  const reviewOnly = await reviewFlowProposal(proposal);
187
307
  console.log(`\n${proposal.summary || 'FeltDB change proposal'}`);
188
308
  for (const line of describeProposal(reviewOnly))
@@ -192,20 +312,60 @@ async function handleAi(args) {
192
312
  console.log('✓ Preview complete. No files, flow, contract, or published state were changed.');
193
313
  return;
194
314
  }
195
- const review = await applyFlowProposal(proposal, process.cwd(), {
315
+ let confirmed = args.includes('--yes');
316
+ if (!confirmed && process.stdin.isTTY) {
317
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
318
+ confirmed = /^y(?:es)?$/i.test(await prompt.question(`Apply ${stored?.id || 'this proposal'} to repository source? [y/N] `));
319
+ prompt.close();
320
+ }
321
+ if (!confirmed)
322
+ throw new Error('Apply cancelled. Re-run with --yes for non-interactive application.');
323
+ const review = await applyFlowProposalAtomic(proposal, process.cwd(), {
196
324
  allowDirty: args.includes('--allow-dirty'),
197
325
  approveAuthorization: args.includes('--approve-authorization'),
198
326
  replaceGenerated: args.includes('--replace-generated'),
327
+ verify: () => validateRepositoryBuild(args),
199
328
  });
200
329
  console.log(`✓ Applied validated flow proposal (${review.proposedContract.hashes.application})`);
201
330
  for (const file of review.written)
202
331
  console.log(` ${file}`);
203
- validateRepositoryBuild(args);
204
- if (stored)
205
- await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
332
+ if (stored) {
333
+ const applied = await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
334
+ await verifyAndRecord(applied, false);
335
+ }
206
336
  return;
207
337
  }
208
- throw new Error('Usage: feltdb ai [context|review <proposal.json>|apply <proposal.json>]');
338
+ const developerIntent = args.filter(value => !value.startsWith('--')).join(' ').trim();
339
+ if (developerIntent) {
340
+ const resolved = resolveModuleIntent(developerIntent);
341
+ if (resolved) {
342
+ const client = proposalService();
343
+ console.log('Executing intent through the FeltDB Intent Service...');
344
+ const execution = await client.executeIntent(developerIntent);
345
+ if (execution.blockers.length)
346
+ throw new Error(`INTENT_BLOCKED: ${execution.blockers.join(', ')}`);
347
+ const ownerClaim = execution.plan?.module.placement.owner_claim;
348
+ const ownerInference = execution.understanding?.inferences.find(value => value.claim === ownerClaim);
349
+ console.log(`✓ Intent ${execution.intent.id}: ${execution.intent.action} ${execution.intent.module}`);
350
+ console.log(`✓ Evidence collected (${execution.understanding?.evidence.length ?? 0} normalized facts)`);
351
+ if (execution.understanding?.completeness)
352
+ console.log(`✓ Understanding completeness: ${execution.understanding.completeness.score}%`);
353
+ if (ownerInference)
354
+ console.log(`✓ Integration owner: ${String(ownerInference.value)} · ${Math.round(ownerInference.confidence * 100)}% · ${ownerInference.authority}`);
355
+ if (!execution.proposal) {
356
+ console.log(`✓ ${execution.intent.module} is already installed and converged. No proposal created.`);
357
+ return;
358
+ }
359
+ const stored = execution.proposal;
360
+ if (execution.plan?.already_installed)
361
+ console.log('Creating a completion proposal for the existing module implementation.');
362
+ const readiness = await client.status(stored.id, stored.evidence_hash);
363
+ console.log(renderProposalDiagnostic(stored, { readiness, preview: execution.preview }).join('\n'));
364
+ console.log(`Use:\n feltdb ai proposal ${stored.id}`);
365
+ return;
366
+ }
367
+ }
368
+ throw new Error('Usage: feltdb ai ["implement <module>"|context|proposals|proposal <id>|approve <id>|review <proposal.json>|apply <id>|verify <id>|converge <id>]');
209
369
  }
210
370
  async function handleProposals(args) {
211
371
  loadProjectEnvironment();
@@ -213,9 +373,18 @@ async function handleProposals(args) {
213
373
  const action = args[0] || 'list';
214
374
  const id = args[1];
215
375
  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}`);
376
+ const option = (name) => args.includes(name) ? args[args.indexOf(name) + 1] : undefined;
377
+ const result = await client.list({ status: option('--status'), environment: option('--environment'), limit: option('--limit') ? Number(option('--limit')) : undefined, cursor: option('--cursor') });
378
+ if (args.includes('--json')) {
379
+ console.log(JSON.stringify(result, null, 2));
380
+ return;
381
+ }
382
+ const { proposals } = result;
383
+ console.log('PROPOSALS');
384
+ for (const value of proposals)
385
+ console.log(`${value.id}\t${value.status.toUpperCase()}\t${value.summary}\t${value.created_at}`);
386
+ if (result.pagination?.nextCursor)
387
+ console.log(`Next: --cursor ${result.pagination.nextCursor}`);
219
388
  if (!proposals.length)
220
389
  console.log('No proposals.');
221
390
  return;
@@ -223,7 +392,14 @@ async function handleProposals(args) {
223
392
  if (!id)
224
393
  throw new Error(`feltdb proposals ${action} requires a proposal id`);
225
394
  if (action === 'get') {
226
- console.log(JSON.stringify({ proposal: await client.get(id), history: (await client.history(id)).events }, null, 2));
395
+ const proposal = await client.get(id);
396
+ const [readiness, history] = await Promise.all([currentProposalReadiness(client, proposal), client.history(id)]);
397
+ const preview = proposal.preview_id ? await client.getPreview(proposal.preview_id).catch(() => undefined) : undefined;
398
+ const result = { proposal, readiness, preview, history: history.events };
399
+ if (args.includes('--json'))
400
+ console.log(JSON.stringify(result, null, 2));
401
+ else
402
+ console.log(renderProposalDiagnostic(proposal, { readiness, history: history.events, preview: preview }).join('\n'));
227
403
  return;
228
404
  }
229
405
  if (action === 'diff') {
@@ -233,12 +409,24 @@ async function handleProposals(args) {
233
409
  return;
234
410
  }
235
411
  if (action === 'approve' || action === 'reject') {
236
- const value = await client.transition(id, action === 'approve' ? 'approved' : 'rejected', 'local-developer', false, args.includes('--approve-authorization'));
412
+ const value = action === 'approve' ? await client.approve(id, 'local-developer', args.includes('--approve-authorization')) : await client.reject(id, 'local-developer');
237
413
  console.log(`✓ ${value.id} ${value.status}`);
238
414
  return;
239
415
  }
240
416
  throw new Error('Usage: feltdb proposals [list|get|diff|approve|reject] [id]');
241
417
  }
418
+ async function currentProposalReadiness(client, proposal) {
419
+ if (!proposal.understanding)
420
+ return client.status(proposal.id);
421
+ const current = await loadLocalDevelopmentContract();
422
+ let moduleIntent;
423
+ try {
424
+ moduleIntent = resolveModuleIntent(proposal.understanding.intent);
425
+ }
426
+ catch { }
427
+ const understanding = await collectApplicationUnderstanding({ intent: proposal.understanding.intent, snapshot: current.snapshot, currentFlow: current.source, moduleIntent, observations: collectRepositoryEvidence(), now: proposal.understanding.created_at });
428
+ return client.status(proposal.id, understanding.evidence_hash);
429
+ }
242
430
  async function handleSourceSync(args) {
243
431
  loadProjectEnvironment();
244
432
  const context = await loadLocalDevelopmentContract();
@@ -617,9 +805,11 @@ For more info, visit: https://github.com/rkendel1/feltdb
617
805
  }
618
806
  async function handleConnect(args) {
619
807
  loadProjectEnvironment();
808
+ const managed = readManagedProjectState();
620
809
  const projectFile = path.resolve('feltdb.config.json');
621
810
  const project = fs.existsSync(projectFile) ? JSON.parse(fs.readFileSync(projectFile, 'utf8')) : {};
622
811
  const url = (args[0]
812
+ || managed.url
623
813
  || process.env.VITE_FELTDB_MANAGED_URL
624
814
  || process.env.VITE_FELTDB_URL
625
815
  || project.server?.url
@@ -634,8 +824,8 @@ async function handleConnect(args) {
634
824
  let health;
635
825
  try {
636
826
  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}` }
827
+ headers: process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY
828
+ ? { Authorization: `Bearer ${process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY}` }
639
829
  : {},
640
830
  signal: controller.signal,
641
831
  });
@@ -658,14 +848,14 @@ async function handleConnect(args) {
658
848
  fs.writeFileSync(connFile, JSON.stringify({
659
849
  url,
660
850
  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',
851
+ authenticated: Boolean(process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY),
852
+ namespace: managed.namespace || project.namespace || 'default',
663
853
  runtime: project.runtime || 'self-hosted',
664
854
  }, null, 2));
665
855
  console.log('✅ Connected to FeltDB\n');
666
856
  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'}`);
857
+ console.log(`Namespace: ${managed.namespace || project.namespace || 'default'}`);
858
+ console.log(`Authenticated: ${process.env.FELTDB_TOKEN || process.env.VITE_FELTDB_API_KEY ? 'yes' : 'no'}`);
669
859
  console.log(`Runtime: ${project.runtime || 'self-hosted'}`);
670
860
  console.log(`Health: ${health.status || 'healthy'}\n`);
671
861
  console.log('Use: feltdb status (to check connection)\n');
@@ -780,7 +970,7 @@ async function handleWorkspaceConnect(args) {
780
970
  console.log(' (when @feltdb/client workspace APIs are available)\n');
781
971
  }
782
972
  async function handleDev(args) {
783
- var _a, _b, _c;
973
+ var _a;
784
974
  const projectDir = process.cwd();
785
975
  if (args.includes('--help')) {
786
976
  console.log('Usage: feltdb dev [--app-url URL] [--studio-port 7701] [--authority-port 7700] [--discovery-port 7799] [--no-open]');
@@ -833,14 +1023,10 @@ async function handleDev(args) {
833
1023
  ? { ...configDefaults, ...JSON.parse(fs.readFileSync(configPath, 'utf-8')) }
834
1024
  : configDefaults;
835
1025
  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.');
1026
+ resolveManagedAuthorityEnvironment(process.env);
841
1027
  }
842
1028
  const runtimeNamespace = config.runtime === 'managed'
843
- ? process.env.VITE_FELTDB_MANAGED_NAMESPACE || config.namespace
1029
+ ? readManagedProjectState(projectDir).namespace || config.namespace
844
1030
  : config.namespace;
845
1031
  const applicationLifecycle = resolveApplicationLifecycle(config);
846
1032
  // Application URL precedence:
@@ -929,7 +1115,7 @@ async function handleDev(args) {
929
1115
  if (compose.status !== 0)
930
1116
  throw new Error(`docker compose failed with status ${compose.status ?? 'unknown'}`);
931
1117
  selfHostedStarted = true;
932
- (_c = process.env).VITE_FELTDB_URL || (_c.VITE_FELTDB_URL = 'http://127.0.0.1:7700');
1118
+ (_a = process.env).VITE_FELTDB_URL || (_a.VITE_FELTDB_URL = 'http://127.0.0.1:7700');
933
1119
  }
934
1120
  if (!process.env.VITE_FELTDB_URL) {
935
1121
  const authorityPort = Number(args.includes('--authority-port') ? args[args.indexOf('--authority-port') + 1] || '7700' : '7700');
@@ -937,6 +1123,7 @@ async function handleDev(args) {
937
1123
  dataDir: path.join(projectDir, '.feltdb-data', 'authority'),
938
1124
  port: authorityPort,
939
1125
  proposalContext: () => loadLocalDevelopmentContract(projectDir),
1126
+ intentObservations: () => collectRepositoryEvidence(projectDir),
940
1127
  });
941
1128
  process.env.VITE_FELTDB_URL = localAuthority.endpoint;
942
1129
  const connectionsPath = path.join(projectDir, '.feltdb', 'connections.json');
@@ -1403,10 +1590,11 @@ async function handleStudio(args, onReady) {
1403
1590
  loadProjectEnvironment();
1404
1591
  const projectConfigPath = path.resolve('feltdb.config.json');
1405
1592
  const projectConfig = fs.existsSync(projectConfigPath) ? JSON.parse(fs.readFileSync(projectConfigPath, 'utf8')) : {};
1593
+ const managedProject = readManagedProjectState();
1406
1594
  const projectIsManaged = projectConfig.runtime === 'managed';
1407
1595
  const connectUrl = args.includes('--connect')
1408
1596
  ? args[args.indexOf('--connect') + 1]
1409
- : projectIsManaged ? process.env.VITE_FELTDB_MANAGED_URL : undefined;
1597
+ : projectIsManaged ? process.env.VITE_FELTDB_MANAGED_URL || managedProject.url : undefined;
1410
1598
  const port = args.includes('--port')
1411
1599
  ? args[args.indexOf('--port') + 1] || '7701'
1412
1600
  : '7701';
@@ -1416,10 +1604,16 @@ async function handleStudio(args, onReady) {
1416
1604
  const open = !args.includes('--no-open');
1417
1605
  const namespace = args.includes('--namespace')
1418
1606
  ? args[args.indexOf('--namespace') + 1] || 'default'
1419
- : projectIsManaged ? process.env.VITE_FELTDB_MANAGED_NAMESPACE || projectConfig.namespace || 'default' : 'default';
1607
+ : projectIsManaged ? managedProject.namespace || projectConfig.namespace || 'default' : 'default';
1420
1608
  const runtime = args.includes('--runtime')
1421
1609
  ? args[args.indexOf('--runtime') + 1] || 'browser'
1422
1610
  : projectIsManaged ? 'managed' : connectUrl ? 'remote' : 'browser';
1611
+ const applicationId = args.includes('--application')
1612
+ ? args[args.indexOf('--application') + 1] || ''
1613
+ : process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managedProject.applicationId || '';
1614
+ const environment = args.includes('--environment')
1615
+ ? args[args.indexOf('--environment') + 1] || 'production'
1616
+ : managedProject.environment || 'production';
1423
1617
  const appUrl = args.includes('--app-url')
1424
1618
  ? args[args.indexOf('--app-url') + 1]
1425
1619
  : undefined;
@@ -1465,14 +1659,14 @@ async function handleStudio(args, onReady) {
1465
1659
  response.setHeader('Cache-Control', 'no-store');
1466
1660
  response.setHeader('Pragma', 'no-cache');
1467
1661
  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',
1662
+ url: connectUrl || managedProject.url || '',
1663
+ namespace: managedProject.namespace || namespace,
1664
+ tenantId: managedProject.tenantId || '',
1665
+ applicationId: process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managedProject.applicationId || '',
1666
+ environment: managedProject.environment || 'production',
1473
1667
  publishEndpoint: '/_feltdb/publish',
1474
1668
  } : 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() } }));
1669
+ response.end(JSON.stringify({ token: connectUrl ? process.env.VITE_FELTDB_API_KEY || '' : '', managedPublish, session, sourceSync: { enabled: true, webllmEnabled, previewOnly, token: studioMutationToken, aiModel: publicAiModelConfiguration() } }));
1476
1670
  return;
1477
1671
  }
1478
1672
  if (pathname === '/_feltdb/contract') {
@@ -1484,12 +1678,12 @@ async function handleStudio(args, onReady) {
1484
1678
  ? JSON.parse(fs.readFileSync(path.resolve('.feltdb/last-published.json'), 'utf8')) : undefined;
1485
1679
  let runningContract;
1486
1680
  let runningSnapshot;
1487
- const applicationId = process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || lastPublished?.applicationId;
1681
+ const applicationId = process.env.VITE_FELTDB_MANAGED_APPLICATION_ID || managedProject.applicationId || lastPublished?.applicationId;
1488
1682
  if (connectUrl && applicationId) {
1489
1683
  try {
1490
1684
  const endpoint = new URL('/v1/application', connectUrl);
1491
1685
  endpoint.searchParams.set('application_id', applicationId);
1492
- endpoint.searchParams.set('environment', process.env.VITE_FELTDB_MANAGED_ENVIRONMENT || lastPublished?.environment || 'production');
1686
+ endpoint.searchParams.set('environment', managedProject.environment || lastPublished?.environment || 'production');
1493
1687
  const headers = {};
1494
1688
  const credential = process.env.FELTDB_MANAGED_CONTROL_API_KEY || process.env.VITE_FELTDB_API_KEY;
1495
1689
  if (credential)
@@ -1514,6 +1708,29 @@ async function handleStudio(args, onReady) {
1514
1708
  }
1515
1709
  return;
1516
1710
  }
1711
+ if (pathname === '/_feltdb/repository-readiness' && request.method === 'GET') {
1712
+ response.setHeader('Content-Type', 'application/json; charset=utf-8');
1713
+ response.setHeader('Cache-Control', 'no-store');
1714
+ try {
1715
+ const proposalId = new URL(request.url || '/', 'http://localhost').searchParams.get('proposal');
1716
+ if (!proposalId)
1717
+ throw new Error('proposal is required');
1718
+ const proposal = await proposalClient.get(proposalId);
1719
+ assertProposalSourcePlan(proposal.source_plan);
1720
+ const local = await loadLocalDevelopmentContract();
1721
+ const repository = await repositoryContext(process.cwd(), proposal.source_plan.files.map(value => value.path));
1722
+ const contractMatches = local.snapshot.contract_hash === proposal.base_contract_hash;
1723
+ const flowMatches = local.flowHash === proposal.base_flow_hash;
1724
+ response.end(JSON.stringify({ ready: contractMatches && flowMatches && repository.dirty.length === 0,
1725
+ contractMatches, flowMatches, gitClean: repository.dirty.length === 0, conflicts: repository.dirty,
1726
+ branch: repository.branch, commit: repository.commit }));
1727
+ }
1728
+ catch (error) {
1729
+ response.statusCode = 409;
1730
+ response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
1731
+ }
1732
+ return;
1733
+ }
1517
1734
  if (pathname === '/_feltdb/proposals/list' && request.method === 'GET') {
1518
1735
  response.setHeader('Content-Type', 'application/json; charset=utf-8');
1519
1736
  response.setHeader('Cache-Control', 'no-store');
@@ -1526,6 +1743,55 @@ async function handleStudio(args, onReady) {
1526
1743
  }
1527
1744
  return;
1528
1745
  }
1746
+ if (pathname === '/_feltdb/intents' && request.method === 'POST') {
1747
+ response.setHeader('Content-Type', 'application/json; charset=utf-8');
1748
+ response.setHeader('Cache-Control', 'no-store');
1749
+ if (request.headers['x-feltdb-studio-token'] !== studioMutationToken) {
1750
+ response.statusCode = 403;
1751
+ response.end(JSON.stringify({ error: 'Invalid Studio token' }));
1752
+ return;
1753
+ }
1754
+ try {
1755
+ const chunks = [];
1756
+ for await (const chunk of request)
1757
+ chunks.push(Buffer.from(chunk));
1758
+ const input = JSON.parse(Buffer.concat(chunks).toString('utf8'));
1759
+ if (!input.intent?.trim())
1760
+ throw new Error('An intent is required');
1761
+ const execution = await proposalClient.executeIntent(input.intent, 'studio-developer');
1762
+ if (!execution.proposal) {
1763
+ response.statusCode = execution.blockers.length ? 409 : 200;
1764
+ response.end(JSON.stringify(execution));
1765
+ return;
1766
+ }
1767
+ const flowProposal = {
1768
+ baseContractHash: execution.proposal.base_contract_hash,
1769
+ baseFlowHash: execution.proposal.base_flow_hash,
1770
+ proposedFlow: execution.proposal.proposed_flow,
1771
+ summary: execution.proposal.summary,
1772
+ changes: execution.proposal.contract_diff,
1773
+ warnings: execution.proposal.warnings,
1774
+ source_plan: execution.proposal.source_plan,
1775
+ implementation: execution.plan ? {
1776
+ intent: { action: execution.intent.action === 'extend' ? 'extend' : 'implement', module: execution.plan.module.id, provider: execution.plan.module.provider, version: execution.plan.module.version, original: execution.intent.raw_request },
1777
+ module: execution.plan.module, alreadyInstalled: execution.plan.already_installed, owner: Object.values(execution.plan.ownership)[0], ownerClaim: execution.plan.module.placement.owner_claim, primaryApplicationModel: execution.plan.module.placement.primary_application_model,
1778
+ applicationModels: execution.plan.state_changes, relationships: execution.plan.relationships, events: execution.plan.events,
1779
+ workflows: execution.plan.workflows, capabilities: execution.plan.capabilities, authorization: execution.plan.authorization,
1780
+ configuration: execution.plan.configuration, secrets: execution.plan.secrets, dependencies: execution.plan.dependencies, stateFlow: [], warnings: execution.plan.warnings,
1781
+ } : undefined,
1782
+ understanding: execution.understanding,
1783
+ };
1784
+ const review = await reviewFlowProposal(flowProposal);
1785
+ response.end(JSON.stringify({ execution, proposal: flowProposal, proposalId: execution.proposal.id,
1786
+ changes: describeProposal(review), sourcePlan: execution.proposal.source_plan.files.map(value => value.path),
1787
+ authorizationChanges: review.authorizationChanges, migration: review.migration, contractHash: review.proposedContract.hashes.application }));
1788
+ }
1789
+ catch (error) {
1790
+ response.statusCode = 400;
1791
+ response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
1792
+ }
1793
+ return;
1794
+ }
1529
1795
  const proposalDetail = pathname.match(/^\/_feltdb\/proposals\/(p_[A-Za-z0-9]+)$/);
1530
1796
  if (proposalDetail && request.method === 'GET') {
1531
1797
  response.setHeader('Content-Type', 'application/json; charset=utf-8');
@@ -1578,6 +1844,7 @@ async function handleStudio(args, onReady) {
1578
1844
  stored = await proposalClient.create({ application_id: current.snapshot.application.application_id,
1579
1845
  environment: current.snapshot.application.environment, base_contract_hash: proposal.baseContractHash,
1580
1846
  base_flow_hash: proposal.baseFlowHash, proposed_flow: proposal.proposedFlow, summary: proposal.summary,
1847
+ proposal_version: 1, contract_diff: [], source_plan: { files: [] },
1581
1848
  warnings: proposal.warnings, actor: 'studio-developer', metadata: { model: publicAiModelConfiguration().model, created_from: 'studio' } });
1582
1849
  await proposalClient.validate(stored.id, 'feltdb-validator', review.proposedContract.manifest);
1583
1850
  stored = (await proposalClient.preview(stored.id, 'studio-developer')).proposal;
@@ -1715,6 +1982,9 @@ async function handleStudio(args, onReady) {
1715
1982
  const parameters = new URLSearchParams({ namespace, runtime });
1716
1983
  if (connectUrl)
1717
1984
  parameters.set('connect', connectUrl);
1985
+ if (applicationId)
1986
+ parameters.set('application_id', applicationId);
1987
+ parameters.set('environment', environment);
1718
1988
  if (appUrl)
1719
1989
  parameters.set('app', appUrl);
1720
1990
  if (session.workspaceId)
@@ -1751,9 +2021,14 @@ Commands:
1751
2021
  ai model show Show the selected local or user-provided model
1752
2022
  ai model set Select WebLLM or an OpenAI-compatible model
1753
2023
  ai context Show the secret-free contract grounding snapshot
2024
+ ai proposals List bounded proposals [--status/--environment/--json]
2025
+ ai proposal <id> Show canonical proposal, readiness, preview, and history
2026
+ ai approve <id> Approve a current preview [--yes]
1754
2027
  ai review <file> Validate and review a WebLLM flow proposal
1755
- ai apply <id> Apply an approved proposal and synchronize source
2028
+ ai apply <id> Atomically apply an approved proposal [--yes]
1756
2029
  proposals List durable FeltDB application proposals
2030
+ module list List deterministic FeltDB modules
2031
+ module inspect <id> Inspect a resolved module semantic contract
1757
2032
  proposals get <id> Show a proposal and its lifecycle history
1758
2033
  proposals diff <id> Show contract and structured source impact
1759
2034
  proposals approve <id> Approve a previewed proposal
@@ -1776,6 +2051,7 @@ Commands:
1776
2051
  Studio:
1777
2052
  feltdb studio Launch local Studio
1778
2053
  feltdb studio --connect <url> Connect to remote instance
2054
+ --application <id> --environment <name> Select one application environment
1779
2055
  feltdb studio --port 7701 --no-open Start on port 7701 without opening browser
1780
2056
 
1781
2057
  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
+ }