@feltdb/core 0.9.1 → 0.11.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/dist/cli/commands.js +151 -22
  2. package/dist/cli/development-session.js +16 -0
  3. package/dist/cli/index.js +1 -1
  4. package/dist/cli/studio-artifact.js +33 -0
  5. package/dist/collection.d.ts.map +1 -1
  6. package/dist/collection.js +6 -4
  7. package/dist/create/package-versions.js +1 -1
  8. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +8 -1
  9. package/dist/create/server-source/crates/feltdb/src/application.rs +16 -0
  10. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +21 -22
  11. package/dist/create/server-source/crates/feltdb/src/lib.rs +556 -45
  12. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +48 -3
  13. package/dist/create/server-source/crates/feltdb/src/state_model.rs +569 -4
  14. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +1 -0
  15. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +708 -0
  16. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
  17. package/dist/create/server-source/crates/feltdb/src/workload.rs +480 -44
  18. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +123 -0
  19. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +281 -0
  20. package/dist/create/server-source/crates/feltdb/tests/local_process_ownership.rs +69 -0
  21. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +15 -3
  22. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +49 -24
  23. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +328 -0
  24. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +20 -3
  25. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +14 -4
  26. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +0 -4
  27. package/dist/create/server-source/crates/feltdb-server/src/canonical_principals.rs +1093 -0
  28. package/dist/create/server-source/crates/feltdb-server/src/causal.rs +905 -144
  29. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +9 -6
  30. package/dist/create/server-source/crates/feltdb-server/src/control_plane.rs +90 -0
  31. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +1 -0
  32. package/dist/create/server-source/crates/feltdb-server/src/main.rs +418 -82
  33. package/dist/create/server-source/crates/feltdb-server/src/principals.rs +9 -501
  34. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +22 -0
  35. package/dist/create/server-source/crates/feltdb-server/tests/principal_tenant_migration.rs +247 -0
  36. package/dist/db.d.ts +6 -5
  37. package/dist/db.d.ts.map +1 -1
  38. package/dist/db.js +18 -24
  39. package/dist/deployment.d.ts.map +1 -1
  40. package/dist/deployment.js +4 -2
  41. package/dist/embedded-transaction.d.ts.map +1 -1
  42. package/dist/embedded-transaction.js +4 -0
  43. package/dist/error-codes.d.ts +12 -0
  44. package/dist/error-codes.d.ts.map +1 -1
  45. package/dist/error-codes.js +20 -2
  46. package/dist/feltdb.d.ts +4 -0
  47. package/dist/feltdb.d.ts.map +1 -1
  48. package/dist/file-db.d.ts +12 -0
  49. package/dist/file-db.d.ts.map +1 -1
  50. package/dist/file-db.js +66 -6
  51. package/dist/http-db.d.ts +4 -0
  52. package/dist/http-db.d.ts.map +1 -1
  53. package/dist/http-db.js +73 -12
  54. package/dist/index-core.d.ts +1 -0
  55. package/dist/index-core.d.ts.map +1 -1
  56. package/dist/index-core.js +1 -0
  57. package/dist/memory-db.d.ts +1 -0
  58. package/dist/memory-db.d.ts.map +1 -1
  59. package/dist/memory-db.js +1 -1
  60. package/dist/public-contract.d.ts +112 -0
  61. package/dist/public-contract.d.ts.map +1 -0
  62. package/dist/public-contract.js +125 -0
  63. package/dist/studio/app.d.ts +23 -0
  64. package/dist/studio/app.d.ts.map +1 -1
  65. package/dist/studio/components/ContractInspector.d.ts +5 -0
  66. package/dist/studio/components/ContractInspector.d.ts.map +1 -1
  67. package/dist/studio/components/SettingsPanel.d.ts +4 -1
  68. package/dist/studio/components/SettingsPanel.d.ts.map +1 -1
  69. package/dist/studio/components/index.js +4 -4
  70. package/dist/studio/{components-CehkcX_k.js → components-BHTKlUgT.js} +323 -304
  71. package/dist/studio/control-plane/Data.d.ts.map +1 -1
  72. package/dist/studio/control-plane/Instance.d.ts +10 -0
  73. package/dist/studio/control-plane/Instance.d.ts.map +1 -1
  74. package/dist/studio/control-plane/Operations.d.ts +3 -0
  75. package/dist/studio/control-plane/Operations.d.ts.map +1 -1
  76. package/dist/studio/control-plane/State.d.ts.map +1 -1
  77. package/dist/studio/control-plane/index.d.ts +4 -2
  78. package/dist/studio/control-plane/index.d.ts.map +1 -1
  79. package/dist/studio/control-plane/index.js +2 -2
  80. package/dist/studio/control-plane-DP8ljTDO.js +3207 -0
  81. package/dist/studio/index.js +96 -75
  82. package/dist/studio/utils/api.d.ts +1 -1
  83. package/dist/studio/utils/api.d.ts.map +1 -1
  84. package/dist/studio/utils/control-plane-api.d.ts +12 -1
  85. package/dist/studio/utils/control-plane-api.d.ts.map +1 -1
  86. package/dist/studio/utils/control-plane-api.js +1 -1
  87. package/dist/studio/utils/index.js +16 -16
  88. package/dist/studio/utils/service-api.d.ts +8 -0
  89. package/dist/studio/utils/service-api.d.ts.map +1 -1
  90. package/dist/studio/utils/service-api.js +15 -12
  91. package/dist/studio-app/assets/{feltdb_wasm-DksXDnLa.js → feltdb_wasm-Dlr_kLlk.js} +1 -1
  92. package/dist/studio-app/assets/feltdb_wasm_bg-CkozAQKb.wasm +0 -0
  93. package/dist/studio-app/assets/index-H_L5pjur.js +30 -0
  94. package/dist/studio-app/feltdb-studio.json +6 -0
  95. package/dist/studio-app/index.html +1 -1
  96. package/dist/transaction.d.ts +9 -1
  97. package/dist/transaction.d.ts.map +1 -1
  98. package/dist/transaction.js +6 -2
  99. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  100. package/dist/workspace/local-development-authority.d.ts +8 -0
  101. package/dist/workspace/local-development-authority.d.ts.map +1 -1
  102. package/dist/workspace/local-development-authority.js +136 -1
  103. package/package.json +2 -2
  104. package/dist/studio/control-plane-D6TT2fCQ.js +0 -3064
  105. package/dist/studio-app/assets/feltdb_wasm_bg-Yy4QRHJT.wasm +0 -0
  106. package/dist/studio-app/assets/index-CZ55nLgp.js +0 -30
@@ -5,7 +5,7 @@ import fs from 'fs';
5
5
  import path from 'path';
6
6
  import http from 'http';
7
7
  import net from 'net';
8
- import { createRequire } from 'module';
8
+ import { createRequire } from 'node:module';
9
9
  import { spawn, spawnSync } from 'child_process';
10
10
  import { randomBytes } from 'crypto';
11
11
  import { createInterface } from 'node:readline/promises';
@@ -15,7 +15,7 @@ import { collectRepositoryEvidence } from './repository-evidence.js';
15
15
  import { discoverWorkspace, ensureWorkspaceGitIgnored, generatePairingToken, persistPairingToken, displayWorkspaceStatus, initializeWorkspace, startPairingDiscoveryServer } from './workspace-integration.js';
16
16
  import { applicationUrlCandidate, detectApplication, discoverApplicationUrl } from './application.js';
17
17
  import { resolveApplicationLifecycle, startManagedApplication, waitForManagedApplication } from './application-lifecycle.js';
18
- import { createDevelopmentSession, developmentSessionEnvironment, developmentSessionSummary, transitionDevelopmentSession } from './development-session.js';
18
+ import { createDevelopmentSession, developmentSessionEnvironment, developmentSessionSummary, developmentStudioArguments, transitionDevelopmentSession } from './development-session.js';
19
19
  import { openApplication, openStudio } from './browser-opening.js';
20
20
  import { publishApplication } from './publish-engine.js';
21
21
  import { applyFlowProposalAtomic, assertProposalSourcePlan, describeProposal, loadLocalDevelopmentContract, repositoryContext, reviewFlowProposal, syncGeneratedSource } from './source-sync.js';
@@ -23,6 +23,8 @@ import { configureAiModel, generateConfiguredFlowProposal, publicAiModelConfigur
23
23
  import { proposalService } from './proposal-client.js';
24
24
  import { resolveManagedAuthorityEnvironment } from './managed-environment.js';
25
25
  import { readManagedProjectState } from './managed-project.js';
26
+ import { resolveCanonicalStudioArtifact } from './studio-artifact.js';
27
+ import { configureManagedAccount, finalizeManagedProvisioning } from '../create/index.js';
26
28
  function loadProjectEnvironment(file = path.resolve('.env.local')) {
27
29
  if (!fs.existsSync(file))
28
30
  return;
@@ -60,6 +62,8 @@ export async function handleCommand(command, args) {
60
62
  return handleServer(args);
61
63
  case 'keys':
62
64
  return handleKeys(args);
65
+ case 'init-managed':
66
+ return handleInitManaged(args);
63
67
  case 'connect':
64
68
  return handleConnect(args);
65
69
  case 'status':
@@ -863,6 +867,123 @@ async function handleConnect(args) {
863
867
  console.log(`Health: ${health.status || 'healthy'}\n`);
864
868
  console.log('Use: feltdb status (to check connection)\n');
865
869
  }
870
+ async function promptSecret(message) {
871
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
872
+ return '';
873
+ const input = process.stdin;
874
+ const output = process.stdout;
875
+ const wasRaw = input.isRaw;
876
+ const readline = await import('node:readline');
877
+ readline.emitKeypressEvents(input);
878
+ input.setRawMode(true);
879
+ input.resume();
880
+ output.write(message);
881
+ return new Promise(resolve => {
882
+ let value = '';
883
+ const finish = () => {
884
+ input.off('keypress', onKeypress);
885
+ input.setRawMode(Boolean(wasRaw));
886
+ input.pause();
887
+ output.write('\n');
888
+ resolve(value);
889
+ };
890
+ const onKeypress = (character, key) => {
891
+ if (key.ctrl && key.name === 'c')
892
+ process.exit(130);
893
+ if (key.name === 'return' || key.name === 'enter')
894
+ return finish();
895
+ if (key.name === 'backspace') {
896
+ value = value.slice(0, -1);
897
+ return;
898
+ }
899
+ if (!key.ctrl && !key.meta && character)
900
+ value += character;
901
+ };
902
+ input.on('keypress', onKeypress);
903
+ });
904
+ }
905
+ async function handleInitManaged(args) {
906
+ loadProjectEnvironment();
907
+ const projectDir = process.cwd();
908
+ // Check if already managed
909
+ const managedState = readManagedProjectState(projectDir);
910
+ if (managedState.applicationId) {
911
+ console.log('✅ This project is already configured for managed FeltDB\n');
912
+ console.log(`Application: ${managedState.applicationId}`);
913
+ console.log(`Namespace: ${managedState.namespace}`);
914
+ console.log(`URL: ${managedState.url}`);
915
+ if (managedState.revisionId) {
916
+ console.log(`Revision: ${managedState.revisionId}`);
917
+ }
918
+ console.log('\nTo update credentials, manually edit .env.local and .feltdb/managed.json\n');
919
+ return;
920
+ }
921
+ // Prompt for credentials
922
+ console.log('☁️ Setting up managed FeltDB for this project\n');
923
+ const prompt = await import('node:readline/promises').then(m => m.createInterface({ input: process.stdin, output: process.stdout }));
924
+ const email = args[0] || await prompt.question('Email: ');
925
+ if (!email)
926
+ throw new Error('Email is required');
927
+ const password = args[1] || await promptSecret('Password (8+ characters): ');
928
+ if (password.length < 8)
929
+ throw new Error('Password must be at least 8 characters');
930
+ prompt.close();
931
+ console.log('\nProvisioning managed account...\n');
932
+ try {
933
+ const projectConfig = fs.existsSync(path.join(projectDir, 'feltdb.config.json'))
934
+ ? JSON.parse(fs.readFileSync(path.join(projectDir, 'feltdb.config.json'), 'utf8'))
935
+ : {};
936
+ const namespace = projectConfig.namespace || path.basename(projectDir);
937
+ const applicationName = projectConfig.name || path.basename(projectDir);
938
+ const managed = await configureManagedAccount({
939
+ projectDir,
940
+ applicationName,
941
+ namespace,
942
+ email,
943
+ password,
944
+ });
945
+ console.log(`✓ Managed application: ${managed.application.name}`);
946
+ console.log(`✓ Namespace: ${managed.application.namespace || namespace}`);
947
+ console.log(`✓ Environment configured in .env.local\n`);
948
+ // Ask if user wants to publish the initial revision
949
+ const publishPrompt = await import('node:readline/promises').then(m => m.createInterface({ input: process.stdin, output: process.stdout }));
950
+ const shouldPublish = await publishPrompt.question('Publish initial application revision now? [Y/n] ');
951
+ publishPrompt.close();
952
+ if (!/^(n|no)$/i.test(shouldPublish.trim())) {
953
+ console.log('\n🔨 Building and publishing initial revision...\n');
954
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
955
+ // Run publish
956
+ await new Promise((resolve, reject) => {
957
+ const child = spawn(npm, ['run', 'feltdb:publish'], {
958
+ cwd: projectDir,
959
+ stdio: 'inherit',
960
+ env: process.env,
961
+ });
962
+ child.once('error', reject);
963
+ child.once('exit', code => code === 0 ? resolve() : reject(new Error(`feltdb publish exited with status ${code ?? 'unknown'}`)));
964
+ });
965
+ console.log('\n✓ Initial revision published\n');
966
+ // Finalize and verify
967
+ try {
968
+ const finalized = await finalizeManagedProvisioning({ projectDir });
969
+ console.log(`✓ Managed runtime verified at revision ${finalized.revisionId}\n`);
970
+ }
971
+ catch (error) {
972
+ console.log('⚠️ Revision published but verification skipped.\n');
973
+ console.log('Your application is ready to use. Run: npm run dev\n');
974
+ }
975
+ }
976
+ else {
977
+ console.log('\n✅ Managed setup complete!\n');
978
+ console.log('Next steps:');
979
+ console.log(' 1. npm run feltdb:publish (to publish your initial revision)');
980
+ console.log(' 2. npm run dev (to start development)\n');
981
+ }
982
+ }
983
+ catch (error) {
984
+ throw error;
985
+ }
986
+ }
866
987
  async function handleExplain(args) {
867
988
  const flowRef = args[0];
868
989
  if (!flowRef || !flowRef.startsWith('flow://')) {
@@ -1127,6 +1248,14 @@ async function handleDev(args) {
1127
1248
  port: authorityPort,
1128
1249
  proposalContext: () => loadLocalDevelopmentContract(projectDir),
1129
1250
  intentObservations: () => collectRepositoryEvidence(projectDir),
1251
+ runtimeContext: {
1252
+ namespace: runtimeNamespace || config.namespace,
1253
+ runtime: config.runtime,
1254
+ storage: config.storage,
1255
+ distributed: Boolean(config.distributed),
1256
+ applicationLifecycle,
1257
+ applicationUrl: appUrl,
1258
+ },
1130
1259
  });
1131
1260
  process.env.VITE_FELTDB_URL = localAuthority.endpoint;
1132
1261
  const connectionsPath = path.join(projectDir, '.feltdb', 'connections.json');
@@ -1277,20 +1406,7 @@ async function handleDev(args) {
1277
1406
  }
1278
1407
  const open = !args.includes('--no-open');
1279
1408
  try {
1280
- const studio = handleStudio([
1281
- '--port', studioPort,
1282
- '--namespace', developmentSession.namespace,
1283
- '--runtime', developmentSession.runtime,
1284
- '--workspace-id', developmentSession.workspaceId,
1285
- '--session-id', developmentSession.sessionId,
1286
- '--project-id', developmentSession.projectId,
1287
- '--authority-url', developmentSession.authorityUrl,
1288
- '--pairing-url', developmentSession.pairingUrl,
1289
- '--lifecycle', developmentSession.lifecycle,
1290
- ...(developmentSession.applicationUrl ? ['--app-url', developmentSession.applicationUrl] : []),
1291
- ...((developmentSession.runtime === 'self-hosted' || developmentSession.runtime === 'managed') ? ['--connect', developmentSession.authorityUrl] : []),
1292
- '--no-open',
1293
- ], studioUrl => {
1409
+ const studio = handleStudio(developmentStudioArguments(developmentSession, studioPort), studioUrl => {
1294
1410
  developmentSession.studioUrl = studioUrl;
1295
1411
  transitionDevelopmentSession(developmentSession, 'READY');
1296
1412
  console.log(`\n${developmentSessionSummary(developmentSession)}\n`);
@@ -1640,13 +1756,15 @@ async function handleStudio(args, onReady) {
1640
1756
  else {
1641
1757
  console.log(`Studio Server: http://localhost:${port}\n`);
1642
1758
  }
1643
- const require = createRequire(import.meta.url);
1644
- const packageFile = require.resolve('@feltdb/core/package.json');
1645
- const root = path.join(path.dirname(packageFile), 'dist', 'studio-app');
1646
- if (!fs.existsSync(path.join(root, 'index.html'))) {
1647
- console.log('⚠️ Studio application artifacts not found.');
1759
+ let studioArtifact;
1760
+ try {
1761
+ studioArtifact = resolveCanonicalStudioArtifact();
1762
+ }
1763
+ catch (error) {
1764
+ console.log(`⚠️ ${error instanceof Error ? error.message : String(error)}`);
1648
1765
  return;
1649
1766
  }
1767
+ const root = studioArtifact.root;
1650
1768
  const canonicalFlow = path.resolve(process.cwd(), 'feltdb.flow');
1651
1769
  const projectFlow = fs.existsSync(canonicalFlow) ? canonicalFlow : undefined;
1652
1770
  const studioMutationToken = randomBytes(24).toString('hex');
@@ -2019,6 +2137,7 @@ Commands:
2019
2137
  diff [file] Diff FlowSpec against the last deployed model
2020
2138
  deploy [file] Validate, version, and deploy FlowSpec
2021
2139
  publish [file] Build and publish/update a managed application
2140
+ init-managed Set up managed FeltDB for an existing project
2022
2141
  ai Open the canonical interactive local-AI workflow
2023
2142
  ai --dry-run Open an ephemeral preview-only AI session
2024
2143
  ai model show Show the selected local or user-provided model
@@ -2068,7 +2187,15 @@ API Key Commands:
2068
2187
  feltdb keys revoke <key-id>
2069
2188
 
2070
2189
  Connection:
2071
- feltdb connect <url>
2190
+ feltdb connect <url> Connect to a running FeltDB server
2191
+
2192
+ Managed FeltDB Setup (Existing Projects):
2193
+ feltdb init-managed Set up managed FeltDB for an existing project
2194
+ Prompts for email and password, provisions resources,
2195
+ and optionally publishes the initial revision
2196
+ feltdb init-managed <email> <password> Non-interactive setup with provided credentials
2197
+
2198
+ Development:
2072
2199
  dev Start development server
2073
2200
  build Build FeltDB application
2074
2201
  check Validate configuration
@@ -2102,6 +2229,8 @@ Examples:
2102
2229
  feltdb studio --connect http://localhost:7700
2103
2230
  feltdb server --port 7700 --data ./data
2104
2231
  feltdb keys create --name development
2232
+ feltdb init-managed (interactive - prompts for email/password)
2233
+ feltdb init-managed user@example.com secret123
2105
2234
  feltdb connect http://localhost:7700
2106
2235
  feltdb status
2107
2236
  feltdb dev
@@ -41,6 +41,22 @@ export function developmentSessionEnvironment(session) {
41
41
  VITE_FELTDB_NAMESPACE: session.namespace,
42
42
  };
43
43
  }
44
+ export function developmentStudioArguments(session, port) {
45
+ return [
46
+ '--port', port,
47
+ '--namespace', session.namespace,
48
+ '--runtime', session.runtime,
49
+ '--workspace-id', session.workspaceId,
50
+ '--session-id', session.sessionId,
51
+ '--project-id', session.projectId,
52
+ '--authority-url', session.authorityUrl,
53
+ '--pairing-url', session.pairingUrl,
54
+ '--lifecycle', session.lifecycle,
55
+ ...(session.applicationUrl ? ['--app-url', session.applicationUrl] : []),
56
+ '--connect', session.authorityUrl,
57
+ '--no-open',
58
+ ];
59
+ }
44
60
  export function developmentSessionSummary(session) {
45
61
  const rows = [
46
62
  ['Session', session.sessionId], ['Workspace', session.workspaceId], ['Project', session.projectId], ['Namespace', session.namespace],
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.8.0';
26
+ const VERSION = '0.9.0';
27
27
  function prompt(question) {
28
28
  const rl = readline.createInterface({
29
29
  input: process.stdin,
@@ -0,0 +1,33 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ export const CANONICAL_STUDIO_ARTIFACT_CONTRACT = 'feltdb.studio.app.v1';
5
+ // Kept non-contiguous so Core assembly's package-import rewrite cannot mutate
6
+ // this artifact identity from its source package into an import subpath.
7
+ export const CANONICAL_STUDIO_ARTIFACT_SOURCE = '@feltdb/' + 'studio';
8
+ /** Resolve the one Studio application shipped by the installed Core package. */
9
+ export function resolveCanonicalStudioArtifact(options = {}) {
10
+ const require = createRequire(import.meta.url);
11
+ const resolvePackage = options.resolvePackage || (specifier => require.resolve(specifier));
12
+ const packageFile = resolvePackage('@feltdb/core/package.json');
13
+ const root = path.join(path.dirname(packageFile), 'dist', 'studio-app');
14
+ const index = path.join(root, 'index.html');
15
+ const identityFile = path.join(root, 'feltdb-studio.json');
16
+ if (!fs.existsSync(index) || !fs.existsSync(identityFile)) {
17
+ throw new Error(`The installed @feltdb/core package does not contain the canonical FeltDB Studio artifact (${root})`);
18
+ }
19
+ let identity;
20
+ try {
21
+ identity = JSON.parse(fs.readFileSync(identityFile, 'utf8'));
22
+ }
23
+ catch (error) {
24
+ throw new Error(`The canonical FeltDB Studio artifact identity is unreadable (${identityFile}): ${error instanceof Error ? error.message : String(error)}`);
25
+ }
26
+ if (identity.contract !== CANONICAL_STUDIO_ARTIFACT_CONTRACT
27
+ || identity.source !== CANONICAL_STUDIO_ARTIFACT_SOURCE
28
+ || typeof identity.version !== 'string' || !identity.version
29
+ || identity.entrypoint !== 'src/main.tsx') {
30
+ throw new Error(`The installed Studio artifact is not the canonical @feltdb/core/studio application (${identityFile})`);
31
+ }
32
+ return { root, index, identity };
33
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,QAAQ,EACd,MAAM,gBAAgB,CAAC;AAKxB,OAAO,KAAK,EAAE,WAAW,EAAc,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAErF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC;AAChD,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;AAiBjD;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,gGAAgG;IAChG,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC;IACnC,4EAA4E;IAC5E,eAAe,EAAE,OAAO,CAAC;IACzB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,mCAAmC;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,iEAAiE;IACjE,IAAI,CAAC,EAAE,CAAC,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC,+DAA+D;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,qBAAa,UAAU,CAAC,CAAC;IACvB,OAAO,CAAC,EAAE,CAAO;IACjB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,oBAAoB,CAA8B;IAC1D,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,kBAAkB,CAA6B;IACvD,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,aAAa,CAAS;IAC9B,mFAAmF;IACnF,OAAO,CAAC,eAAe,CAAK;IAC5B,gFAAgF;IAChF,OAAO,CAAC,iBAAiB,CAAM;IAC/B,OAAO,CAAC,QAAQ,CAAoC;IACpD,oEAAoE;IACpE,OAAO,CAAC,WAAW,CAA+B;IAClD,kFAAkF;IAClF,OAAO,CAAC,eAAe,CAAuB;IAC9C;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc,CAAyB;IAC/C,yEAAyE;IACzE,OAAO,CAAC,aAAa,CAA+B;gBAExC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,WAAW,UAAO;IAoBxG;;OAEG;YACW,oBAAoB;IAalC;;;OAGG;IACG,GAAG,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC;IAKzB,qEAAqE;IAC/D,IAAI,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC;IAI1B;;;;;;;;;;;;;OAaG;IACG,IAAI,CAAC,KAAK,GAAE,OAAO,CAAC,CAAC,CAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAoEhD,6EAA6E;IACvE,KAAK,CAAC,KAAK,GAAE,OAAO,CAAC,CAAC,CAAM,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAIjD;;;;;OAKG;IACH,aAAa,IAAI,mBAAmB,GAAG,IAAI;IAI3C,wEAAwE;IACxE,OAAO,CAAC,QAAQ;IAWhB;;;;;;OAMG;IACH,OAAO,CAAC,eAAe;IAiBvB;;;;;;OAMG;IACH,OAAO,CAAC,wBAAwB;IA0BhC;;;;;OAKG;IACH,WAAW,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAWtC;;OAEG;IACH,WAAW,IAAI,WAAW,EAAE;IAI5B;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAYrC;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAazC;;OAEG;IACG,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAYjD;;;OAGG;IACH,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;IAO7C;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BrE,qEAAqE;IAC/D,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIlE;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BrE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgCG;IACG,eAAe,CACnB,EAAE,EAAE,MAAM,GAAG,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,aAAa,CAAC,EAAE,MAAM,EACtB,eAAe,CAAC,EAAE,MAAM,EACxB,gBAAgB,CAAC,EAAE,OAAO,EAC1B,YAAY,CAAC,EAAE,CAAC,GACf,OAAO,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IA2EpC;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BhD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAK9B;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKnD;;;;;;OAMG;IACH,OAAO,CAAC,SAAS;IAKjB;;;;;;;;OAQG;YACW,6BAA6B;IAkC3C;;;;;OAKG;YACW,iCAAiC;IAyE/C;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,CAAC,CAAA;KAAE,CAAC;IAsClG;;;OAGG;IACH,SAAS,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,IAAI;IA6CxE,mEAAmE;IACnE,KAAK,IAAI,IAAI;IASb;;;;;;;;;;;OAWG;IACH,SAAS,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIzC;;;;;;OAMG;IACH,kBAAkB,IAAI,cAAc,GAAG,IAAI;IAI3C;;;;;;;;;OASG;YACW,eAAe;IAQ7B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CA2E/B;AAED;;GAEG;AACH,qBAAa,YAAY,CAAC,MAAM,EAAE,KAAK;IACrC,OAAO,CAAC,QAAQ,CAAO;IACvB,OAAO,CAAC,OAAO,CAAO;IACtB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,UAAU,CAAoC;gBAGpD,QAAQ,EAAE,IAAI,EACd,OAAO,EAAE,IAAI,EACb,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,MAAM;IAQ/C;;OAEG;IACG,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC;CAoBxD"}
1
+ {"version":3,"file":"collection.d.ts","sourceRoot":"","sources":["../src/collection.ts"],"names":[],"mappings":"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"}
@@ -484,10 +484,12 @@ export class Collection {
484
484
  conflictCode: casResult.conflictCode,
485
485
  };
486
486
  }
487
- // Use the item returned by cas() which has the correct incremented version
488
- const finalItem = (casResult.item || updated);
489
- // CAS operation on backend increments version atomically - update finalItem to match
490
- finalItem.__version = casResult.currentVersion;
487
+ // Use the item returned by cas() if available (file-db, memory-db), otherwise use local copy
488
+ // When cas() returns the item, it's the authoritative post-commit state from the backend.
489
+ // When it doesn't, we reconstruct from our local updated object and the returned version.
490
+ const finalItem = casResult.item
491
+ ? casResult.item
492
+ : { ...updated, __version: casResult.currentVersion };
491
493
  // Update indexes
492
494
  this.indexBackend.updateRecord(String(id), current, finalItem);
493
495
  // Emit change through reactive dependency graph
@@ -1,4 +1,4 @@
1
1
  // One release train keeps generated applications installable. The repository
2
2
  // validation script checks these values against every workspace manifest.
3
- export const FELTDB_PACKAGE_VERSION = '0.9.1';
3
+ export const FELTDB_PACKAGE_VERSION = '0.11.0';
4
4
  export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
@@ -1460,9 +1460,12 @@ mod convergence_acceptance_tests {
1460
1460
  use crate::FeltDb;
1461
1461
  use serde::{Deserialize, Serialize};
1462
1462
  use std::path::PathBuf;
1463
+ use std::sync::atomic::{AtomicU64, Ordering};
1463
1464
  use std::time::SystemTime;
1464
1465
  use std::time::UNIX_EPOCH;
1465
1466
 
1467
+ static TEMP_FILE_SEQUENCE: AtomicU64 = AtomicU64::new(0);
1468
+
1466
1469
  #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1467
1470
  struct Task {
1468
1471
  title: String,
@@ -1475,7 +1478,11 @@ mod convergence_acceptance_tests {
1475
1478
  .duration_since(UNIX_EPOCH)
1476
1479
  .expect("time")
1477
1480
  .as_nanos();
1478
- std::env::temp_dir().join(format!("feltdb-convergence-test-{nanos}.data"))
1481
+ let sequence = TEMP_FILE_SEQUENCE.fetch_add(1, Ordering::Relaxed);
1482
+ std::env::temp_dir().join(format!(
1483
+ "feltdb-convergence-test-{}-{nanos}-{sequence}.data",
1484
+ std::process::id()
1485
+ ))
1479
1486
  }
1480
1487
 
1481
1488
  /// Test convergence: Two instances make concurrent mutations on same key
@@ -1203,6 +1203,22 @@ impl ApplicationStore {
1203
1203
  records: Arc::new(RwLock::new(records)),
1204
1204
  })
1205
1205
  }
1206
+
1207
+ /// Return every tenant scope asserted by canonical application definition
1208
+ /// records for one stable application identity. Callers must reject zero
1209
+ /// or multiple distinct tenants; ordering is never ownership evidence.
1210
+ pub fn tenant_ownership_candidates(&self, application_id: &str) -> Vec<String> {
1211
+ let Ok(records) = self.records.read() else {
1212
+ return Vec::new();
1213
+ };
1214
+ records
1215
+ .revisions
1216
+ .iter()
1217
+ .map(|value| (&value.application_id, &value.tenant_id))
1218
+ .filter(|(candidate, _)| candidate.as_str() == application_id)
1219
+ .map(|(_, tenant)| tenant.clone())
1220
+ .collect()
1221
+ }
1206
1222
  fn persist(&self, r: &RevisionRecords) -> Result<(), String> {
1207
1223
  if let Some(parent) = self.path.parent() {
1208
1224
  fs::create_dir_all(parent).map_err(|e| e.to_string())?;
@@ -744,11 +744,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
744
744
  link.peer.clone(),
745
745
  envelope.envelope_id.sequence,
746
746
  );
747
- if link.io.send(message.clone()).await.is_ok() {
748
- delivered.push(link.peer.clone());
749
- } else if connect_with_retry(link, 3).await
750
- && link.io.send(message).await.is_ok()
751
- {
747
+ // Forwarding, like re-sending, is an explicit
748
+ // recovery operation. Prefer a fresh connection so
749
+ // a peer restart cannot leave this write accepted
750
+ // by a stale local socket and delivered nowhere.
751
+ let _ = connect_with_retry(link, 3).await;
752
+ if link.io.send(message).await.is_ok() {
752
753
  delivered.push(link.peer.clone());
753
754
  }
754
755
  }
@@ -793,19 +794,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
793
794
  link.peer.clone(),
794
795
  envelope.envelope_id.sequence,
795
796
  );
796
- // The peer this is aimed at may have restarted
797
- // since the link was established, which is the
798
- // whole point of re-sending. Reconnect and retry,
799
- // as `execute-tx` does.
800
- match link.io.send(message.clone()).await {
801
- Ok(()) => delivered.push(link.peer.clone()),
802
- Err(_) => {
803
- if connect_with_retry(link, 3).await
804
- && link.io.send(message).await.is_ok()
805
- {
806
- delivered.push(link.peer.clone());
807
- }
808
- }
797
+ // Re-sending is the recovery primitive used after a
798
+ // peer restart. A write to the old TCP socket can
799
+ // still return success after the peer has exited,
800
+ // because that only confirms acceptance by the
801
+ // local kernel. Try to establish a fresh connection
802
+ // first. If the peer never restarted, the connect
803
+ // attempt leaves the established stream untouched.
804
+ let _ = connect_with_retry(link, 3).await;
805
+ if link.io.send(message).await.is_ok() {
806
+ delivered.push(link.peer.clone());
809
807
  }
810
808
  }
811
809
  println!(
@@ -961,12 +959,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
961
959
 
962
960
  let mut delivered = Vec::new();
963
961
  for link in &send_links {
962
+ // Acknowledgements are durable recovery evidence and are
963
+ // commonly rebroadcast after a peer restart. Refresh the
964
+ // connection first for the same reason as explicit
965
+ // envelope replay.
966
+ let _ = connect_with_retry(link, 3).await;
964
967
  if link.io.send_acknowledgement(ack.clone()).await.is_ok() {
965
968
  delivered.push(link.peer.clone());
966
- } else if connect_with_retry(link, 3).await
967
- && link.io.send_acknowledgement(ack.clone()).await.is_ok()
968
- {
969
- delivered.push(link.peer.clone());
970
969
  }
971
970
  }
972
971
  println!(