@feltdb/core 0.8.0 → 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 (74) hide show
  1. package/dist/application-development.d.ts +7 -2
  2. package/dist/application-development.d.ts.map +1 -1
  3. package/dist/application-development.js +7 -13
  4. package/dist/application-manifest.d.ts +1 -0
  5. package/dist/application-manifest.d.ts.map +1 -1
  6. package/dist/application-manifest.js +1 -1
  7. package/dist/application-understanding.d.ts +76 -0
  8. package/dist/application-understanding.d.ts.map +1 -0
  9. package/dist/application-understanding.js +282 -0
  10. package/dist/cli/ai-model-config.js +3 -3
  11. package/dist/cli/commands.js +131 -38
  12. package/dist/cli/proposal-client.js +5 -1
  13. package/dist/cli/publish-engine.js +1 -1
  14. package/dist/cli/repository-evidence.js +55 -0
  15. package/dist/cli/source-sync.js +190 -75
  16. package/dist/convergence.d.ts +55 -0
  17. package/dist/convergence.d.ts.map +1 -0
  18. package/dist/convergence.js +63 -0
  19. package/dist/create/managed-account.js +1 -0
  20. package/dist/create/package-versions.js +1 -1
  21. package/dist/create/server-source/crates/feltdb/src/application.rs +4 -0
  22. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +14 -0
  23. package/dist/create/server-source/crates/feltdb-server/src/main.rs +174 -24
  24. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +8 -1
  25. package/dist/create/template/default-project/src/context/AuthContext.tsx +9 -1
  26. package/dist/create/template/default-project/src/pages/SignUp.tsx +1 -1
  27. package/dist/flowspec.d.ts.map +1 -1
  28. package/dist/flowspec.js +6 -1
  29. package/dist/http-client.d.ts +32 -1
  30. package/dist/http-client.d.ts.map +1 -1
  31. package/dist/http-client.js +14 -2
  32. package/dist/http-db.d.ts +3 -0
  33. package/dist/http-db.d.ts.map +1 -1
  34. package/dist/http-db.js +28 -3
  35. package/dist/index-core.d.ts +3 -0
  36. package/dist/index-core.d.ts.map +1 -1
  37. package/dist/index-core.js +3 -0
  38. package/dist/intent.d.ts +98 -0
  39. package/dist/intent.d.ts.map +1 -0
  40. package/dist/intent.js +343 -0
  41. package/dist/module-intent.d.ts +19 -5
  42. package/dist/module-intent.d.ts.map +1 -1
  43. package/dist/module-intent.js +211 -62
  44. package/dist/module.d.ts +41 -12
  45. package/dist/module.d.ts.map +1 -1
  46. package/dist/module.js +543 -38
  47. package/dist/proposal.d.ts +13 -1
  48. package/dist/proposal.d.ts.map +1 -1
  49. package/dist/proposal.js +21 -4
  50. package/dist/studio/components/AskFeltDB.d.ts.map +1 -1
  51. package/dist/studio/components/ProposalBrowser.d.ts +1 -1
  52. package/dist/studio/components/ProposalBrowser.d.ts.map +1 -1
  53. package/dist/studio/components/index.js +1 -1
  54. package/dist/studio/{components-CNSNrmA9.js → components-Duq2xQWt.js} +251 -193
  55. package/dist/studio/index.js +1 -1
  56. package/dist/studio/studio.css +1 -1
  57. package/dist/studio/utils/proposal-api.d.ts +4 -1
  58. package/dist/studio/utils/proposal-api.d.ts.map +1 -1
  59. package/dist/studio/utils/proposal-api.js +3 -0
  60. package/dist/studio-app/assets/{feltdb_wasm-CSB6KVgw.js → feltdb_wasm-bIqcRzAr.js} +1 -1
  61. package/dist/studio-app/assets/feltdb_wasm_bg-BE79okwX.wasm +0 -0
  62. package/dist/studio-app/assets/{index-yazaWMUN.css → index-Cd_NPw14.css} +1 -1
  63. package/dist/studio-app/assets/index-XGYdlElN.js +29 -0
  64. package/dist/studio-app/index.html +2 -2
  65. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  66. package/dist/workspace/local-development-authority.d.ts +2 -0
  67. package/dist/workspace/local-development-authority.d.ts.map +1 -1
  68. package/dist/workspace/local-development-authority.js +65 -2
  69. package/package.json +1 -1
  70. package/dist/studio-app/assets/dist-CCOk39Uc.js +0 -1
  71. package/dist/studio-app/assets/feltdb_wasm_bg-Dd1fnO9U.wasm +0 -0
  72. package/dist/studio-app/assets/index-Bncji3aN.js +0 -43
  73. package/dist/studio-app/assets/lib-B2_7fcn7.js +0 -69
  74. package/dist/studio-app/assets/worker-CARZ5g7K.js +0 -69
@@ -9,7 +9,9 @@ import { createRequire } from 'module';
9
9
  import { spawn, spawnSync } from 'child_process';
10
10
  import { randomBytes } from 'crypto';
11
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
+ 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';
13
15
  import { discoverWorkspace, ensureWorkspaceGitIgnored, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace, startPairingDiscoveryServer } from './workspace-integration.js';
14
16
  import { applicationUrlCandidate, detectApplication, discoverApplicationUrl } from './application.js';
15
17
  import { resolveApplicationLifecycle, startManagedApplication, waitForManagedApplication } from './application-lifecycle.js';
@@ -111,7 +113,8 @@ function handleModules(args) {
111
113
  const flowPath = path.resolve('feltdb.flow');
112
114
  const spec = args.includes('--application') && fs.existsSync(flowPath) ? parseFlowSpec(fs.readFileSync(flowPath, 'utf8')) : undefined;
113
115
  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' });
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 || '' });
115
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)}`)) ?? [];
116
119
  console.log(JSON.stringify({ ...module, application_usage: usage }, null, 2));
117
120
  return;
@@ -140,6 +143,30 @@ function validateRepositoryBuild(args) {
140
143
  console.log(`✓ ${name} passed`);
141
144
  }
142
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;
169
+ }
143
170
  async function handleAi(args) {
144
171
  loadProjectEnvironment();
145
172
  if (args[0] === 'model') {
@@ -171,6 +198,16 @@ async function handleAi(args) {
171
198
  return handleStudio(args, studioUrl => console.log(`Ask FeltDB: ${studioUrl}`));
172
199
  }
173
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
+ }
174
211
  if (action === 'proposals')
175
212
  return handleProposals(['list', ...args.slice(1)]);
176
213
  if (action === 'proposal') {
@@ -185,7 +222,7 @@ async function handleAi(args) {
185
222
  throw new Error('Usage: feltdb ai approve <id> [--yes]');
186
223
  const client = proposalService();
187
224
  const proposal = await client.get(id);
188
- const readiness = await client.status(id);
225
+ const readiness = await currentProposalReadiness(client, proposal);
189
226
  if (proposal.status !== 'previewed')
190
227
  throw new Error(`Cannot approve Proposal ${id}. Current status: ${proposal.status}. A current preview is required.`);
191
228
  if (readiness.blockers.length)
@@ -231,7 +268,7 @@ async function handleAi(args) {
231
268
  throw new Error('A proposal id or JSON file is required');
232
269
  const stored = target.startsWith('p_') ? await proposalService().get(target) : undefined;
233
270
  const proposal = stored ? { baseContractHash: stored.base_contract_hash, baseFlowHash: stored.base_flow_hash,
234
- 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 }
235
272
  : JSON.parse(fs.readFileSync(path.resolve(target), 'utf8'));
236
273
  if (stored && stored.status !== 'approved')
237
274
  throw new Error(`Cannot apply Proposal ${stored.id}. Current status: ${stored.status}. Approval is required. Run: feltdb ai approve ${stored.id}`);
@@ -250,6 +287,7 @@ async function handleAi(args) {
250
287
  });
251
288
  await proposalService().transition(stored.id, 'applied', 'cli-repository-applier', true);
252
289
  console.log(`✓ Reconciled and marked ${stored.id} applied`);
290
+ await verifyAndRecord({ ...stored, status: 'applied' }, false);
253
291
  return;
254
292
  }
255
293
  if (stored) {
@@ -291,50 +329,43 @@ async function handleAi(args) {
291
329
  console.log(`✓ Applied validated flow proposal (${review.proposedContract.hashes.application})`);
292
330
  for (const file of review.written)
293
331
  console.log(` ${file}`);
294
- if (stored)
295
- 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
+ }
296
336
  return;
297
337
  }
298
338
  const developerIntent = args.filter(value => !value.startsWith('--')).join(' ').trim();
299
339
  if (developerIntent) {
300
340
  const resolved = resolveModuleIntent(developerIntent);
301
341
  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
342
  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'));
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'));
333
364
  console.log(`Use:\n feltdb ai proposal ${stored.id}`);
334
365
  return;
335
366
  }
336
367
  }
337
- throw new Error('Usage: feltdb ai ["implement <module>"|context|proposals|proposal <id>|approve <id>|review <proposal.json>|apply <id>]');
368
+ throw new Error('Usage: feltdb ai ["implement <module>"|context|proposals|proposal <id>|approve <id>|review <proposal.json>|apply <id>|verify <id>|converge <id>]');
338
369
  }
339
370
  async function handleProposals(args) {
340
371
  loadProjectEnvironment();
@@ -362,7 +393,7 @@ async function handleProposals(args) {
362
393
  throw new Error(`feltdb proposals ${action} requires a proposal id`);
363
394
  if (action === 'get') {
364
395
  const proposal = await client.get(id);
365
- const [readiness, history] = await Promise.all([client.status(id), client.history(id)]);
396
+ const [readiness, history] = await Promise.all([currentProposalReadiness(client, proposal), client.history(id)]);
366
397
  const preview = proposal.preview_id ? await client.getPreview(proposal.preview_id).catch(() => undefined) : undefined;
367
398
  const result = { proposal, readiness, preview, history: history.events };
368
399
  if (args.includes('--json'))
@@ -384,6 +415,18 @@ async function handleProposals(args) {
384
415
  }
385
416
  throw new Error('Usage: feltdb proposals [list|get|diff|approve|reject] [id]');
386
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
+ }
387
430
  async function handleSourceSync(args) {
388
431
  loadProjectEnvironment();
389
432
  const context = await loadLocalDevelopmentContract();
@@ -1080,6 +1123,7 @@ async function handleDev(args) {
1080
1123
  dataDir: path.join(projectDir, '.feltdb-data', 'authority'),
1081
1124
  port: authorityPort,
1082
1125
  proposalContext: () => loadLocalDevelopmentContract(projectDir),
1126
+ intentObservations: () => collectRepositoryEvidence(projectDir),
1083
1127
  });
1084
1128
  process.env.VITE_FELTDB_URL = localAuthority.endpoint;
1085
1129
  const connectionsPath = path.join(projectDir, '.feltdb', 'connections.json');
@@ -1699,6 +1743,55 @@ async function handleStudio(args, onReady) {
1699
1743
  }
1700
1744
  return;
1701
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
+ }
1702
1795
  const proposalDetail = pathname.match(/^\/_feltdb\/proposals\/(p_[A-Za-z0-9]+)$/);
1703
1796
  if (proposalDetail && request.method === 'GET') {
1704
1797
  response.setHeader('Content-Type', 'application/json; charset=utf-8');
@@ -39,6 +39,8 @@ export class ProposalServiceClient {
39
39
  }
40
40
  actor(input = {}) { return { application_id: this.connection.applicationId, environment: this.connection.environment, ...input }; }
41
41
  create(input) { return this.request('/v1/proposals', { method: 'POST', body: JSON.stringify(input) }); }
42
+ executeIntent(rawRequest, actor = 'cli-developer') { return this.request('/v1/intents', { method: 'POST', body: JSON.stringify(this.actor({ raw_request: rawRequest, actor })) }); }
43
+ intents() { return this.request(this.scoped('/v1/intents')); }
42
44
  list(filters = {}) { const query = new URLSearchParams(); if (this.connection.applicationId)
43
45
  query.set('application_id', this.connection.applicationId); query.set('environment', filters.environment || this.connection.environment || 'local'); if (filters.status)
44
46
  query.set('status', filters.status); if (filters.limit)
@@ -46,12 +48,14 @@ export class ProposalServiceClient {
46
48
  query.set('cursor', filters.cursor); return this.request(`/v1/proposals?${query}`); }
47
49
  get(id) { return this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}`)); }
48
50
  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`)); }
51
+ status(id, currentEvidenceHash) { const path = this.scoped(`/v1/proposals/${encodeURIComponent(id)}/status`); return this.request(currentEvidenceHash ? `${path}&current_evidence_hash=${encodeURIComponent(currentEvidenceHash)}` : path); }
50
52
  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 })) }); }
51
53
  preview(id, actor = 'local-developer') { return this.request(`/v1/proposals/${encodeURIComponent(id)}/preview`, { method: 'POST', body: JSON.stringify(this.actor({ actor })) }); }
52
54
  getPreview(id) { return this.request(this.scoped(`/v1/previews/${encodeURIComponent(id)}`)); }
53
55
  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
56
  reject(id, actor = 'local-developer', reason) { return this.request(`/v1/proposals/${encodeURIComponent(id)}/reject`, { method: 'POST', body: JSON.stringify(this.actor({ actor, reason })) }); }
57
+ convergence(id) { return this.request(this.scoped(`/v1/proposals/${encodeURIComponent(id)}/convergence`)); }
58
+ recordConvergence(id, result) { return this.request(`/v1/proposals/${encodeURIComponent(id)}/convergence`, { method: 'POST', body: JSON.stringify(this.actor(result)) }); }
55
59
  transition(id, status, actor = 'local-developer', repositoryAuthority = false, approveAuthorization = false) {
56
60
  return this.request(`/v1/proposals/${encodeURIComponent(id)}/status`, { method: 'PATCH', headers: repositoryAuthority && this.connection.proposalAuthorityToken
57
61
  ? { 'FeltDB-Proposal-Authority': this.connection.proposalAuthorityToken, 'FeltDB-Repository-Apply': '1' } : {}, body: JSON.stringify(this.actor({ status, actor, approve_authorization: approveAuthorization })) });
@@ -81,7 +81,7 @@ export function buildPublishBundle(cwd, configuration) {
81
81
  'feltdb.artifact_hash': artifactHash,
82
82
  'feltdb.namespace': configuration.namespace,
83
83
  };
84
- manifest.artifacts = artifacts.map(value => ({ name: value.path, uri: `data:${value.mediaType};base64,${value.content}`, media_type: value.mediaType }));
84
+ manifest.artifacts = artifacts.map(value => ({ name: value.path, uri: `artifact://${value.hash}`, media_type: value.mediaType, ...(value.path === 'feltdb.flow' ? { content: value.content } : {}) }));
85
85
  const git = spawnSync('git', ['rev-parse', 'HEAD'], { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
86
86
  const sourceRevision = git.status === 0 ? git.stdout.trim() : undefined;
87
87
  if (sourceRevision)
@@ -0,0 +1,55 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+ const sourceExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.rs']);
5
+ function sourceFiles(root) {
6
+ const found = [];
7
+ const visit = (directory) => {
8
+ if (found.length >= 300 || !fs.existsSync(directory))
9
+ return;
10
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
11
+ if (found.length >= 300)
12
+ break;
13
+ const absolute = path.join(directory, entry.name);
14
+ if (entry.isDirectory() && !['node_modules', 'dist', 'build', '.git', '.feltdb-data'].includes(entry.name))
15
+ visit(absolute);
16
+ else if (entry.isFile() && sourceExtensions.has(path.extname(entry.name)) && fs.statSync(absolute).size <= 64 * 1024)
17
+ found.push(absolute);
18
+ }
19
+ };
20
+ visit(path.join(root, 'src'));
21
+ return found;
22
+ }
23
+ /** Produces bounded claims and file references; source text never leaves this function. */
24
+ export function collectRepositoryEvidence(cwd = process.cwd()) {
25
+ const observations = [];
26
+ try {
27
+ const status = execFileSync('git', ['status', '--porcelain=v1'], { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
28
+ observations.push({ source: 'git', claim: 'git.working_tree', value: status.trim() ? 'dirty' : 'clean', source_ref: '.git' });
29
+ }
30
+ catch { }
31
+ for (const absolute of sourceFiles(cwd)) {
32
+ if (observations.length >= 50)
33
+ break;
34
+ const relative = path.relative(cwd, absolute).split(path.sep).join('/');
35
+ let source = '';
36
+ try {
37
+ source = fs.readFileSync(absolute, 'utf8');
38
+ }
39
+ catch {
40
+ continue;
41
+ }
42
+ const add = (claim, value) => observations.push({ source: 'repository', claim, value, source_ref: relative });
43
+ if (/\b(authenticated|useSession|session|signIn|signOut)\b/i.test(source))
44
+ add('repository.authentication', 'authenticated session implementation observed');
45
+ if (/\borganization(?:Id|_id)?\b/i.test(source) && /\b(project|settings|billing)\b/i.test(source))
46
+ add('repository.organization_boundary', 'organization-scoped implementation observed');
47
+ if (/\buser(?:Id|_id|\.id)?\b/i.test(source) && /\b(project|settings|billing)\b/i.test(source))
48
+ add('repository.user_boundary', 'user-scoped implementation observed');
49
+ if (/\b(checkout|subscription|billing|stripe)\b/i.test(source))
50
+ add('repository.billing_surface', 'billing implementation surface observed');
51
+ if (/\b(github|repository|installation|webhook)\b/i.test(source))
52
+ add('repository.integration_surface', 'external collaboration implementation surface observed');
53
+ }
54
+ return observations.slice(0, 50);
55
+ }