create-feltdb 0.7.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist/cli.js +10 -1
  2. package/dist/create.js +10 -12
  3. package/dist/managed-account.js +71 -7
  4. package/dist/package-versions.js +1 -1
  5. package/dist/server-source/crates/feltdb/src/application.rs +10 -0
  6. package/dist/server-source/crates/feltdb/src/lib.rs +20 -2
  7. package/dist/server-source/crates/feltdb-server/src/app_state.rs +12 -24
  8. package/dist/server-source/crates/feltdb-server/src/application_contract.rs +11 -3
  9. package/dist/server-source/crates/feltdb-server/src/authenticated_principal.rs +0 -1
  10. package/dist/server-source/crates/feltdb-server/src/certification_harness.rs +23 -22
  11. package/dist/server-source/crates/feltdb-server/src/delegation_token.rs +5 -15
  12. package/dist/server-source/crates/feltdb-server/src/durable_operations.rs +13 -28
  13. package/dist/server-source/crates/feltdb-server/src/identity.rs +79 -14
  14. package/dist/server-source/crates/feltdb-server/src/key_management.rs +28 -7
  15. package/dist/server-source/crates/feltdb-server/src/lib.rs +5 -5
  16. package/dist/server-source/crates/feltdb-server/src/main.rs +1777 -259
  17. package/dist/server-source/crates/feltdb-server/src/managed_diagnostics.rs +10 -30
  18. package/dist/server-source/crates/feltdb-server/src/membership_policy.rs +12 -4
  19. package/dist/server-source/crates/feltdb-server/src/request_telemetry.rs +8 -6
  20. package/dist/server-source/crates/feltdb-server/src/snapshot_cursor.rs +10 -14
  21. package/dist/server-source/crates/feltdb-server/src/tenancy.rs +183 -21
  22. package/dist/server-source/crates/feltdb-server/src/tenant_policies.rs +9 -21
  23. package/dist/server-source/crates/feltdb-server/src/transaction_idempotency.rs +5 -3
  24. package/dist/server-source/crates/feltdb-server/src/transaction_recovery.rs +9 -8
  25. package/dist/server-source/crates/feltdb-server/tests/revision_recovery_integration_test.rs +1 -4
  26. package/dist/template/default-project/feltdb.flow +5 -0
  27. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ import readline from 'readline';
10
10
  import { spawn, spawnSync } from 'child_process';
11
11
  import { createProject } from './create.js';
12
12
  import { FELTDB_PACKAGE_VERSION } from './package-versions.js';
13
- import { configureManagedAccount } from './managed-account.js';
13
+ import { configureManagedAccount, finalizeManagedProvisioning } from './managed-account.js';
14
14
  import { localFeltdbExecutable } from './development-handoff.js';
15
15
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
16
  function parseArgs(args) {
@@ -331,9 +331,18 @@ Learn more: https://github.com/rkendel1/feltdb`);
331
331
  }
332
332
  console.log('\nāœ… FeltDB application created successfully!\n');
333
333
  const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
334
+ if (options.runtime === 'managed' && !shouldInstall) {
335
+ throw new Error('Managed provisioning requires dependency installation so the initial revision can be built, published, promoted, and verified. Remove --no-install.');
336
+ }
334
337
  if (shouldInstall) {
335
338
  console.log(`šŸ“¦ Installing application, Studio${options.webllm ? ', and local WebLLM app builder' : ''} dependencies...\n`);
336
339
  await run(npm, ['install'], projectDir);
340
+ if (options.runtime === 'managed') {
341
+ console.log('\nā˜ļø Publishing and verifying the initial managed revision...\n');
342
+ await run(localFeltdbExecutable(projectDir), ['publish', '--yes', '--approve-destructive-changes'], projectDir);
343
+ const finalized = await finalizeManagedProvisioning({ projectDir });
344
+ console.log(`āœ“ Managed runtime verified at revision ${finalized.revisionId}`);
345
+ }
337
346
  }
338
347
  if (shouldStart) {
339
348
  if (!shouldInstall) {
package/dist/create.js CHANGED
@@ -198,7 +198,7 @@ ${hasAgents ? ` agent WorkspaceAssistant {
198
198
  const runtimeOptions = runtime === 'browser'
199
199
  ? "{ namespace: import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', browser: true }"
200
200
  : runtime === 'managed'
201
- ? "{ namespace: import.meta.env.VITE_FELTDB_MANAGED_NAMESPACE || import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_MANAGED_URL || import.meta.env.VITE_FELTDB_URL || '', token: import.meta.env.VITE_FELTDB_MANAGED_API_KEY || import.meta.env.VITE_FELTDB_API_KEY || '', applicationId: import.meta.env.VITE_FELTDB_MANAGED_APPLICATION_ID, environment: import.meta.env.VITE_FELTDB_MANAGED_ENVIRONMENT || 'production' } }"
201
+ ? "{ namespace: '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_MANAGED_URL || '', applicationId: import.meta.env.VITE_FELTDB_MANAGED_APPLICATION_ID, environment: 'production' } }"
202
202
  : runtime === 'self-hosted'
203
203
  ? "{ namespace: import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', server: { url: import.meta.env.VITE_FELTDB_URL || 'http://localhost:7700', token: import.meta.env.VITE_FELTDB_API_KEY || '' } }"
204
204
  : "{ namespace: import.meta.env.VITE_FELTDB_NAMESPACE || '" + applicationName + "', memory: true }";
@@ -1234,8 +1234,7 @@ main().catch(console.error);
1234
1234
  const envExample = `# FeltDB Configuration
1235
1235
  # Copy this file to .env.local and update the values
1236
1236
 
1237
- # API Key for authenticating with FeltDB servers
1238
- # Leave empty for browser runtime; required for authenticated self-hosted and managed runtimes
1237
+ # API key for authenticated self-hosted browser access
1239
1238
  VITE_FELTDB_API_KEY=
1240
1239
 
1241
1240
  # FeltDB Server URL (self-hosted or managed runtime)
@@ -1243,13 +1242,10 @@ VITE_FELTDB_URL=http://localhost:7700
1243
1242
 
1244
1243
  # Managed example: https://runtime.your-app.feltdb.com
1245
1244
  VITE_FELTDB_MANAGED_URL=
1246
- VITE_FELTDB_MANAGED_API_KEY=
1245
+ VITE_FELTDB_MANAGED_APPLICATION_ID=
1247
1246
  # Server/control-plane only. Never rename with a VITE_ prefix.
1248
1247
  FELTDB_MANAGED_CONTROL_API_KEY=
1249
- VITE_FELTDB_MANAGED_TENANT_ID=
1250
- VITE_FELTDB_MANAGED_APPLICATION_ID=
1251
- VITE_FELTDB_MANAGED_NAMESPACE=
1252
- VITE_FELTDB_MANAGED_ENVIRONMENT=production
1248
+ FELTDB_TOKEN=
1253
1249
 
1254
1250
  # Override the default self-hosted container image
1255
1251
  # By default the server image is built locally from the bundled source.
@@ -1388,7 +1384,7 @@ The self-hosted instance runs on \`http://localhost:7700\` by default.
1388
1384
  }
1389
1385
  \`\`\`
1390
1386
 
1391
- Managed mode uses the same application API with a FeltDB-hosted endpoint. The CLI creates \`.env.local\` with \`VITE_FELTDB_MANAGED_URL\` and \`VITE_FELTDB_MANAGED_API_KEY\` after account setup. Studio uses that same connection for state, health, operations, and API-key administration.
1387
+ Managed mode uses the same application API with a FeltDB-hosted endpoint. The CLI writes only the public URL and application ID plus separate server-only publish and runtime credentials. Tenant, namespace, environment, and active revision are discovered deployment metadata under \`.feltdb/managed.json\`.
1392
1388
 
1393
1389
  ## Vector Search Status
1394
1390
 
@@ -1657,7 +1653,7 @@ FeltDB runs through a dedicated server with Docker Compose and persistent data v
1657
1653
 
1658
1654
  ### Managed Runtime
1659
1655
 
1660
- FeltDB provides the runtime endpoint, durable storage, synchronization, and background workload infrastructure. The CLI configures \`VITE_FELTDB_MANAGED_URL\` and \`VITE_FELTDB_MANAGED_API_KEY\` in \`.env.local\`.
1656
+ FeltDB provides the runtime endpoint, durable storage, synchronization, and background workload infrastructure. The CLI configures the public \`VITE_FELTDB_MANAGED_*\` identity, separate server-only control and data-plane credentials, and the promoted revision in \`.env.local\`. The browser API-key value stays empty because browser users receive short-lived actor sessions.
1661
1657
 
1662
1658
  ## Configuration
1663
1659
 
@@ -1687,12 +1683,14 @@ Create a \`.env.local\` file (copy from \`.env.example\`):
1687
1683
  \`\`\`
1688
1684
  VITE_FELTDB_API_KEY=your_api_key_here
1689
1685
  VITE_FELTDB_URL=http://localhost:7700
1690
- VITE_FELTDB_MANAGED_API_KEY=your_managed_api_key_here
1691
1686
  VITE_FELTDB_MANAGED_URL=https://api.feltdb.com
1687
+ VITE_FELTDB_MANAGED_APPLICATION_ID=your_application_id
1688
+ FELTDB_MANAGED_CONTROL_API_KEY=your_server_only_control_key
1689
+ FELTDB_TOKEN=your_server_only_application_data_key
1692
1690
  VITE_FELTDB_WEBSOCKET_URL=ws://localhost:7700
1693
1691
  \`\`\`
1694
1692
 
1695
- These are required when connecting to an authenticated self-hosted or managed FeltDB instance.
1693
+ Never expose \`FELTDB_MANAGED_CONTROL_API_KEY\` or \`FELTDB_TOKEN\` through a \`VITE_\` variable. Managed browser access uses \`db.auth.signUp()\` and \`db.auth.signIn()\`; the active revision is discovered rather than configured.
1696
1694
 
1697
1695
  ## Development
1698
1696
 
@@ -1,5 +1,20 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ function loadEnvironment(file) {
4
+ const values = {};
5
+ for (const raw of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
6
+ const line = raw.trim();
7
+ if (!line || line.startsWith('#'))
8
+ continue;
9
+ const separator = line.indexOf('=');
10
+ if (separator > 0)
11
+ values[line.slice(0, separator)] = line.slice(separator + 1);
12
+ }
13
+ return values;
14
+ }
15
+ function managedStateFile(projectDir) {
16
+ return path.join(projectDir, '.feltdb', 'managed.json');
17
+ }
3
18
  async function responseJson(response, fallback) {
4
19
  const body = await response.json().catch(() => ({}));
5
20
  if (!response.ok)
@@ -47,7 +62,8 @@ export async function configureManagedAccount(options) {
47
62
  method: 'POST', headers,
48
63
  body: JSON.stringify({
49
64
  id: `${options.applicationName}-control`,
50
- namespace: options.namespace,
65
+ namespace: application.namespace || options.namespace,
66
+ application_id: application.id,
51
67
  scopes: [
52
68
  'application:read', 'application:write', 'application:revision:create',
53
69
  'application:revision:read', 'application:revision:promote',
@@ -57,19 +73,67 @@ export async function configureManagedAccount(options) {
57
73
  }), 'Could not create the managed control-plane key');
58
74
  if (!controlKey.secret)
59
75
  throw new Error('Managed key service did not return the control-plane credential');
76
+ const dataKey = await responseJson(await request(`${sessionOrigin}/api/keys`, {
77
+ method: 'POST', headers,
78
+ body: JSON.stringify({
79
+ id: `${options.applicationName}-runtime`,
80
+ namespace: application.namespace || options.namespace,
81
+ application_id: application.id,
82
+ scopes: [
83
+ 'application:read', 'application:revision:read',
84
+ 'state:read', 'state:write', 'events:read',
85
+ 'workflows:run', 'agents:run', 'capabilities:run',
86
+ ],
87
+ }),
88
+ }), 'Could not create the managed application data key');
89
+ if (!dataKey.secret)
90
+ throw new Error('Managed key service did not return the application data credential');
91
+ try {
92
+ await responseJson(await request(`${apiUrl}/api/applications/${encodeURIComponent(application.id)}/revisions`, {
93
+ headers: { Authorization: `Bearer ${controlKey.secret}` },
94
+ }), 'Managed control-plane credential was created but is not accepted by the managed API');
95
+ }
96
+ catch (error) {
97
+ await Promise.allSettled([controlKey.id, dataKey.id].filter(Boolean).map(id => request(`${sessionOrigin}/api/keys/${encodeURIComponent(id)}`, { method: 'DELETE', headers })));
98
+ throw error;
99
+ }
60
100
  const environment = [
61
101
  '# Generated by create-feltdb managed setup. Do not commit this file.',
62
102
  `VITE_FELTDB_MANAGED_URL=${apiUrl}`,
63
- '# Browser authentication uses short-lived actor sessions; no long-lived data key is embedded.',
64
- 'VITE_FELTDB_MANAGED_API_KEY=',
103
+ `VITE_FELTDB_MANAGED_APPLICATION_ID=${application.id}`,
65
104
  '# Control-plane credential. Never prefix this with VITE_ or expose it to browser code.',
66
105
  `FELTDB_MANAGED_CONTROL_API_KEY=${controlKey.secret}`,
67
- `VITE_FELTDB_MANAGED_TENANT_ID=${tenant.id}`,
68
- `VITE_FELTDB_MANAGED_APPLICATION_ID=${application.id}`,
69
- `VITE_FELTDB_MANAGED_NAMESPACE=${application.namespace || options.namespace}`,
70
- 'VITE_FELTDB_MANAGED_ENVIRONMENT=production',
106
+ '# Server/deployment data-plane credential. Never prefix this with VITE_.',
107
+ `FELTDB_TOKEN=${dataKey.secret}`,
71
108
  '',
72
109
  ].join('\n');
73
110
  fs.writeFileSync(path.join(options.projectDir, '.env.local'), environment, { mode: 0o600 });
111
+ fs.mkdirSync(path.dirname(managedStateFile(options.projectDir)), { recursive: true });
112
+ fs.writeFileSync(managedStateFile(options.projectDir), `${JSON.stringify({
113
+ url: apiUrl,
114
+ tenantId: tenant.id,
115
+ applicationId: application.id,
116
+ namespace: application.namespace || options.namespace,
117
+ environment: 'production',
118
+ }, null, 2)}\n`, { mode: 0o600 });
74
119
  return { tenant, application, apiUrl };
75
120
  }
121
+ export async function finalizeManagedProvisioning(options) {
122
+ const publishedFile = path.join(options.projectDir, '.feltdb', 'last-published.json');
123
+ if (!fs.existsSync(publishedFile))
124
+ throw new Error('Managed provisioning did not produce .feltdb/last-published.json');
125
+ const published = JSON.parse(fs.readFileSync(publishedFile, 'utf8'));
126
+ if (!published.revisionId)
127
+ throw new Error('Managed provisioning did not promote an application revision');
128
+ const environmentFile = path.join(options.projectDir, '.env.local');
129
+ const environment = loadEnvironment(environmentFile);
130
+ const managedFile = managedStateFile(options.projectDir);
131
+ const managed = JSON.parse(fs.readFileSync(managedFile, 'utf8'));
132
+ const request = options.fetchImpl || fetch;
133
+ const discovery = await responseJson(await request(`${managed.url}/v1/application?application_id=${encodeURIComponent(managed.applicationId)}&environment=${encodeURIComponent(managed.environment)}`, { headers: { Authorization: `Bearer ${environment.FELTDB_TOKEN}` } }), 'Managed application data credential is not accepted by the promoted runtime');
134
+ if (discovery.revision_id !== published.revisionId) {
135
+ throw new Error('Managed runtime revision does not match the revision promoted during provisioning');
136
+ }
137
+ fs.writeFileSync(managedFile, `${JSON.stringify({ ...managed, revisionId: published.revisionId }, null, 2)}\n`, { mode: 0o600 });
138
+ return { revisionId: published.revisionId };
139
+ }
@@ -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.7.4';
3
+ export const FELTDB_PACKAGE_VERSION = '0.8.0';
4
4
  export const feltdbPackageRange = `^${FELTDB_PACKAGE_VERSION}`;
@@ -272,6 +272,9 @@ pub struct ApplicationManifest {
272
272
  pub agents: Vec<AgentDefinition>,
273
273
  #[serde(default)]
274
274
  pub capabilities: Vec<CapabilityBinding>,
275
+ /// Resolved, provider-neutral module semantic contracts. Secret values are forbidden.
276
+ #[serde(default)]
277
+ pub modules: Vec<serde_json::Value>,
275
278
  #[serde(default)]
276
279
  pub connections: Vec<ConnectionBinding>,
277
280
  #[serde(default)]
@@ -313,6 +316,7 @@ impl ApplicationManifest {
313
316
  schedules: vec![],
314
317
  agents: vec![],
315
318
  capabilities: vec![],
319
+ modules: vec![],
316
320
  connections: vec![],
317
321
  policies: vec![],
318
322
  ui: UiApplicationModel::default(),
@@ -733,6 +737,11 @@ pub fn validate_manifest(
733
737
  }
734
738
  }
735
739
  }
740
+ for (index, module) in manifest.modules.iter().enumerate() {
741
+ if contains_plaintext_secret(module) || module.get("value").is_some() {
742
+ issues.push(issue("security", format!("modules.{index}"), "module contracts may declare secret requirements but never secret values"));
743
+ }
744
+ }
736
745
  for environment in &manifest.environments {
737
746
  if environment.configuration.iter().any(|(key, value)| {
738
747
  matches!(
@@ -868,6 +877,7 @@ fn sorted(mut manifest: ApplicationManifest) -> ApplicationManifest {
868
877
  agent.capabilities.sort();
869
878
  }
870
879
  sort_name!(manifest.capabilities);
880
+ manifest.modules.sort_by(|a, b| a.get("id").and_then(Value::as_str).cmp(&b.get("id").and_then(Value::as_str)));
871
881
  sort_name!(manifest.connections);
872
882
  sort_name!(manifest.policies);
873
883
  sort_name!(manifest.ui.bindings);
@@ -246,7 +246,8 @@ use serde_json::Value;
246
246
  use sha2::{Digest, Sha256};
247
247
  use std::any::type_name;
248
248
  use std::collections::hash_map::DefaultHasher;
249
- use std::collections::{HashMap, HashSet};
249
+ use std::collections::{BTreeMap, HashMap, HashSet};
250
+ use std::ops::Bound::{Excluded, Unbounded};
250
251
  use std::fmt::{Display, Formatter};
251
252
  use std::fs::{self, OpenOptions};
252
253
  use std::hash::{Hash, Hasher};
@@ -315,7 +316,7 @@ pub struct FeltDb {
315
316
  #[allow(dead_code)] // Reserved runtime subsystems are initialized before their public APIs land.
316
317
  struct Inner {
317
318
  path: PathBuf,
318
- rows: HashMap<String, HashMap<String, StoredRow>>,
319
+ rows: HashMap<String, BTreeMap<String, StoredRow>>,
319
320
  key_counters: HashMap<String, u64>,
320
321
  query_count_by_type: HashMap<String, usize>,
321
322
  adaptive_indexes: HashSet<String>,
@@ -1721,6 +1722,23 @@ impl FeltDb {
1721
1722
  .unwrap_or_default())
1722
1723
  }
1723
1724
 
1725
+ /// Traverse one collection in immutable record-key order without materializing it.
1726
+ pub fn list_collection_page(&self, capability: &str, after: Option<&str>, limit: usize) -> Result<Vec<StoredRow>> {
1727
+ let inner = self.inner.lock().expect("lock poisoned");
1728
+ let Some(rows) = inner.rows.get(capability) else { return Ok(vec![]) };
1729
+ let values: Box<dyn Iterator<Item = &StoredRow>> = match after {
1730
+ Some(key) => Box::new(rows.range::<str, _>((Excluded(key), Unbounded)).map(|(_, row)| row)),
1731
+ None => Box::new(rows.values()),
1732
+ };
1733
+ Ok(values.filter(|row| !row.deleted).take(limit).cloned().collect())
1734
+ }
1735
+
1736
+ /// Fetch one live collection record without materializing collection state.
1737
+ pub fn get_collection_record(&self, capability: &str, key: &str) -> Result<Option<StoredRow>> {
1738
+ let inner = self.inner.lock().expect("lock poisoned");
1739
+ Ok(inner.rows.get(capability).and_then(|rows| rows.get(key)).filter(|row| !row.deleted).cloned())
1740
+ }
1741
+
1724
1742
  /// Subscribe to canonical mutation events.
1725
1743
  pub fn subscribe_changes(&self) -> broadcast::Receiver<ChangeEvent> {
1726
1744
  self.event_tx.subscribe()
@@ -1,10 +1,10 @@
1
+ use serde_json::Value;
1
2
  use std::path::PathBuf;
2
3
  use std::{
3
4
  collections::HashMap,
4
5
  sync::{atomic::AtomicU64, Arc},
5
6
  time::Instant,
6
7
  };
7
- use serde_json::Value;
8
8
 
9
9
  #[derive(Clone)]
10
10
  pub struct BoundedQueryCursor {
@@ -16,34 +16,22 @@ pub struct BoundedQueryCursor {
16
16
  pub created_at: u64,
17
17
  }
18
18
 
19
+ use crate::{
20
+ application_contract::ApplicationContractStore, artifacts::ArtifactStore, audit::AuditLog,
21
+ auth::KeyStore, causal::CausalStore, clock::LeaseClock, cluster::ClusterStore,
22
+ connections::ConnectionStore, content::ContentStore, identity::IdentityStore,
23
+ leases::LeaseStore, metrics::Metrics, portable_bundle::BundleStore, principals::PrincipalStore,
24
+ providers::ProviderStore, releases::ReleaseStore, sessions::SessionVerifier,
25
+ tenancy::TenancyStore,
26
+ };
19
27
  use feltdb::{
20
- FeltDb,
21
28
  application::ApplicationStore,
29
+ application_runtime::RuntimeStore,
22
30
  authorization::{GrantSigner, GrantStore},
23
31
  sync_contract::SyncStore,
24
- workload::WorkloadStore,
25
32
  worker_mesh::WorkerMeshStore,
26
- application_runtime::RuntimeStore,
27
- };
28
- use crate::{
29
- artifacts::ArtifactStore,
30
- audit::AuditLog,
31
- auth::KeyStore,
32
- causal::CausalStore,
33
- cluster::ClusterStore,
34
- connections::ConnectionStore,
35
- content::ContentStore,
36
- identity::IdentityStore,
37
- leases::LeaseStore,
38
- metrics::Metrics,
39
- portable_bundle::BundleStore,
40
- principals::PrincipalStore,
41
- providers::ProviderStore,
42
- releases::ReleaseStore,
43
- sessions::SessionVerifier,
44
- tenancy::TenancyStore,
45
- application_contract::ApplicationContractStore,
46
- clock::LeaseClock,
33
+ workload::WorkloadStore,
34
+ FeltDb,
47
35
  };
48
36
 
49
37
  #[derive(Clone)]
@@ -283,10 +283,18 @@ impl ApplicationContractStore {
283
283
  confidence: 0.0,
284
284
  evidence_required: vec!["contract evidence".into()],
285
285
  suggested_questions: vec![match p {
286
- "actors" => "Who will use this application, and what roles do they have?".into(),
287
- "entities" => "What information or records should the application keep track of?".into(),
286
+ "actors" => {
287
+ "Who will use this application, and what roles do they have?".into()
288
+ }
289
+ "entities" => {
290
+ "What information or records should the application keep track of?"
291
+ .into()
292
+ }
288
293
  "actions" => "What should people be able to do with those records?".into(),
289
- "permissions" => "Who can view, create, edit, delete, or administer this information?".into(),
294
+ "permissions" => {
295
+ "Who can view, create, edit, delete, or administer this information?"
296
+ .into()
297
+ }
290
298
  _ => format!("What should FeltDB understand about {p}?"),
291
299
  }],
292
300
  candidate_resolutions: vec![],
@@ -154,7 +154,6 @@ fn now() -> u64 {
154
154
  .as_secs()
155
155
  }
156
156
 
157
-
158
157
  #[cfg(test)]
159
158
  mod tests {
160
159
  use super::*;
@@ -224,10 +224,7 @@ impl CertificationHarness {
224
224
 
225
225
  /// Gets the count of tests in a category.
226
226
  pub fn test_count(&self, category: TestCategory) -> usize {
227
- self.results
228
- .get(&category)
229
- .map(|v| v.len())
230
- .unwrap_or(0)
227
+ self.results.get(&category).map(|v| v.len()).unwrap_or(0)
231
228
  }
232
229
 
233
230
  /// Gets all failed tests.
@@ -439,13 +436,10 @@ mod tests {
439
436
 
440
437
  #[test]
441
438
  fn test_execution_plan_builder() {
442
- let plan = TestExecutionPlan::new(
443
- TestSuiteDefinition::standard(),
444
- TestEnvironment::Local,
445
- )
446
- .with_parallel()
447
- .with_timeout(600)
448
- .with_retry(3);
439
+ let plan = TestExecutionPlan::new(TestSuiteDefinition::standard(), TestEnvironment::Local)
440
+ .with_parallel()
441
+ .with_timeout(600)
442
+ .with_retry(3);
449
443
 
450
444
  assert!(plan.parallel_execution);
451
445
  assert_eq!(plan.timeout_secs, 600);
@@ -480,19 +474,18 @@ mod tests {
480
474
  fn test_environment_strings() {
481
475
  assert_eq!(TestEnvironment::Local.as_str(), "local");
482
476
  assert_eq!(TestEnvironment::ManagedStaging.as_str(), "managed_staging");
483
- assert_eq!(TestEnvironment::ManagedProduction.as_str(), "managed_production");
477
+ assert_eq!(
478
+ TestEnvironment::ManagedProduction.as_str(),
479
+ "managed_production"
480
+ );
484
481
  }
485
482
 
486
483
  #[test]
487
484
  fn certification_report_serialization() {
488
485
  let mut harness = CertificationHarness::new(TestEnvironment::Local);
489
486
 
490
- let result = TestResult::new(
491
- "test_1".into(),
492
- TestCategory::Operations,
493
- "test_1".into(),
494
- )
495
- .passed(50);
487
+ let result =
488
+ TestResult::new("test_1".into(), TestCategory::Operations, "test_1".into()).passed(50);
496
489
 
497
490
  harness.record_result(result);
498
491
 
@@ -512,11 +505,19 @@ mod tests {
512
505
 
513
506
  for i in 0..3 {
514
507
  let result = if i == 1 {
515
- TestResult::new(format!("test_{}", i), TestCategory::Authorization, format!("test_{}", i))
516
- .failed("test failed".into(), 50)
508
+ TestResult::new(
509
+ format!("test_{}", i),
510
+ TestCategory::Authorization,
511
+ format!("test_{}", i),
512
+ )
513
+ .failed("test failed".into(), 50)
517
514
  } else {
518
- TestResult::new(format!("test_{}", i), TestCategory::Authorization, format!("test_{}", i))
519
- .passed(50)
515
+ TestResult::new(
516
+ format!("test_{}", i),
517
+ TestCategory::Authorization,
518
+ format!("test_{}", i),
519
+ )
520
+ .passed(50)
520
521
  };
521
522
  harness.record_result(result);
522
523
  }
@@ -132,10 +132,7 @@ impl DelegationTokenIssuer {
132
132
  let payload = self.canonicalize_for_signing(&token)?;
133
133
  let signature = self.sign(&payload)?;
134
134
 
135
- Ok(DelegationToken {
136
- signature,
137
- ..token
138
- })
135
+ Ok(DelegationToken { signature, ..token })
139
136
  }
140
137
 
141
138
  /// Canonicalizes token for consistent signing/verification.
@@ -180,11 +177,7 @@ impl DelegationTokenIssuer {
180
177
 
181
178
  impl DelegationTokenValidator {
182
179
  /// Creates a new token validator.
183
- pub fn new(
184
- expected_issuer: String,
185
- expected_audience: String,
186
- shared_secret: Vec<u8>,
187
- ) -> Self {
180
+ pub fn new(expected_issuer: String, expected_audience: String, shared_secret: Vec<u8>) -> Self {
188
181
  Self {
189
182
  expected_issuer,
190
183
  expected_audience,
@@ -316,8 +309,7 @@ mod tests {
316
309
  fn issue_and_validate_delegation_token() {
317
310
  let secret = test_shared_secret();
318
311
  let issuer = DelegationTokenIssuer::new("sherpa".into(), secret.clone());
319
- let validator =
320
- DelegationTokenValidator::new("sherpa".into(), "feltdb".into(), secret);
312
+ let validator = DelegationTokenValidator::new("sherpa".into(), "feltdb".into(), secret);
321
313
 
322
314
  let token = issuer
323
315
  .issue(
@@ -337,8 +329,7 @@ mod tests {
337
329
  fn reject_expired_token() {
338
330
  let secret = test_shared_secret();
339
331
  let issuer = DelegationTokenIssuer::new("sherpa".into(), secret.clone());
340
- let validator =
341
- DelegationTokenValidator::new("sherpa".into(), "feltdb".into(), secret);
332
+ let validator = DelegationTokenValidator::new("sherpa".into(), "feltdb".into(), secret);
342
333
 
343
334
  let mut token = issuer
344
335
  .issue(
@@ -450,8 +441,7 @@ mod tests {
450
441
  fn tampering_invalidates_signature() {
451
442
  let secret = test_shared_secret();
452
443
  let issuer = DelegationTokenIssuer::new("sherpa".into(), secret.clone());
453
- let validator =
454
- DelegationTokenValidator::new("sherpa".into(), "feltdb".into(), secret);
444
+ let validator = DelegationTokenValidator::new("sherpa".into(), "feltdb".into(), secret);
455
445
 
456
446
  let mut token = issuer
457
447
  .issue(
@@ -114,10 +114,7 @@ impl DurableOperationStore {
114
114
  .map_err(|_| "store lock poisoned".to_string())?;
115
115
 
116
116
  if ops.contains_key(&operation_id) {
117
- return Err(format!(
118
- "operation already exists: {}",
119
- operation_id
120
- ));
117
+ return Err(format!("operation already exists: {}", operation_id));
121
118
  }
122
119
 
123
120
  let now = now();
@@ -158,7 +155,8 @@ impl DurableOperationStore {
158
155
  .ok_or(format!("operation not found: {}", operation_id))?;
159
156
 
160
157
  // Can only claim Pending or Unknown operations
161
- if operation.state != OperationState::Pending && operation.state != OperationState::Unknown {
158
+ if operation.state != OperationState::Pending && operation.state != OperationState::Unknown
159
+ {
162
160
  return Err(format!(
163
161
  "cannot claim operation in state {:?}",
164
162
  operation.state
@@ -339,7 +337,8 @@ impl DurableOperationStore {
339
337
  }
340
338
 
341
339
  // Must be in Claimed or Running state
342
- if operation.state != OperationState::Claimed && operation.state != OperationState::Running {
340
+ if operation.state != OperationState::Claimed && operation.state != OperationState::Running
341
+ {
343
342
  return Err(format!(
344
343
  "cannot fail operation in state {:?}",
345
344
  operation.state
@@ -356,10 +355,7 @@ impl DurableOperationStore {
356
355
  }
357
356
 
358
357
  /// Marks an operation as Unknown (when lease expires).
359
- pub fn mark_unknown(
360
- &self,
361
- operation_id: &str,
362
- ) -> Result<DurableOperation, String> {
358
+ pub fn mark_unknown(&self, operation_id: &str) -> Result<DurableOperation, String> {
363
359
  let mut ops = self
364
360
  .operations
365
361
  .write()
@@ -372,7 +368,8 @@ impl DurableOperationStore {
372
368
  let now = now();
373
369
 
374
370
  // Can only mark Unknown if in Claimed or Running state
375
- if operation.state != OperationState::Claimed && operation.state != OperationState::Running {
371
+ if operation.state != OperationState::Claimed && operation.state != OperationState::Running
372
+ {
376
373
  return Err(format!(
377
374
  "cannot mark Unknown operation in state {:?}",
378
375
  operation.state
@@ -484,9 +481,7 @@ impl DurableOperationStore {
484
481
  .map_err(|_| "store lock poisoned".to_string())?;
485
482
 
486
483
  let before_len = ops.len();
487
- ops.retain(|_, op| {
488
- !(op.state == OperationState::Completed && op.updated_at < cutoff_time)
489
- });
484
+ ops.retain(|_, op| !(op.state == OperationState::Completed && op.updated_at < cutoff_time));
490
485
  let after_len = ops.len();
491
486
 
492
487
  Ok(before_len - after_len)
@@ -755,9 +750,7 @@ mod tests {
755
750
  .claim_operation("op_123", "worker_1".into(), 60)
756
751
  .expect("claim failed");
757
752
 
758
- let unknown = store
759
- .mark_unknown("op_123")
760
- .expect("mark unknown failed");
753
+ let unknown = store.mark_unknown("op_123").expect("mark unknown failed");
761
754
  assert_eq!(unknown.state, OperationState::Unknown);
762
755
 
763
756
  let reconciled = store
@@ -826,9 +819,7 @@ mod tests {
826
819
  )
827
820
  .expect("create failed");
828
821
 
829
- let ops = store
830
- .list_operations("tenant_prod")
831
- .expect("list failed");
822
+ let ops = store.list_operations("tenant_prod").expect("list failed");
832
823
  assert_eq!(ops.len(), 2);
833
824
  }
834
825
 
@@ -915,9 +906,7 @@ mod tests {
915
906
  .expect("cleanup failed");
916
907
  assert_eq!(deleted, 1);
917
908
 
918
- let ops = store
919
- .list_operations("tenant_prod")
920
- .expect("list failed");
909
+ let ops = store.list_operations("tenant_prod").expect("list failed");
921
910
  assert_eq!(ops.len(), 1);
922
911
  assert_eq!(ops[0].operation_id, "op_pending");
923
912
  }
@@ -978,11 +967,7 @@ mod tests {
978
967
  .expect("start failed");
979
968
 
980
969
  store
981
- .complete_operation(
982
- "op_123",
983
- "worker_1",
984
- serde_json::json!({"result": "done"}),
985
- )
970
+ .complete_operation("op_123", "worker_1", serde_json::json!({"result": "done"}))
986
971
  .expect("complete failed");
987
972
 
988
973
  let final_op = store.get_operation("op_123").expect("get failed");