@feltdb/core 0.9.1 → 0.10.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 (99) hide show
  1. package/dist/cli/commands.js +19 -21
  2. package/dist/cli/development-session.js +16 -0
  3. package/dist/cli/studio-artifact.js +33 -0
  4. package/dist/create/package-versions.js +1 -1
  5. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +8 -1
  6. package/dist/create/server-source/crates/feltdb/src/application.rs +16 -0
  7. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +21 -22
  8. package/dist/create/server-source/crates/feltdb/src/lib.rs +451 -33
  9. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +48 -3
  10. package/dist/create/server-source/crates/feltdb/src/state_model.rs +569 -4
  11. package/dist/create/server-source/crates/feltdb/src/state_trigger.rs +1 -0
  12. package/dist/create/server-source/crates/feltdb/src/versioned_object_substrate.rs +708 -0
  13. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +1 -0
  14. package/dist/create/server-source/crates/feltdb/src/workload.rs +480 -44
  15. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_evidence.rs +123 -0
  16. package/dist/create/server-source/crates/feltdb/tests/generalized_version_control_substrate_completion.rs +281 -0
  17. package/dist/create/server-source/crates/feltdb/tests/production_readiness_contract.rs +15 -3
  18. package/dist/create/server-source/crates/feltdb/tests/revision_retention_boundary_evidence.rs +28 -18
  19. package/dist/create/server-source/crates/feltdb/tests/storage_independent_versioned_object_substrate.rs +328 -0
  20. package/dist/create/server-source/crates/feltdb/tests/workload_envelope_contract.rs +20 -3
  21. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +14 -4
  22. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +0 -4
  23. package/dist/create/server-source/crates/feltdb-server/src/canonical_principals.rs +1093 -0
  24. package/dist/create/server-source/crates/feltdb-server/src/causal.rs +905 -144
  25. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +9 -6
  26. package/dist/create/server-source/crates/feltdb-server/src/control_plane.rs +90 -0
  27. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +1 -0
  28. package/dist/create/server-source/crates/feltdb-server/src/main.rs +408 -74
  29. package/dist/create/server-source/crates/feltdb-server/src/principals.rs +9 -501
  30. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +22 -0
  31. package/dist/create/server-source/crates/feltdb-server/tests/principal_tenant_migration.rs +247 -0
  32. package/dist/db.d.ts +6 -5
  33. package/dist/db.d.ts.map +1 -1
  34. package/dist/db.js +18 -24
  35. package/dist/deployment.d.ts.map +1 -1
  36. package/dist/deployment.js +4 -2
  37. package/dist/embedded-transaction.d.ts.map +1 -1
  38. package/dist/embedded-transaction.js +4 -0
  39. package/dist/error-codes.d.ts +12 -0
  40. package/dist/error-codes.d.ts.map +1 -1
  41. package/dist/error-codes.js +20 -2
  42. package/dist/feltdb.d.ts +4 -0
  43. package/dist/feltdb.d.ts.map +1 -1
  44. package/dist/file-db.d.ts +10 -0
  45. package/dist/file-db.d.ts.map +1 -1
  46. package/dist/file-db.js +18 -0
  47. package/dist/http-db.d.ts +4 -0
  48. package/dist/http-db.d.ts.map +1 -1
  49. package/dist/http-db.js +73 -12
  50. package/dist/index-core.d.ts +1 -0
  51. package/dist/index-core.d.ts.map +1 -1
  52. package/dist/index-core.js +1 -0
  53. package/dist/public-contract.d.ts +112 -0
  54. package/dist/public-contract.d.ts.map +1 -0
  55. package/dist/public-contract.js +125 -0
  56. package/dist/studio/app.d.ts +23 -0
  57. package/dist/studio/app.d.ts.map +1 -1
  58. package/dist/studio/components/ContractInspector.d.ts +5 -0
  59. package/dist/studio/components/ContractInspector.d.ts.map +1 -1
  60. package/dist/studio/components/SettingsPanel.d.ts +4 -1
  61. package/dist/studio/components/SettingsPanel.d.ts.map +1 -1
  62. package/dist/studio/components/index.js +4 -4
  63. package/dist/studio/{components-CehkcX_k.js → components-CJFgdKG6.js} +322 -303
  64. package/dist/studio/control-plane/Data.d.ts.map +1 -1
  65. package/dist/studio/control-plane/Instance.d.ts +10 -0
  66. package/dist/studio/control-plane/Instance.d.ts.map +1 -1
  67. package/dist/studio/control-plane/Operations.d.ts +3 -0
  68. package/dist/studio/control-plane/Operations.d.ts.map +1 -1
  69. package/dist/studio/control-plane/State.d.ts.map +1 -1
  70. package/dist/studio/control-plane/index.d.ts +4 -2
  71. package/dist/studio/control-plane/index.d.ts.map +1 -1
  72. package/dist/studio/control-plane/index.js +2 -2
  73. package/dist/studio/control-plane-DP8ljTDO.js +3207 -0
  74. package/dist/studio/index.js +96 -75
  75. package/dist/studio/utils/api.d.ts +1 -1
  76. package/dist/studio/utils/api.d.ts.map +1 -1
  77. package/dist/studio/utils/control-plane-api.d.ts +12 -1
  78. package/dist/studio/utils/control-plane-api.d.ts.map +1 -1
  79. package/dist/studio/utils/control-plane-api.js +1 -1
  80. package/dist/studio/utils/index.js +16 -16
  81. package/dist/studio/utils/service-api.d.ts +8 -0
  82. package/dist/studio/utils/service-api.d.ts.map +1 -1
  83. package/dist/studio/utils/service-api.js +15 -12
  84. package/dist/studio-app/assets/{feltdb_wasm-DksXDnLa.js → feltdb_wasm-CfNb3IoF.js} +1 -1
  85. package/dist/studio-app/assets/feltdb_wasm_bg-D9d3gY-E.wasm +0 -0
  86. package/dist/studio-app/assets/index-SDXdROi1.js +30 -0
  87. package/dist/studio-app/feltdb-studio.json +6 -0
  88. package/dist/studio-app/index.html +1 -1
  89. package/dist/transaction.d.ts +9 -1
  90. package/dist/transaction.d.ts.map +1 -1
  91. package/dist/transaction.js +6 -2
  92. package/dist/wasm/feltdb_wasm_bg.wasm +0 -0
  93. package/dist/workspace/local-development-authority.d.ts +8 -0
  94. package/dist/workspace/local-development-authority.d.ts.map +1 -1
  95. package/dist/workspace/local-development-authority.js +136 -1
  96. package/package.json +2 -2
  97. package/dist/studio/control-plane-D6TT2fCQ.js +0 -3064
  98. package/dist/studio-app/assets/feltdb_wasm_bg-Yy4QRHJT.wasm +0 -0
  99. 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,7 @@ 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';
26
27
  function loadProjectEnvironment(file = path.resolve('.env.local')) {
27
28
  if (!fs.existsSync(file))
28
29
  return;
@@ -1127,6 +1128,14 @@ async function handleDev(args) {
1127
1128
  port: authorityPort,
1128
1129
  proposalContext: () => loadLocalDevelopmentContract(projectDir),
1129
1130
  intentObservations: () => collectRepositoryEvidence(projectDir),
1131
+ runtimeContext: {
1132
+ namespace: runtimeNamespace || config.namespace,
1133
+ runtime: config.runtime,
1134
+ storage: config.storage,
1135
+ distributed: Boolean(config.distributed),
1136
+ applicationLifecycle,
1137
+ applicationUrl: appUrl,
1138
+ },
1130
1139
  });
1131
1140
  process.env.VITE_FELTDB_URL = localAuthority.endpoint;
1132
1141
  const connectionsPath = path.join(projectDir, '.feltdb', 'connections.json');
@@ -1277,20 +1286,7 @@ async function handleDev(args) {
1277
1286
  }
1278
1287
  const open = !args.includes('--no-open');
1279
1288
  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 => {
1289
+ const studio = handleStudio(developmentStudioArguments(developmentSession, studioPort), studioUrl => {
1294
1290
  developmentSession.studioUrl = studioUrl;
1295
1291
  transitionDevelopmentSession(developmentSession, 'READY');
1296
1292
  console.log(`\n${developmentSessionSummary(developmentSession)}\n`);
@@ -1640,13 +1636,15 @@ async function handleStudio(args, onReady) {
1640
1636
  else {
1641
1637
  console.log(`Studio Server: http://localhost:${port}\n`);
1642
1638
  }
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.');
1639
+ let studioArtifact;
1640
+ try {
1641
+ studioArtifact = resolveCanonicalStudioArtifact();
1642
+ }
1643
+ catch (error) {
1644
+ console.log(`⚠️ ${error instanceof Error ? error.message : String(error)}`);
1648
1645
  return;
1649
1646
  }
1647
+ const root = studioArtifact.root;
1650
1648
  const canonicalFlow = path.resolve(process.cwd(), 'feltdb.flow');
1651
1649
  const projectFlow = fs.existsSync(canonicalFlow) ? canonicalFlow : undefined;
1652
1650
  const studioMutationToken = randomBytes(24).toString('hex');
@@ -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],
@@ -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,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.10.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!(