@noy-db/hub 0.4.0-pre.2 → 0.4.0-pre.4
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.
- package/dist/aggregate/index.js +1 -1
- package/dist/blobs/index.js.map +1 -1
- package/dist/broker/index.js +3 -3
- package/dist/broker/index.js.map +1 -1
- package/dist/cargo/index.js +5 -5
- package/dist/{chunk-OGY4ELVE.js → chunk-3XVOLB75.js} +2 -2
- package/dist/{chunk-OTU5ZI3U.js → chunk-4X6DJZHY.js} +3 -3
- package/dist/{chunk-IU22SQGU.js → chunk-5KI3REO3.js} +2 -2
- package/dist/{chunk-IU22SQGU.js.map → chunk-5KI3REO3.js.map} +1 -1
- package/dist/{chunk-PWZKD4HW.js → chunk-BLB7P7LS.js} +3 -3
- package/dist/{chunk-Y5E6CH6S.js → chunk-EW4HFWGJ.js} +2 -2
- package/dist/{chunk-S3OYXPSE.js → chunk-HKR25MGX.js} +2 -2
- package/dist/{chunk-RI3OKC3Y.js → chunk-JIMEB4C6.js} +1 -1
- package/dist/chunk-JIMEB4C6.js.map +1 -0
- package/dist/{chunk-J6U4BOKP.js → chunk-JW3XCZA2.js} +9 -9
- package/dist/{chunk-4JYYIFHW.js → chunk-QUIG6BZT.js} +16 -16
- package/dist/{chunk-4JYYIFHW.js.map → chunk-QUIG6BZT.js.map} +1 -1
- package/dist/{chunk-TYBXP6WE.js → chunk-QYJIOWKN.js} +2 -2
- package/dist/{chunk-A64XXIIB.js → chunk-U73LHVPF.js} +17 -17
- package/dist/{chunk-A64XXIIB.js.map → chunk-U73LHVPF.js.map} +1 -1
- package/dist/{chunk-VZELV7ZJ.js → chunk-W7TCXZWU.js} +455 -554
- package/dist/chunk-W7TCXZWU.js.map +1 -0
- package/dist/{chunk-RGL53NXZ.js → chunk-WEC3JGBA.js} +2 -2
- package/dist/{chunk-HVZT5T22.js → chunk-WJDYZP3U.js} +1 -1
- package/dist/chunk-WJDYZP3U.js.map +1 -0
- package/dist/{collection-facade-ENLTJZBT.js → collection-facade-CMZGQ6IS.js} +2 -2
- package/dist/{executor-UF7LTX6O.js → executor-YORMIBWV.js} +3 -3
- package/dist/index.d.ts +5 -0
- package/dist/index.js +14 -14
- package/dist/kernel/collection-config.d.ts +11 -69
- package/dist/kernel/collection.d.ts +10 -21
- package/dist/kernel/enclave/index.d.ts +1 -1
- package/dist/kernel/noydb.d.ts +24 -13
- package/dist/kernel/vault.d.ts +21 -68
- package/dist/{liberate-W5MDWYOV.js → liberate-FK7JI2QX.js} +3 -3
- package/dist/materialized-views/index.js +3 -3
- package/dist/{noydb-U63S3Z7F.js → noydb-N5IW6OFZ.js} +10 -10
- package/dist/port/with/archive-strategy.d.ts +31 -0
- package/dist/port/with/strategies.d.ts +134 -0
- package/dist/query/index.js +2 -2
- package/dist/{seed-6GTDHL4E.js → seed-EKF4DIBB.js} +2 -2
- package/dist/{stale-NB5BGRVU.js → stale-4UNPHWOU.js} +2 -2
- package/dist/team/index.js +3 -3
- package/dist/tiers/index.js +1 -1
- package/dist/via/blob/active.d.ts +2 -1
- package/dist/with/index.js +2 -2
- package/dist/with-audit/forget/index.d.ts +2 -1
- package/dist/with-audit/tiers/index.d.ts +1 -0
- package/dist/with-cargo/index.d.ts +2 -0
- package/dist/with-formula/derivations/index.d.ts +2 -0
- package/dist/with-formula/materialized-views/index.d.ts +1 -0
- package/dist/with-lookup/aggregate/reducers.d.ts +1 -2
- package/dist/with-party/broker/index.d.ts +1 -0
- package/dist/with-party/team/keyring.d.ts +2 -2
- package/package.json +5 -4
- package/dist/chunk-HVZT5T22.js.map +0 -1
- package/dist/chunk-RI3OKC3Y.js.map +0 -1
- package/dist/chunk-VZELV7ZJ.js.map +0 -1
- /package/dist/{chunk-OGY4ELVE.js.map → chunk-3XVOLB75.js.map} +0 -0
- /package/dist/{chunk-OTU5ZI3U.js.map → chunk-4X6DJZHY.js.map} +0 -0
- /package/dist/{chunk-PWZKD4HW.js.map → chunk-BLB7P7LS.js.map} +0 -0
- /package/dist/{chunk-Y5E6CH6S.js.map → chunk-EW4HFWGJ.js.map} +0 -0
- /package/dist/{chunk-S3OYXPSE.js.map → chunk-HKR25MGX.js.map} +0 -0
- /package/dist/{chunk-J6U4BOKP.js.map → chunk-JW3XCZA2.js.map} +0 -0
- /package/dist/{chunk-TYBXP6WE.js.map → chunk-QYJIOWKN.js.map} +0 -0
- /package/dist/{chunk-RGL53NXZ.js.map → chunk-WEC3JGBA.js.map} +0 -0
- /package/dist/{collection-facade-ENLTJZBT.js.map → collection-facade-CMZGQ6IS.js.map} +0 -0
- /package/dist/{executor-UF7LTX6O.js.map → executor-YORMIBWV.js.map} +0 -0
- /package/dist/{liberate-W5MDWYOV.js.map → liberate-FK7JI2QX.js.map} +0 -0
- /package/dist/{noydb-U63S3Z7F.js.map → noydb-N5IW6OFZ.js.map} +0 -0
- /package/dist/{seed-6GTDHL4E.js.map → seed-EKF4DIBB.js.map} +0 -0
- /package/dist/{stale-NB5BGRVU.js.map → stale-4UNPHWOU.js.map} +0 -0
package/dist/aggregate/index.js
CHANGED
package/dist/blobs/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/via/blob/active.ts"],"sourcesContent":["/**\n * Active blob strategy factory. Calling `blobs()` returns a\n * `BlobStrategy` whose `openSlot` constructs a real `BlobSet` bound\n * to the caller's record. The returned strategy is passed into\n * `createNoydb({ blobStrategy: blobs() })` to light up the\n * `collection.blob(id)` path.\n *\n * This module is only reachable through the `@noy-db/hub/blobs`\n * subpath — a consumer that never imports the subpath ships none of\n * this (ESM tree-shaking + hub's `\"sideEffects\": false`).\n */\n\nimport { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { BlobStrategy } from '../../port/with/blob-strategy.js'\nimport { createBlobPinCache } from '../../with-shape/blobs/blob-pinning.js'\nimport type { BlobPinStore, BlobCacheStats } from '../../with-shape/blobs/blob-pinning.js'\n\n/** Options for {@link withBlobs} (#808). */\nexport interface WithBlobsOptions {\n /**\n * Device-local backend for the offline-pin registry + external side-cache\n * (`collection.blob(id).pin()`, the `cacheBudget` LRU stamps). Default:\n * in-memory — pins then last one app session. Supply a durable,\n * DEVICE-LOCAL store (IndexedDB-backed on the web; it must never sync) to\n * keep pins across restarts. See `BlobPinStore`'s doc for the posture.\n */\n readonly pinStore?: BlobPinStore\n}\n\n/**\n * The strategy `withBlobs()` returns — a `BlobStrategy` plus the device-local\n * blob-cache KPI surface (#808).\n */\nexport interface BlobsService extends BlobStrategy {\n /**\n * Snapshot of this device's blob-cache KPI counters: local-read hits,\n * network misses, and total bytes downloaded from the object store.\n */\n cacheStats(): BlobCacheStats\n}\n\n/**\n * Build a default `BlobStrategy` ready to pass into `createNoydb`.\n *\n * Named `withBlobs` (plugin-pattern canonical) rather than `blobs` to\n * avoid shadowing the very common local idiom\n * `const blobs = invoices.blob(id)` in user code.\n *\n * One `withBlobs()` instance owns one device-local pin registry + KPI\n * counter set (#808) — treat it as \"this device's\" blob service.\n *\n * @example\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withBlobs } from '@noy-db/hub/blobs'\n *\n * const db = await createNoydb({\n * store, user, secret,\n * blobStrategy: withBlobs(),\n * })\n *\n * // Now live — delegates to BlobSet.\n * await db.
|
|
1
|
+
{"version":3,"sources":["../../src/via/blob/active.ts"],"sourcesContent":["/**\n * Active blob strategy factory. Calling `blobs()` returns a\n * `BlobStrategy` whose `openSlot` constructs a real `BlobSet` bound\n * to the caller's record. The returned strategy is passed into\n * `createNoydb({ blobStrategy: blobs() })` to light up the\n * `collection.blob(id)` path.\n *\n * This module is only reachable through the `@noy-db/hub/blobs`\n * subpath — a consumer that never imports the subpath ships none of\n * this (ESM tree-shaking + hub's `\"sideEffects\": false`).\n */\n\nimport { BlobSet } from '../../with-shape/blobs/blob-set.js'\nimport type { BlobStrategy } from '../../port/with/blob-strategy.js'\nimport { createBlobPinCache } from '../../with-shape/blobs/blob-pinning.js'\nimport type { BlobPinStore, BlobCacheStats } from '../../with-shape/blobs/blob-pinning.js'\n\n/** Options for {@link withBlobs} (#808). */\nexport interface WithBlobsOptions {\n /**\n * Device-local backend for the offline-pin registry + external side-cache\n * (`collection.blob(id).pin()`, the `cacheBudget` LRU stamps). Default:\n * in-memory — pins then last one app session. Supply a durable,\n * DEVICE-LOCAL store (IndexedDB-backed on the web; it must never sync) to\n * keep pins across restarts. See `BlobPinStore`'s doc for the posture.\n */\n readonly pinStore?: BlobPinStore\n}\n\n/**\n * The strategy `withBlobs()` returns — a `BlobStrategy` plus the device-local\n * blob-cache KPI surface (#808).\n */\nexport interface BlobsService extends BlobStrategy {\n /**\n * Snapshot of this device's blob-cache KPI counters: local-read hits,\n * network misses, and total bytes downloaded from the object store.\n */\n cacheStats(): BlobCacheStats\n}\n\n/**\n * Build a default `BlobStrategy` ready to pass into `createNoydb`.\n *\n * Named `withBlobs` (plugin-pattern canonical) rather than `blobs` to\n * avoid shadowing the very common local idiom\n * `const blobs = invoices.blob(id)` in user code.\n *\n * One `withBlobs()` instance owns one device-local pin registry + KPI\n * counter set (#808) — treat it as \"this device's\" blob service.\n *\n * @example\n * ```ts\n * import { createNoydb } from '@noy-db/hub'\n * import { withBlobs } from '@noy-db/hub/blobs'\n *\n * const db = await createNoydb({\n * store, user, secret,\n * blobStrategy: withBlobs(),\n * })\n *\n * // Now live — delegates to BlobSet.\n * const vault = await db.openVault('acme')\n * await vault.collection('invoices').blob('inv-1').put('receipt.pdf', bytes)\n * ```\n */\nexport function withBlobs(options: WithBlobsOptions = {}): BlobsService {\n const pinCache = createBlobPinCache(options.pinStore)\n return {\n openSlot(args) {\n return new BlobSet({ ...args, pinCache })\n },\n cacheStats() {\n return { ...pinCache.stats }\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEO,SAAS,UAAU,UAA4B,CAAC,GAAiB;AACtE,QAAM,WAAW,mBAAmB,QAAQ,QAAQ;AACpD,SAAO;AAAA,IACL,SAAS,MAAM;AACb,aAAO,IAAI,QAAQ,EAAE,GAAG,MAAM,SAAS,CAAC;AAAA,IAC1C;AAAA,IACA,aAAa;AACX,aAAO,EAAE,GAAG,SAAS,MAAM;AAAA,IAC7B;AAAA,EACF;AACF;","names":[]}
|
package/dist/broker/index.js
CHANGED
|
@@ -30,7 +30,7 @@ function withBroker(config) {
|
|
|
30
30
|
const cache = /* @__PURE__ */ new Map();
|
|
31
31
|
return {
|
|
32
32
|
async enroll(ctx) {
|
|
33
|
-
const { enrollSeed } = await import("../seed-
|
|
33
|
+
const { enrollSeed } = await import("../seed-EKF4DIBB.js");
|
|
34
34
|
return enrollSeed({ ...ctx, config });
|
|
35
35
|
},
|
|
36
36
|
async rotate(ctx) {
|
|
@@ -39,7 +39,7 @@ function withBroker(config) {
|
|
|
39
39
|
const inFlight = scoped.map(([, e]) => e.inFlight).filter((p) => p !== void 0);
|
|
40
40
|
await Promise.allSettled(inFlight);
|
|
41
41
|
for (const [key] of scoped) cache.delete(key);
|
|
42
|
-
const { rotateSeed } = await import("../seed-
|
|
42
|
+
const { rotateSeed } = await import("../seed-EKF4DIBB.js");
|
|
43
43
|
return rotateSeed({ ...ctx, config });
|
|
44
44
|
},
|
|
45
45
|
credentialSource(ctx, profile) {
|
|
@@ -56,7 +56,7 @@ function withBroker(config) {
|
|
|
56
56
|
const skewMs = config.skewMs ?? DEFAULT_SKEW_MS;
|
|
57
57
|
const capturedEntry = entry;
|
|
58
58
|
const inFlight = (async () => {
|
|
59
|
-
const { mintStoreCredentials } = await import("../seed-
|
|
59
|
+
const { mintStoreCredentials } = await import("../seed-EKF4DIBB.js");
|
|
60
60
|
const creds = await mintStoreCredentials({ ...ctx, config }, profile);
|
|
61
61
|
capturedEntry.cached = { creds, validUntil: cacheFloor(creds, skewMs) };
|
|
62
62
|
return creds;
|
package/dist/broker/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/with-party/broker/active.ts","../../src/with-party/broker/index.ts"],"sourcesContent":["/**\n * Enable the credential-broker capability (#479, spec §5).\n *\n * Pass to `createNoydb({ brokerStrategy: withBroker(config) })` to make\n * `vault.broker()` live. Each method dynamic-imports the seed lifecycle\n * engine (`./seed.js`) — mirroring `via/classified/active.ts`'s per-method\n * dynamic-import seam — so `with-party/broker/seed.ts` (the heavy module:\n * the CAS enrol, quiesce-then-swap rotate, and the challenge/response round\n * trip) is reachable only through the `@noy-db/hub/broker` subpath and\n * never enters the single-user floor bundle.\n *\n * The single-flight, per-vault-per-profile refresh cache (H-6/I6) lives\n * HERE, in this factory's closure — shared across every `credentialSource(profile)`\n * call for this one broker instance (in turn shared across every vault of a\n * `createNoydb()` instance — `vault.broker()` passes the SAME `BrokerStrategy`\n * for all of them), so `rotate()` can quiesce it (await any in-flight\n * round-trip, then drop it) before the seed swap. The cache key is\n * `${vault}\\0${profile}` — keyed by vault FIRST, or two vaults sharing this\n * one strategy would collide and hand vault-B a cached credential minted\n * (and STS-scoped) for vault-A.\n */\nimport type { BrokerStrategy, BrokerConfig, BrokerCtx } from '../../port/with/broker-strategy.js'\nimport type { StoreCredentials, StoreCredentialSource } from '../../kernel/types.js'\n\nconst DEFAULT_SKEW_MS = 60_000\n/** Floor under the skew-adjusted boundary so a sub-skew credential TTL\n * doesn't collapse to \"already expired\" and thrash re-proving every call (I6b). */\nconst MIN_CACHE_MS = 1_000\n\ninterface CacheEntry {\n cached?: { creds: StoreCredentials; validUntil: number } | undefined\n inFlight?: Promise<StoreCredentials> | undefined\n}\n\nfunction cacheFloor(creds: StoreCredentials, skewMs: number): number {\n const expiresAtMs = creds.expiresAt ? Date.parse(creds.expiresAt) : Date.now() + skewMs * 2\n return Math.max(expiresAtMs - skewMs, Date.now() + MIN_CACHE_MS)\n}\n\nexport function withBroker(config: BrokerConfig): BrokerStrategy {\n const cache = new Map<string, CacheEntry>()\n\n return {\n async enroll(ctx) {\n const { enrollSeed } = await import('./seed.js')\n return enrollSeed({ ...ctx, config })\n },\n async rotate(ctx) {\n // Quiesce (I5): let any in-flight round-trip finish (it may still be\n // proving under the pre-rotation seed) before the local record is\n // overwritten, then drop the cache — scoped to THIS vault's entries\n // only (`${ctx.vault}\\0`-prefixed keys). A global cache.clear() here\n // would blow away every OTHER vault's cache on this shared strategy\n // too — same root cause as the cross-vault read collision this fixes.\n const vaultPrefix = `${ctx.vault}\\0`\n const scoped = [...cache.entries()].filter(([key]) => key.startsWith(vaultPrefix))\n const inFlight = scoped.map(([, e]) => e.inFlight).filter((p): p is Promise<StoreCredentials> => p !== undefined)\n await Promise.allSettled(inFlight)\n for (const [key] of scoped) cache.delete(key)\n const { rotateSeed } = await import('./seed.js')\n return rotateSeed({ ...ctx, config })\n },\n credentialSource(ctx: BrokerCtx, profile?: string): StoreCredentialSource {\n const key = `${ctx.vault}\\0${profile ?? ''}`\n return async () => {\n let entry = cache.get(key)\n if (!entry) {\n entry = {}\n cache.set(key, entry)\n }\n const now = Date.now()\n if (entry.cached && now < entry.cached.validUntil) return entry.cached.creds\n if (entry.inFlight) return entry.inFlight\n\n const skewMs = config.skewMs ?? DEFAULT_SKEW_MS\n const capturedEntry = entry\n const inFlight = (async () => {\n const { mintStoreCredentials } = await import('./seed.js')\n const creds = await mintStoreCredentials({ ...ctx, config }, profile)\n capturedEntry.cached = { creds, validUntil: cacheFloor(creds, skewMs) }\n return creds\n })()\n entry.inFlight = inFlight\n try {\n return await inFlight\n } finally {\n // Clear-on-settle (I6a): a rejected in-flight promise must never\n // stay pinned in the cache, or every subsequent call would wedge\n // on the same rejection instead of retrying.\n capturedEntry.inFlight = undefined\n }\n }\n },\n }\n}\n","/**\n * `@noy-db/hub/broker` — subpath export for the credential-broker capability\n * (#479, credential-broker spec). Apps that never call `vault.broker()` can\n * exclude this subpath entirely: the floor default `NO_BROKER` throws, and\n * this module's real engine (`with-party/broker/{seed,active}.ts`) is only\n * ever reached through `withBroker()`'s dynamic imports, so it never enters\n * a floor bundle.\n *\n * Also re-exports the host-side helpers (`issueChallenge`, `verifyBrokerProof`)\n * from the enclave barrel, so a Lambda/Worker implementing the reference\n * broker host can import just this one subpath and get everything it\n * needs, `crypto.subtle`-only.\n */\n\n// ─── Capability opt-in seam ──────────────────────────────────\nexport { withBroker } from './active.js'\nexport {\n NO_BROKER,\n type BrokerStrategy,\n type BrokerCtx,\n type BrokerConfig,\n type CredentialBrokerHandle,\n} from '../../port/with/broker-strategy.js'\nexport { BrokerNotEnabledError, BrokerEnrolmentError, BrokerProofError } from '../../kernel/errors.js'\n\n// ─── Host helpers (verify side — for a reference broker host) ───\nimport { issueChallenge as issueChallengeRaw, verifyBrokerProof } from '../../kernel/enclave/index.js'\nimport type { IssuedChallenge, VerifyBrokerProofArgs } from '../../kernel/enclave/index.js'\n\n/**\n * Floor — a challenge TTL must survive network latency + clock drift\n * between host and client. The enclave's `issueChallenge` is deliberately\n * unclamped (a mechanical primitive, not a policy); this floor is host\n * POLICY, so it lives here in the party layer, not\n * `kernel/enclave/broker/proof.ts` (carried forward from Task 2's review,\n * I6b).\n */\nconst MIN_CHALLENGE_TTL_MS = 10_000\n/** Ceiling — spec §2 step 2: \"TTL ≤ 60 s\". */\nconst MAX_CHALLENGE_TTL_MS = 60_000\n\n/**\n * {@link issueChallengeRaw} (the enclave's mechanical primitive) with a\n * `[10s, 60s]` TTL clamp applied, so a reference host built on this barrel\n * gets a safe challenge TTL without having to know the bounds itself.\n */\nexport function issueChallenge(opts?: { ttlMs?: number }): IssuedChallenge {\n const requested = opts?.ttlMs ?? MAX_CHALLENGE_TTL_MS\n const clamped = Math.min(Math.max(requested, MIN_CHALLENGE_TTL_MS), MAX_CHALLENGE_TTL_MS)\n return issueChallengeRaw({ ttlMs: clamped })\n}\nexport { verifyBrokerProof }\nexport type { VerifyBrokerProofArgs }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAM,kBAAkB;AAGxB,IAAM,eAAe;AAOrB,SAAS,WAAW,OAAyB,QAAwB;AACnE,QAAM,cAAc,MAAM,YAAY,KAAK,MAAM,MAAM,SAAS,IAAI,KAAK,IAAI,IAAI,SAAS;AAC1F,SAAO,KAAK,IAAI,cAAc,QAAQ,KAAK,IAAI,IAAI,YAAY;AACjE;AAEO,SAAS,WAAW,QAAsC;AAC/D,QAAM,QAAQ,oBAAI,IAAwB;AAE1C,SAAO;AAAA,IACL,MAAM,OAAO,KAAK;AAChB,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,qBAAW;AAC/C,aAAO,WAAW,EAAE,GAAG,KAAK,OAAO,CAAC;AAAA,IACtC;AAAA,IACA,MAAM,OAAO,KAAK;AAOhB,YAAM,cAAc,GAAG,IAAI,KAAK;AAChC,YAAM,SAAS,CAAC,GAAG,MAAM,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,IAAI,WAAW,WAAW,CAAC;AACjF,YAAM,WAAW,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAsC,MAAM,MAAS;AAChH,YAAM,QAAQ,WAAW,QAAQ;AACjC,iBAAW,CAAC,GAAG,KAAK,OAAQ,OAAM,OAAO,GAAG;AAC5C,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,qBAAW;AAC/C,aAAO,WAAW,EAAE,GAAG,KAAK,OAAO,CAAC;AAAA,IACtC;AAAA,IACA,iBAAiB,KAAgB,SAAyC;AACxE,YAAM,MAAM,GAAG,IAAI,KAAK,KAAK,WAAW,EAAE;AAC1C,aAAO,YAAY;AACjB,YAAI,QAAQ,MAAM,IAAI,GAAG;AACzB,YAAI,CAAC,OAAO;AACV,kBAAQ,CAAC;AACT,gBAAM,IAAI,KAAK,KAAK;AAAA,QACtB;AACA,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,MAAM,UAAU,MAAM,MAAM,OAAO,WAAY,QAAO,MAAM,OAAO;AACvE,YAAI,MAAM,SAAU,QAAO,MAAM;AAEjC,cAAM,SAAS,OAAO,UAAU;AAChC,cAAM,gBAAgB;AACtB,cAAM,YAAY,YAAY;AAC5B,gBAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,qBAAW;AACzD,gBAAM,QAAQ,MAAM,qBAAqB,EAAE,GAAG,KAAK,OAAO,GAAG,OAAO;AACpE,wBAAc,SAAS,EAAE,OAAO,YAAY,WAAW,OAAO,MAAM,EAAE;AACtE,iBAAO;AAAA,QACT,GAAG;AACH,cAAM,WAAW;AACjB,YAAI;AACF,iBAAO,MAAM;AAAA,QACf,UAAE;AAIA,wBAAc,WAAW;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACzDA,IAAM,uBAAuB;AAE7B,IAAM,uBAAuB;AAOtB,SAASA,gBAAe,MAA4C;AACzE,QAAM,YAAY,MAAM,SAAS;AACjC,QAAM,UAAU,KAAK,IAAI,KAAK,IAAI,WAAW,oBAAoB,GAAG,oBAAoB;AACxF,SAAO,eAAkB,EAAE,OAAO,QAAQ,CAAC;AAC7C;","names":["issueChallenge"]}
|
|
1
|
+
{"version":3,"sources":["../../src/with-party/broker/active.ts","../../src/with-party/broker/index.ts"],"sourcesContent":["/**\n * Enable the credential-broker capability (#479, spec §5).\n *\n * Pass to `createNoydb({ brokerStrategy: withBroker(config) })` to make\n * `vault.broker()` live. Each method dynamic-imports the seed lifecycle\n * engine (`./seed.js`) — mirroring `via/classified/active.ts`'s per-method\n * dynamic-import seam — so `with-party/broker/seed.ts` (the heavy module:\n * the CAS enrol, quiesce-then-swap rotate, and the challenge/response round\n * trip) is reachable only through the `@noy-db/hub/broker` subpath and\n * never enters the single-user floor bundle.\n *\n * The single-flight, per-vault-per-profile refresh cache (H-6/I6) lives\n * HERE, in this factory's closure — shared across every `credentialSource(profile)`\n * call for this one broker instance (in turn shared across every vault of a\n * `createNoydb()` instance — `vault.broker()` passes the SAME `BrokerStrategy`\n * for all of them), so `rotate()` can quiesce it (await any in-flight\n * round-trip, then drop it) before the seed swap. The cache key is\n * `${vault}\\0${profile}` — keyed by vault FIRST, or two vaults sharing this\n * one strategy would collide and hand vault-B a cached credential minted\n * (and STS-scoped) for vault-A.\n */\nimport type { BrokerStrategy, BrokerConfig, BrokerCtx } from '../../port/with/broker-strategy.js'\nimport type { StoreCredentials, StoreCredentialSource } from '../../kernel/types.js'\n\nconst DEFAULT_SKEW_MS = 60_000\n/** Floor under the skew-adjusted boundary so a sub-skew credential TTL\n * doesn't collapse to \"already expired\" and thrash re-proving every call (I6b). */\nconst MIN_CACHE_MS = 1_000\n\ninterface CacheEntry {\n cached?: { creds: StoreCredentials; validUntil: number } | undefined\n inFlight?: Promise<StoreCredentials> | undefined\n}\n\nfunction cacheFloor(creds: StoreCredentials, skewMs: number): number {\n const expiresAtMs = creds.expiresAt ? Date.parse(creds.expiresAt) : Date.now() + skewMs * 2\n return Math.max(expiresAtMs - skewMs, Date.now() + MIN_CACHE_MS)\n}\n\nexport function withBroker(config: BrokerConfig): BrokerStrategy {\n const cache = new Map<string, CacheEntry>()\n\n return {\n async enroll(ctx) {\n const { enrollSeed } = await import('./seed.js')\n return enrollSeed({ ...ctx, config })\n },\n async rotate(ctx) {\n // Quiesce (I5): let any in-flight round-trip finish (it may still be\n // proving under the pre-rotation seed) before the local record is\n // overwritten, then drop the cache — scoped to THIS vault's entries\n // only (`${ctx.vault}\\0`-prefixed keys). A global cache.clear() here\n // would blow away every OTHER vault's cache on this shared strategy\n // too — same root cause as the cross-vault read collision this fixes.\n const vaultPrefix = `${ctx.vault}\\0`\n const scoped = [...cache.entries()].filter(([key]) => key.startsWith(vaultPrefix))\n const inFlight = scoped.map(([, e]) => e.inFlight).filter((p): p is Promise<StoreCredentials> => p !== undefined)\n await Promise.allSettled(inFlight)\n for (const [key] of scoped) cache.delete(key)\n const { rotateSeed } = await import('./seed.js')\n return rotateSeed({ ...ctx, config })\n },\n credentialSource(ctx: BrokerCtx, profile?: string): StoreCredentialSource {\n const key = `${ctx.vault}\\0${profile ?? ''}`\n return async () => {\n let entry = cache.get(key)\n if (!entry) {\n entry = {}\n cache.set(key, entry)\n }\n const now = Date.now()\n if (entry.cached && now < entry.cached.validUntil) return entry.cached.creds\n if (entry.inFlight) return entry.inFlight\n\n const skewMs = config.skewMs ?? DEFAULT_SKEW_MS\n const capturedEntry = entry\n const inFlight = (async () => {\n const { mintStoreCredentials } = await import('./seed.js')\n const creds = await mintStoreCredentials({ ...ctx, config }, profile)\n capturedEntry.cached = { creds, validUntil: cacheFloor(creds, skewMs) }\n return creds\n })()\n entry.inFlight = inFlight\n try {\n return await inFlight\n } finally {\n // Clear-on-settle (I6a): a rejected in-flight promise must never\n // stay pinned in the cache, or every subsequent call would wedge\n // on the same rejection instead of retrying.\n capturedEntry.inFlight = undefined\n }\n }\n },\n }\n}\n","/**\n * `@noy-db/hub/broker` — subpath export for the credential-broker capability\n * (#479, credential-broker spec). Apps that never call `vault.broker()` can\n * exclude this subpath entirely: the floor default `NO_BROKER` throws, and\n * this module's real engine (`with-party/broker/{seed,active}.ts`) is only\n * ever reached through `withBroker()`'s dynamic imports, so it never enters\n * a floor bundle.\n *\n * Also re-exports the host-side helpers (`issueChallenge`, `verifyBrokerProof`)\n * from the enclave barrel, so a Lambda/Worker implementing the reference\n * broker host can import just this one subpath and get everything it\n * needs, `crypto.subtle`-only.\n */\n\n// ─── Capability opt-in seam ──────────────────────────────────\nexport { withBroker } from './active.js'\nexport {\n NO_BROKER,\n type BrokerStrategy,\n type BrokerCtx,\n type BrokerConfig,\n type CredentialBrokerHandle,\n} from '../../port/with/broker-strategy.js'\nexport { BrokerNotEnabledError, BrokerEnrolmentError, BrokerProofError } from '../../kernel/errors.js'\n\n// ─── Host helpers (verify side — for a reference broker host) ───\nimport { issueChallenge as issueChallengeRaw, verifyBrokerProof } from '../../kernel/enclave/index.js'\nimport type { IssuedChallenge, VerifyBrokerProofArgs } from '../../kernel/enclave/index.js'\n\n/**\n * Floor — a challenge TTL must survive network latency + clock drift\n * between host and client. The enclave's `issueChallenge` is deliberately\n * unclamped (a mechanical primitive, not a policy); this floor is host\n * POLICY, so it lives here in the party layer, not\n * `kernel/enclave/broker/proof.ts` (carried forward from Task 2's review,\n * I6b).\n */\nconst MIN_CHALLENGE_TTL_MS = 10_000\n/** Ceiling — spec §2 step 2: \"TTL ≤ 60 s\". */\nconst MAX_CHALLENGE_TTL_MS = 60_000\n\n/**\n * {@link issueChallengeRaw} (the enclave's mechanical primitive) with a\n * `[10s, 60s]` TTL clamp applied, so a reference host built on this barrel\n * gets a safe challenge TTL without having to know the bounds itself.\n */\nexport function issueChallenge(opts?: { ttlMs?: number }): IssuedChallenge {\n const requested = opts?.ttlMs ?? MAX_CHALLENGE_TTL_MS\n const clamped = Math.min(Math.max(requested, MIN_CHALLENGE_TTL_MS), MAX_CHALLENGE_TTL_MS)\n return issueChallengeRaw({ ttlMs: clamped })\n}\nexport { verifyBrokerProof }\nexport type { VerifyBrokerProofArgs }\n\n// #837 — issueChallenge's return type must be nameable from this entry.\nexport type { IssuedChallenge } from '../../kernel/enclave/index.js'\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAwBA,IAAM,kBAAkB;AAGxB,IAAM,eAAe;AAOrB,SAAS,WAAW,OAAyB,QAAwB;AACnE,QAAM,cAAc,MAAM,YAAY,KAAK,MAAM,MAAM,SAAS,IAAI,KAAK,IAAI,IAAI,SAAS;AAC1F,SAAO,KAAK,IAAI,cAAc,QAAQ,KAAK,IAAI,IAAI,YAAY;AACjE;AAEO,SAAS,WAAW,QAAsC;AAC/D,QAAM,QAAQ,oBAAI,IAAwB;AAE1C,SAAO;AAAA,IACL,MAAM,OAAO,KAAK;AAChB,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,qBAAW;AAC/C,aAAO,WAAW,EAAE,GAAG,KAAK,OAAO,CAAC;AAAA,IACtC;AAAA,IACA,MAAM,OAAO,KAAK;AAOhB,YAAM,cAAc,GAAG,IAAI,KAAK;AAChC,YAAM,SAAS,CAAC,GAAG,MAAM,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM,IAAI,WAAW,WAAW,CAAC;AACjF,YAAM,WAAW,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,MAAsC,MAAM,MAAS;AAChH,YAAM,QAAQ,WAAW,QAAQ;AACjC,iBAAW,CAAC,GAAG,KAAK,OAAQ,OAAM,OAAO,GAAG;AAC5C,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,qBAAW;AAC/C,aAAO,WAAW,EAAE,GAAG,KAAK,OAAO,CAAC;AAAA,IACtC;AAAA,IACA,iBAAiB,KAAgB,SAAyC;AACxE,YAAM,MAAM,GAAG,IAAI,KAAK,KAAK,WAAW,EAAE;AAC1C,aAAO,YAAY;AACjB,YAAI,QAAQ,MAAM,IAAI,GAAG;AACzB,YAAI,CAAC,OAAO;AACV,kBAAQ,CAAC;AACT,gBAAM,IAAI,KAAK,KAAK;AAAA,QACtB;AACA,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,MAAM,UAAU,MAAM,MAAM,OAAO,WAAY,QAAO,MAAM,OAAO;AACvE,YAAI,MAAM,SAAU,QAAO,MAAM;AAEjC,cAAM,SAAS,OAAO,UAAU;AAChC,cAAM,gBAAgB;AACtB,cAAM,YAAY,YAAY;AAC5B,gBAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,qBAAW;AACzD,gBAAM,QAAQ,MAAM,qBAAqB,EAAE,GAAG,KAAK,OAAO,GAAG,OAAO;AACpE,wBAAc,SAAS,EAAE,OAAO,YAAY,WAAW,OAAO,MAAM,EAAE;AACtE,iBAAO;AAAA,QACT,GAAG;AACH,cAAM,WAAW;AACjB,YAAI;AACF,iBAAO,MAAM;AAAA,QACf,UAAE;AAIA,wBAAc,WAAW;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;ACzDA,IAAM,uBAAuB;AAE7B,IAAM,uBAAuB;AAOtB,SAASA,gBAAe,MAA4C;AACzE,QAAM,YAAY,MAAM,SAAS;AACjC,QAAM,UAAU,KAAK,IAAI,KAAK,IAAI,WAAW,oBAAoB,GAAG,oBAAoB;AACxF,SAAO,eAAkB,EAAE,OAAO,QAAQ,CAAC;AAC7C;","names":["issueChallenge"]}
|
package/dist/cargo/index.js
CHANGED
|
@@ -5,11 +5,11 @@ import {
|
|
|
5
5
|
describeExtraction,
|
|
6
6
|
unsealDeks,
|
|
7
7
|
withCargo
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-BLB7P7LS.js";
|
|
9
9
|
import {
|
|
10
10
|
CustodyApi,
|
|
11
11
|
NO_CARGO
|
|
12
|
-
} from "../chunk-
|
|
12
|
+
} from "../chunk-U73LHVPF.js";
|
|
13
13
|
import {
|
|
14
14
|
extractPartition,
|
|
15
15
|
walkClosure
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
isDeedVault,
|
|
20
20
|
liberateVault,
|
|
21
21
|
loadDeedMarker
|
|
22
|
-
} from "../chunk-
|
|
22
|
+
} from "../chunk-HKR25MGX.js";
|
|
23
23
|
import "../chunk-BCOKZC4B.js";
|
|
24
24
|
import {
|
|
25
25
|
fuseRetrieval
|
|
@@ -38,7 +38,7 @@ import "../chunk-YGBIBOCR.js";
|
|
|
38
38
|
import {
|
|
39
39
|
groupAndReduce,
|
|
40
40
|
reduceRecords
|
|
41
|
-
} from "../chunk-
|
|
41
|
+
} from "../chunk-JIMEB4C6.js";
|
|
42
42
|
import {
|
|
43
43
|
readPath
|
|
44
44
|
} from "../chunk-IGJCYUT5.js";
|
|
@@ -46,7 +46,7 @@ import "../chunk-MEKAMERX.js";
|
|
|
46
46
|
import "../chunk-BVZGNMVM.js";
|
|
47
47
|
import "../chunk-XGVDLQJE.js";
|
|
48
48
|
import "../chunk-STR7WBOB.js";
|
|
49
|
-
import "../chunk-
|
|
49
|
+
import "../chunk-WJDYZP3U.js";
|
|
50
50
|
import "../chunk-7B4GPCX7.js";
|
|
51
51
|
import "../chunk-MSO2C3KG.js";
|
|
52
52
|
import {
|
|
@@ -100,7 +100,7 @@ async function resolveStaleMVOnRead(accessor, outputCollection, dispatchCtx) {
|
|
|
100
100
|
continue;
|
|
101
101
|
}
|
|
102
102
|
if (executor === null) {
|
|
103
|
-
({ MaterializedViewExecutor: executor } = await import("./executor-
|
|
103
|
+
({ MaterializedViewExecutor: executor } = await import("./executor-YORMIBWV.js"));
|
|
104
104
|
}
|
|
105
105
|
for (const dep of reg.dependencies) {
|
|
106
106
|
await accessor.getCollection(dep).ensureHydrated();
|
|
@@ -132,4 +132,4 @@ export {
|
|
|
132
132
|
clearMVStale,
|
|
133
133
|
clearMVStaleFully
|
|
134
134
|
};
|
|
135
|
-
//# sourceMappingURL=chunk-
|
|
135
|
+
//# sourceMappingURL=chunk-3XVOLB75.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-OGFPMCSP.js";
|
|
4
4
|
import {
|
|
5
5
|
reducerBuilder
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JIMEB4C6.js";
|
|
7
7
|
import {
|
|
8
8
|
evaluateClause,
|
|
9
9
|
hasFnClause,
|
|
@@ -1527,11 +1527,11 @@ function coerceRefKey(value) {
|
|
|
1527
1527
|
}
|
|
1528
1528
|
|
|
1529
1529
|
export {
|
|
1530
|
-
buildLiveQuery,
|
|
1531
1530
|
NO_AGGREGATE,
|
|
1531
|
+
buildLiveQuery,
|
|
1532
1532
|
DEFAULT_CROSS_JOIN_MAX_ROWS,
|
|
1533
1533
|
Query,
|
|
1534
1534
|
executePlan,
|
|
1535
1535
|
ScanBuilder
|
|
1536
1536
|
};
|
|
1537
|
-
//# sourceMappingURL=chunk-
|
|
1537
|
+
//# sourceMappingURL=chunk-4X6DJZHY.js.map
|
|
@@ -365,8 +365,8 @@ function classifySealedShred(ctx, live) {
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
export {
|
|
368
|
-
withTiers,
|
|
369
368
|
NO_TIERS,
|
|
369
|
+
withTiers,
|
|
370
370
|
assertTiersEnabled,
|
|
371
371
|
assertDeclaredTier,
|
|
372
372
|
assertTierComposition,
|
|
@@ -378,4 +378,4 @@ export {
|
|
|
378
378
|
demote,
|
|
379
379
|
classifySealedShred
|
|
380
380
|
};
|
|
381
|
-
//# sourceMappingURL=chunk-
|
|
381
|
+
//# sourceMappingURL=chunk-5KI3REO3.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/with-audit/tiers/active.ts","../src/with-audit/tiers/strategy.ts","../src/with-audit/tiers/index.ts"],"sourcesContent":["/**\n * Enable the hierarchical-tiers capability.\n * Pass to `createNoydb({ tiersStrategy: withTiers() })` to make a collection's\n * `putAtTier` / `getAtTier` / `listAtTier` / `elevate` / `demote` methods live.\n * The tier read/write/re-key engine is dynamically imported here, so it stays\n * out of the floor bundle until opted in.\n */\nimport type { TiersStrategy } from './strategy.js'\n\nexport function withTiers(): TiersStrategy {\n return {\n async putAtTier(ctx, id, record, tier, opts) {\n const { putAtTier } = await import('./index.js')\n return putAtTier(ctx, id, record, tier, opts)\n },\n async getAtTier(ctx, id) {\n const { getAtTier } = await import('./index.js')\n return getAtTier(ctx, id)\n },\n async listAtTier(ctx) {\n const { listAtTier } = await import('./index.js')\n return listAtTier(ctx)\n },\n async elevate(ctx, id, toTier) {\n const { elevate } = await import('./index.js')\n return elevate(ctx, id, toTier)\n },\n async demote(ctx, id, toTier) {\n const { demote } = await import('./index.js')\n return demote(ctx, id, toTier)\n },\n }\n}\n","/**\n * Tiers capability strategy — the five on-demand collection-level tier\n * operations the `Collection` routes through. The active engine\n * ({@link withTiers}) dynamically imports the tier cores (keeping them out of\n * the floor bundle); {@link NO_TIERS} throws. Collection always builds its\n * per-call {@link TiersContext} and delegates here, so an un-opted-in caller\n * hits `NO_TIERS`'s throw.\n * @internal\n */\nimport type { GhostRecord } from '../../kernel/types.js'\nimport type { TiersContext, TierMoveResult } from './index.js'\nimport { TiersNotEnabledError } from '../../kernel/errors.js'\n\n// #779 — re-exported so kernel-spine callers (e.g. `Vault._elevatedPut`) can reference the\n// result shape via this grandfathered strategy.js seam instead of statically importing\n// tiers/index.js directly (the port-layering check only grandfathers this file).\nexport type { TierMoveResult }\n\nexport interface TiersStrategy {\n putAtTier<T>(\n ctx: TiersContext<T>,\n id: string,\n record: T,\n tier: number,\n opts?: { elevation?: { reason: string; fromTier: number }; source?: string; sourceTs?: string },\n ): Promise<TierMoveResult>\n getAtTier<T>(ctx: TiersContext<T>, id: string): Promise<T | GhostRecord | null>\n listAtTier<T>(ctx: TiersContext<T>): Promise<Array<{ id: string; tier: number; readable: boolean }>>\n elevate<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult>\n demote<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult>\n}\n\n/**\n * No-op stub — the floor default. Every capability method throws\n * {@link TiersNotEnabledError}; opt in with `tiersStrategy: withTiers()` in\n * createNoydb. @internal\n */\nexport const NO_TIERS: TiersStrategy = {\n async putAtTier() { throw new TiersNotEnabledError() },\n async getAtTier() { throw new TiersNotEnabledError() },\n async listAtTier() { throw new TiersNotEnabledError() },\n async elevate() { throw new TiersNotEnabledError() },\n async demote() { throw new TiersNotEnabledError() },\n}\n","/**\n * Hierarchical access — the collection-level tier operations\n * (`putAtTier` / `getAtTier` / `listAtTier` / `elevate` / `demote`).\n *\n * A collection opted into `{ tiers: [...] }` stamps `_tier: N` on each envelope\n * and encrypts its body under the tier-N DEK. These helpers are the read/write\n * surface for that scheme: write at a tier, read with tier-aware visibility\n * (invisibility vs. ghost), and move a record up (`elevate`) or down (`demote`)\n * the tier ladder by re-wrapping its body key.\n *\n * Each function takes a small {@link TiersContext} (the exact `this.*` the\n * moving methods touched) instead of `this`, mirroring the `record-keys/`\n * siblings. Behaviour is byte-identical to the inline code it replaced.\n *\n * The crux is `cekCache`: `elevate`/`demote`/`getAtTier` write the SAME `Lru`\n * reference `Collection` owns (never a copy) so a per-record CEK re-wrap stays\n * synchronous with the cache the kernel's write/read path also mutates. The\n * cross-tier event sink stays collection-resident and is reached via the\n * `emitCrossTierEvent` callback.\n *\n * Internal service — not exported as a `@noy-db/hub/*` subpath.\n */\nexport { withTiers } from './active.js'\nexport { NO_TIERS, type TiersStrategy } from './strategy.js'\nexport { TiersNotEnabledError } from '../../kernel/errors.js'\nimport { encrypt, decrypt, unwrapCek, rewrapBodyToDek, applyRewrappedBody, isDeleteMarker, isTombstoneShape, type RecordCodec, type EnclaveKey, type SealedShredSlot } from '../../kernel/enclave/index.js'\nimport { TierDemoteDeniedError, UnsupportedTierCompositionError, PersistedIndexCompensationError } from '../../kernel/errors.js'\nimport { dekKey, assertTierAccess } from '../../with-party/team/tiers.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport type { Lru } from '../../kernel/cache/index.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\nimport {\n NOYDB_FORMAT_VERSION,\n type NoydbStore,\n type EncryptedEnvelope,\n type GhostRecord,\n type TierMode,\n type CrossTierAccessEvent,\n} from '../../kernel/types.js'\n\n/** Everything the moving tier methods touched on `this.*`, as a flat context. */\nexport interface TiersContext<T> {\n /** Collection name — the crypto AAD scope and tier-DEK key prefix. */\n readonly name: string\n /** Vault namespace the records live under. */\n readonly vault: string\n /** The ciphertext store. */\n readonly adapter: NoydbStore\n /** The caller's unlocked keyring (tier-DEK holdings + role + userId). */\n readonly keyring: UnlockedKeyring\n /** The record codec — decrypts a tier-0 envelope to T. */\n readonly codec: RecordCodec<T>\n /**\n * The collection's per-record CEK cache (SHARED reference, not a copy).\n * `elevate`/`demote`/`getAtTier` re-wrap a record's CEK and `set` it here so\n * the move stays synchronous with the cache the kernel's read/write path\n * owns. `null` → no caching.\n */\n readonly cekCache: Lru<string, EnclaveKey> | null\n /**\n * Sync the collection's decoded-record cache after a tier move rewraps the\n * envelope (#691). The lazy LRU is evicted on EVERY call — it is never\n * re-seeded, since a lazy `#getRaw` miss refetches and decodes the fresh\n * envelope via its adapter fallback (a stale LRU entry after a tier-0\n * `putAtTier` overwrite was the #702 lazy-mode gap). `null` → also evict\n * the eager cache entry; an `entry` → set the eager cache to the given\n * decoded record/version (used by `demote()` landing back at tier 0 and\n * `putAtTier`'s tier-0 write, #702 — the record is tier-0, so it must stay\n * plain-`get()`-readable in-session).\n */\n syncCache(id: string, entry: { record: T; version: number } | null): void\n /**\n * Sync the collection's indexes after a tier move rewraps the envelope\n * (#709). Persisted `_idx/<field>/<recordId>` side-cars hold the indexed\n * field's PLAINTEXT value and are always encrypted under the tier-0 DEK,\n * whatever the record's own tier — the same leak class `forget()` fixed\n * via `purgePersistedIndexes` (\"forget() crypto-shreds the body but keeps\n * the collection DEK, under which these side-cars are encrypted — so\n * without this they leave the indexed field VALUES readable\"). `null` →\n * the record just left tier 0: purge its persisted side-cars and drop its\n * in-memory index entries. A record → it is tier-0 again: (re)build its\n * entries from that record. Call this BEFORE {@link syncCache} — the\n * implementation reads the pre-write cached value as \"previous\" to clean\n * up stale index buckets, so it must run while that entry is still live.\n * `version` stamps a rebuilt side-car's own envelope version — every\n * caller has one in scope (the envelope it just wrote), including the\n * purge (`null`) branches, which pass it along even though it's ignored\n * there (#720: keeps the parameter honestly required, no dead default).\n * `priorEnvelope` (#720) — the RAW envelope read BEFORE this call's own\n * overwrite (already in scope as `existing`/`envelope` at every record-\n * branch call site), so a lazy same-tier value change (a `putAtTier(0)`\n * dropping an indexed field, a `demote(0)` off an elevated record) can\n * still be tier-gate-decoded as \"previous\" once the live envelope itself\n * has already moved past it. The `null`-record branches never pass one —\n * see the implementation's doc comment for why they don't need to.\n */\n syncIndexes(id: string, record: T | null, version: number, priorEnvelope?: EncryptedEnvelope): Promise<void>\n /** Sync the collection's SEARCH artifacts after a tier move (#721). Both the\n * lexical `_ftindex` blob and the `_vec/<id>` embedding are encrypted under\n * the tier-0 DEK and hold the record's derived plaintext (full field text /\n * a text-invertible vector), so leaving them means elevation never hid what\n * the record was searchable by — the `forget()` precedent, unapplied to\n * elevate. `null` → the record left tier 0: purge its `_vec`, and\n * invalidate the `_ftindex` blob (the cache-driven rebuild then excludes\n * it). A record → it is tier-0 again: re-embed it, and invalidate\n * `_ftindex` (rebuild includes it). No-op fast when the collection has no\n * search. */\n syncSearch(id: string, record: T | null, version?: number): Promise<void>\n /**\n * Sync a record's `_history` snapshots after a tier move rewraps the live\n * envelope (#712, at-rest hardening). Each snapshot carries its own key\n * material (a direct DEK wrap, or a per-record `_cek` itself wrapped under\n * the tier DEK) — `elevate()`'s live-body rewrap moves only the live\n * envelope, leaving prior versions decryptable at rest under the tier the\n * record has moved away from (the same leak class `forget()`'s\n * `tombstoneHistory` closed for erasure). `fromDek`/`toDek` are the SAME\n * DEKs the caller already resolved for the live rewrap — reuse them, don't\n * recompute. No-op when the strategy is `NO_HISTORY`.\n */\n syncHistory(id: string, fromDek: EnclaveKey, toDek: EnclaveKey): Promise<void>\n /**\n * Save a raw pre-move envelope as a `_history` snapshot (#728). Tier moves\n * (`putAtTier`/`elevate`/`demote`) bump `_v` and overwrite the live\n * envelope without ever snapshotting the version that existed just before\n * the move — so `history()` silently lost it. The caller (`tiers/index.ts`)\n * builds `envelope` by reusing the SAME `rewrapBodyToDek(envelope, fromDek,\n * toDek)` result it already computed for the live write, so the snapshot\n * lands wrapped under the DESTINATION tier's DEK — never `ctx.codec.\n * encryptRecord`, which always resolves the tier-0 DEK and would leak the\n * pre-move body whenever `fromTier > 0`. No-op when history is disabled\n * (folds the `historyConfig.enabled` gate so `tiers/index.ts` stays simple).\n */\n saveHistorySnapshot(id: string, envelope: EncryptedEnvelope): Promise<void>\n /**\n * `true` iff a real history strategy is wired (not `NO_HISTORY`) AND not\n * explicitly disabled via `historyConfig.enabled`. `putAtTier` checks this\n * BEFORE decrypting `existing` to build a snapshot (#728, #737 regression\n * fix) — unlike elevate/demote, which reuse a `body` rewrap their live\n * write needs unconditionally, putAtTier's live write never decrypts the\n * prior envelope, so building a snapshot is the ONLY reason it would\n * touch (and potentially throw decrypting) `existing`'s ciphertext. A\n * derivation-free, history-disabled collection must not pay that decode —\n * or risk it, on a corrupted/inaccessible prior body — same law\n * `hasDerivedOutputs` already enforces for the derived-outputs decode.\n */\n readonly historyEnabled: boolean\n /**\n * Rehome a record's blob attachments after a tier move (#724 Arc 10 Task\n * 2, at-rest hardening). A blob's home tier is its owning record's tier —\n * the `_blob` DEK is tier-scoped (`dekKey('_blob', tier)`), so a solo-\n * owned blob's content CEK must move with the record or it stays\n * tier-0-unwrappable at rest even once the live body has moved (the same\n * leak class `syncHistory` closes for `_history` snapshots). Delegates to\n * `BlobSet.rehomeForTier`; no-op fast when the collection has no blob\n * fields. Order-independent with the other sync hooks — touches only\n * blob side structures, not this collection's own cache/indexes.\n */\n syncBlobs(id: string, fromTier: number, toTier: number): Promise<void>\n /**\n * Purge a record's tier-0-era plaintext ledger deltas after a tier move\n * lands it above tier 0 (#729). The audit ledger is a flat, vault-wide,\n * hash-chained log — a record's reverse-JSON-Patch deltas in\n * `_ledger_deltas` are encrypted under one collection-wide `_ledger` DEK\n * (not the record's own tier DEK), so `elevate()`/`putAtTier()`'s live-body\n * rewrap never moves them: they stay readable at rest to any tier-0\n * caller, the same leak class `syncHistory` closed for `_history`\n * snapshots. Chain-safe: `verify()` reads only `_ledger` entry fields\n * (including `deltaHash`, which lives on the entry, not the deleted delta\n * row), never `_ledger_deltas`, so a purge cannot break it.\n * **Irreversible** — the deleted plaintext cannot be restored by\n * `demote()`. **Metadata-retained** — the `_ledger` entries (that the\n * record was mutated, at which version/timestamp/actor) are untouched;\n * only the delta *content* is purged. No-op when the collection has no\n * ledger (`withHistory()` not enabled).\n */\n syncLedger(id: string): Promise<void>\n /**\n * Sync a record's derived outputs (materialized-view rows, rollup\n * contributions, `withDerivation` outputs) after a tier move (#722).\n * Those outputs are computed from this record and written to OTHER\n * (output) collections via a plain `put` at tier 0 — `elevate()`'s live-\n * body rewrap moves only the source envelope, so the output rows keep\n * holding the source's tier-0-era plaintext, the same leak class\n * `syncSearch`/`syncIndexes` closed for THIS collection's own artifacts.\n * `elevated` (landing tier > 0) reuses the SAME onDelete fanout\n * `forgetDerivedFanout` drives for `forget()` (`kernel/via/dispatch.ts`)\n * — minus its `'ref'` cascade edge (elevate/demote never erase a\n * *different* record) — to recompute this record's derived outputs as\n * REMOVED. Recompute is tier-safe: the fanout's own source scan reads\n * the elevated-excluding cache (#701/#709/#712), so it naturally omits\n * the now-elevated record instead of re-embedding its plaintext. Landing\n * back at tier 0 (`elevated === false`) is the reverse (Task 2, #722):\n * `syncDerivedOutputs` instead runs the ordinary local-write add-\n * dispatchers (`dispatchDerivations`/`dispatchMaterializedViews`, the\n * same ones a plain `put()` fires) to restore the contribution —\n * reversible, since the source's plaintext survives the elevate/demote\n * rewrap round-trip. `record` is the decoded source record (PRE-move on\n * the remove side, POST-move on the add side); only the rollup edge\n * consumes it on remove, both add-dispatchers require it on add; `null`\n * (tombstone/delete-marker) skips both. `version` stamps the add-\n * dispatchers' `_derivedFrom.sourceVersion` metadata — reuses the version\n * the tier op already has, no re-read; unused on the remove side. No-op\n * fast when the collection has no MV/derivation source — each dispatcher\n * below checks its own source before doing any work.\n */\n syncDerived(id: string, record: T | null, elevated: boolean, version?: number): Promise<void>\n /**\n * `true` iff the collection has a materialized-view or derivation source\n * attached. Gates the {@link syncDerived} pre-move decode on the remove\n * direction (`elevate()`, `putAtTier(tier>0)`, `demote()`'s intermediate-\n * tier branch): those sites decode the record SOLELY to feed\n * `syncDerived(..., true)`, whose dispatchers no-op immediately when the\n * collection has no MV/derivation source — so a no-derivation collection\n * would otherwise pay a full record-body decrypt on every tier move for\n * nothing (#722 perf regression). `false` short-circuits the decode to\n * `null`, which `syncDerivedOutputs` already treats safely.\n */\n readonly hasDerivedOutputs: boolean\n /** Emit `_source`/`_sourceTs` provenance fields when a source is supplied. */\n readonly provenance: boolean\n /** Declared tiers, or null when the feature is off. */\n readonly tiers: ReadonlySet<number> | null\n /** Above-tier read visibility mode (`'invisibility'` | `'ghost'`). */\n readonly tierMode: TierMode\n /** Resolve a tier DEK by its `dekKey(name, tier)`. */\n getDEK(key: string): Promise<EnclaveKey>\n /** Fire a cross-tier access event (sink stays collection-resident). */\n emitCrossTierEvent(event: CrossTierAccessEvent): void\n /**\n * Register this record's ref in the encrypted `_subject_index` (#766).\n * `putAtTier`'s raw `ctx.adapter.put` bypasses `Collection.put()`'s write-\n * hook pipeline — the `onAfterWrite` hook that normally does this — so a\n * record whose FIRST persistence is `putAtTier` (sensitive from birth, a\n * documented legitimate use) would otherwise never enter the index and\n * stay unreachable by `vault.forget()`. Idempotent (safe on every call,\n * including a repeat write of an already-registered record) and a no-op\n * when the collection declares no forget-subject field.\n */\n addSubjectRef(id: string, record: T): Promise<void>\n}\n\nexport function assertTiersEnabled<T>(ctx: TiersContext<T>): void {\n if (!ctx.tiers) {\n throw new Error(\n `Collection \"${ctx.name}\": hierarchical tiers are not enabled. ` +\n `Pass { tiers: [0, 1, 2, …] } to vault.collection() to opt in.`,\n )\n }\n}\n\nexport function assertDeclaredTier<T>(ctx: TiersContext<T>, tier: number): void {\n if (tier < 0 || !Number.isInteger(tier)) {\n throw new Error(`Collection \"${ctx.name}\": tier must be a non-negative integer, got ${tier}`)\n }\n if (tier === 0) return\n if (!ctx.tiers || !ctx.tiers.has(tier)) {\n throw new Error(\n `Collection \"${ctx.name}\": tier ${tier} is not declared in { tiers: [...] }`,\n )\n }\n}\n\n/**\n * Tier-composition guard (#724 / Arc 7 of the tier-invisibility campaign).\n *\n * Refuses `tiers` declared together with a derived-artifact feature whose\n * crypto has not yet been made tier-aware — i.e. a feature `elevate()` /\n * `demote()` does not re-key when a record moves tiers, so an elevated\n * record's data for that feature would stay readable at tier 0.\n *\n * **Status:** the original call site (`Collection` constructor, beside\n * `buildUniqueConstraintSet`) was removed by Arc 10 Task 1 (#724) — the\n * `tiers + blobFields` refusal below was superseded by a runtime read gate\n * on `collection.blob(id)` (`with-shape/blobs/blob-set.ts`), so this\n * function currently has no caller. Relocated here from\n * `with-lookup/indexing/unique-constraints.ts` (#733) so the tier-domain\n * guard lives in the tier domain, rather than an indexing file it only\n * borrowed for a grandfathered import specifier.\n *\n * ## #724 verified: `tiers + blobFields` leaks\n *\n * `collection.blob(id)` (`Collection.blob()`) never checks the live\n * record's tier before returning a `BlobSet` handle, and `BlobSet`'s crypto\n * is entirely orthogonal to the tier ladder:\n * - the slot map (`_blob_slots_{collection}/{id}`) is encrypted under the\n * collection's TIER-0 DEK (`getDEK(name)` ≡ `dekKey(name, 0)` — see\n * `dekKey` in `with-party/team/tiers.ts`), never a tier-N DEK;\n * - chunk content (`_blob_index` / `_blob_chunks`) is encrypted under the\n * vault-shared `_blob` DEK (`BLOB_COLLECTION`), which has no tier\n * dimension at all.\n * `elevate()`/`demote()` (this file) rewrap only the live record body,\n * `_history` snapshots (#712), and index side-cars — blob slots and chunks\n * are untouched. A caller whose keyring never held the record's elevated\n * tier DEK can still open `collection.blob(id).get(slot)` and read full\n * plaintext, even though `collection.get(id)` correctly reports the same\n * record as invisible. See `__tests__/tier-composition-guard.test.ts` for\n * the reproduction.\n *\n * ## Safe today — no check needed here\n *\n * Field indexes, search (`textIndexes`/`embeddings`), `withHistory`\n * (snapshot keys are rewrapped by `elevate()`/`demote()`, #712), and the\n * decoded-record cache (evicted on tier moves) are already tier-safe. They\n * are not enumerated below; only known-unsafe features are refused, so any\n * combination not named here passes silently by default.\n *\n * ## Deliberately NOT handled here\n *\n * - `ledger` — `withHistory` threads a ledger writer onto every tiered\n * collection by default in shipped usage; refusing `tiers + ledger` would\n * break that default. A ledger-specific tier-rekey handler (rewrap ledger\n * entries, or scope ledger visibility to tier) is a separate arc.\n * - materialized-view (MV) output — an MV's fanout spec lives on the\n * SOURCE collection, not on the MV collection's own config, so it is not\n * visible at `vault.collection()` registration time and this guard cannot\n * detect it structurally.\n */\nexport function assertTierComposition(\n collectionName: string,\n cfg: { readonly tiers: boolean; readonly blobFields: BlobFieldsConfig | undefined },\n): void {\n if (!cfg.tiers) return\n if (cfg.blobFields !== undefined && Object.keys(cfg.blobFields).length > 0) {\n throw new UnsupportedTierCompositionError(\n 'blobs',\n `Collection \"${collectionName}\": blobFields are not supported together with tiers (#724) — ` +\n `blob chunk content is encrypted under a vault-shared DEK that elevate()/demote() do not ` +\n `re-key, so an elevated record's blob attachments would remain readable at tier 0. Use a ` +\n `non-tiered collection for blob-bearing records until a blob tier-rekey handler ships.`,\n )\n }\n}\n\nfunction isElevatorOrOwner<T>(ctx: TiersContext<T>): boolean {\n return ctx.keyring.role === 'owner' || ctx.keyring.role === 'admin'\n}\n\n/**\n * Structural surface {@link syncDerivedOutputs} needs from the owning\n * collection — the SAME onDelete dispatchers `forgetDerivedFanout` already\n * drives (`kernel/via/dispatch.ts`), reused as-is (#722). Narrower than\n * `Collection<T>` (avoids an import cycle: collection.ts constructs\n * `TiersContext` FROM `this`, which already satisfies this shape\n * structurally — no cast needed at the `tiersContext()` call site).\n */\nexport interface DerivedOutputsHost<T> {\n dispatchMaterializedViewsOnDelete(id: string): Promise<{ deleted: number; residueUndecodable: string[]; residueDeclined: string[] }>\n dispatchArrayDerivationsOnDelete(id: string, eraseRecordShapeToo?: boolean): Promise<number>\n dispatchRollupsOnDelete(id: string, deleted: T): Promise<unknown>\n /** Task 2 (#722) add-direction: the same local-write dispatchers an ordinary `put()` fires. */\n dispatchMaterializedViews(id: string, record: T): Promise<void>\n dispatchDerivations(id: string, record: T, version: number): Promise<void>\n}\n\n/**\n * The {@link TiersContext.syncDerived} implementation collection.ts's\n * `tiersContext()` binds `this` into (#722). See that field's doc comment\n * for the design; `host` is `this` — every dispatcher below is a public\n * Collection method, already self-guarding (no-op) when the collection has\n * no materialized-view/derivation source, so no separate fast-path check is\n * needed here.\n */\nexport async function syncDerivedOutputs<T>(\n host: DerivedOutputsHost<T>,\n id: string,\n record: T | null,\n elevated: boolean,\n version?: number,\n): Promise<void> {\n if (elevated) {\n await host.dispatchMaterializedViewsOnDelete(id)\n await host.dispatchArrayDerivationsOnDelete(id, true)\n if (record !== null) await host.dispatchRollupsOnDelete(id, record)\n return\n }\n // Task 2 (#722) recompute-as-add: the record rejoined tier 0 (demote(→0) /\n // putAtTier(0)) — restore its contribution via the SAME local-write\n // dispatchers an ordinary `put()` fires (`collection.ts`'s\n // `_onRecordMutated('local-write')`), same order. `record === null` only\n // when a caller demotes a tombstone/delete-marker to 0 — nothing to add.\n if (record === null) return\n await host.dispatchDerivations(id, record, version ?? 0)\n await host.dispatchMaterializedViews(id, record)\n}\n\n/**\n * tier-aware put. Encrypts the record with the collection's tier-N DEK and\n * stamps `_tier: N` on the envelope. The caller's keyring must hold the tier-N\n * DEK (directly, by delegation, or by virtue of being the grantor); otherwise\n * throws `TierNotGrantedError`.\n *\n * accepts an optional `elevation` context. When present, the emitted cross-tier\n * event is stamped with `authorization: 'elevation'`, the elevation's reason,\n * and the caller's pre-elevation tier. `vault.elevate(...).collection().put`\n * threads this through; direct `putAtTier` calls leave it undefined and fall\n * back to the inherent-write event shape.\n */\nexport async function putAtTier<T>(\n ctx: TiersContext<T>,\n id: string,\n record: T,\n tier: number,\n opts?: { elevation?: { reason: string; fromTier: number }; source?: string; sourceTs?: string },\n): Promise<TierMoveResult> {\n assertTiersEnabled(ctx)\n assertDeclaredTier(ctx, tier)\n assertTierAccess(ctx.keyring, ctx.name, tier)\n\n const key = dekKey(ctx.name, tier)\n const dek = await ctx.getDEK(key)\n\n const existing = await ctx.adapter.get(ctx.vault, ctx.name, id)\n // #712/whole-branch-fix-1: putAtTier moves the record OFF its existing\n // tier, same as elevate/demote — so the caller must also be cleared for\n // that existing tier, not just the target. Without this, a member holding\n // only the target tier's DEK could putAtTier over a record parked at a\n // tier they've never been granted; the from-tier `getDEK` below would then\n // silently MINT a fresh DEK for that tier into their keyring (a\n // non-cleared caller creating tier key material inside the trust\n // boundary — see `assertTierAccess`'s doc comment). Gate here, BEFORE any\n // from-tier getDEK/syncHistory call, so the mint never happens; owner/\n // admin/custodian still bypass (they may mint, same as elevate/demote).\n assertTierAccess(ctx.keyring, ctx.name, existing?._tier ?? 0)\n const version = existing ? existing._v + 1 : 1\n\n // #728 review-fix-2: resolve the from-tier DEK and snapshot the PRE-move\n // version BEFORE the live write below, mirroring elevate()/demote() —\n // originally this ran much later (after syncIndexes/syncLedger/syncDerived/\n // syncSearchResilient), so a throw in any of those left the live record\n // already moved with `existing` — its sole pre-move copy — gone, silently\n // losing the version #728 exists to preserve. `fromKey`/`fromDek` are\n // resolved ONCE here and reused below for the trailing syncHistory rewrap.\n const fromKey = dekKey(ctx.name, existing?._tier ?? 0)\n const fromDek = await ctx.getDEK(fromKey)\n if (existing && ctx.historyEnabled) {\n // Snapshot the version this write is about to overwrite — same law as\n // Collection.put()'s history save, extended to putAtTier (#728). Rewrap\n // under the DESTINATION `dek` via `applyRewrappedBody` (never\n // `ctx.codec.encryptRecord`, which always resolves the tier-0 DEK and\n // would leak this body at rest whenever the prior tier was > 0).\n // Untagged (`_tier`/`_elevatedBy` stripped), same as elevate()/\n // demote()'s snapshots. `_v` stays the PRE-move version (unbumped), as\n // `preCarried` already carries it.\n const body = await rewrapBodyToDek(existing, fromDek, dek)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from ...preCarried\n const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...preCarried } = existing\n await ctx.saveHistorySnapshot(id, applyRewrappedBody(preCarried, body))\n }\n\n const json = JSON.stringify(record)\n const { iv, data } = await encrypt(json, dek)\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: ctx.keyring.userId,\n ...(tier > 0 && { _tier: tier }),\n ...(ctx.provenance && opts?.source !== undefined ? { _source: opts.source, _sourceTs: opts.sourceTs ?? new Date().toISOString() } : {}),\n }\n\n await ctx.adapter.put(ctx.vault, ctx.name, id, envelope)\n\n // #766: putAtTier bypasses Collection.put()'s onAfterWrite hook — register\n // the subject ref directly so a sensitive-from-birth first write stays\n // reachable by vault.forget(). No-op when no forget-subject field is declared.\n await ctx.addSubjectRef(id, record)\n\n // #702/#709: keep the record cache AND indexes coherent with the raw\n // write — same law as elevate/demote (#691): tier > 0 → invisible on\n // tier-0 surfaces (evict + purge); tier 0 → an ordinary write (re-seed +\n // reindex). syncIndexes runs first — it needs the pre-write cache entry\n // as \"previous\", which syncCache is about to overwrite/evict.\n if (tier > 0) {\n await ctx.syncIndexes(id, null, version)\n ctx.syncCache(id, null)\n // #729: the record lands above tier 0 — purge its tier-0-era plaintext\n // ledger deltas (irreversible; a tier-0 putAtTier(0) has nothing to purge).\n await ctx.syncLedger(id)\n // #722: recompute this record's derived outputs now that it left tier 0\n // — same law as elevate() below. `existing` is the PRE-write envelope;\n // decode it only here (the rollup edge is the only consumer). Gated by\n // `hasDerivedOutputs` — no-derivation collections skip this decrypt\n // entirely (perf regression review finding, Arc 9 #722). Whole-branch\n // review: `existing` may sit at ANY prior tier (including a\n // `putAtTier`-origin tier>0 record with no `_cek` ever minted) — decode\n // it under ITS OWN tier's DEK via `codec.decryptRecordAtDek`, not\n // `ctx.codec.decryptRecord`'s tier-unaware default, which threw\n // `TamperedError` here.\n await ctx.syncDerived(\n id,\n ctx.hasDerivedOutputs && existing\n ? await ctx.codec.decryptRecordAtDek(existing, await ctx.getDEK(dekKey(ctx.name, existing._tier ?? 0)), id)\n : null,\n true,\n )\n } else {\n const rec = await ctx.codec.decryptRecord(envelope, { id, sealedAsHandles: true })\n await ctx.syncIndexes(id, rec, envelope._v, existing ?? undefined)\n ctx.syncCache(id, rec !== null ? { record: rec, version: envelope._v } : null)\n // #722 Task 2: the record is written at tier 0 — restore its\n // contribution to every derived output (reuse the decode above, no\n // double-decrypt).\n await ctx.syncDerived(id, rec, false, envelope._v)\n }\n // #721: search artifacts follow the same tier > 0 → purge / tier 0 →\n // re-embed law as syncIndexes above. No ordering dependency on syncCache —\n // the _ftindex rebuild is deferred to the next retrieve() and _vec re-embed\n // reads `record` directly, not the cache. #774: a permanently stuck\n // compensation must not abort the write — the record put above already\n // landed — so it is caught and surfaced as residue instead, same as\n // elevate()/demote() (#764).\n const searchResidue = await syncSearchResilient(ctx, id, tier > 0 ? null : record, envelope._v)\n\n if (tier > 0) {\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier,\n authorization: opts?.elevation ? 'elevation' : 'inherent',\n op: 'put',\n ts: envelope._ts,\n ...(opts?.elevation && {\n reason: opts.elevation.reason,\n elevatedFrom: opts.elevation.fromTier,\n }),\n })\n }\n\n // #712/whole-branch-fix-2: syncHistory runs LAST — after syncIndexes/\n // syncCache/syncSearch (and the cross-tier emit) have already landed. Its\n // own throw (adapter I/O, the Fix-1 assertion, the rewrap) then strands\n // only the `_history` artifact instead of leaving indexes/cache/search\n // unsynced behind an already-moved live record (would conditionally\n // reopen #709/#721/#723 on the error path). A direct putAtTier also moves\n // the record's tier — its history snapshots (if any exist from an earlier\n // tier-0 put()) must follow, same as elevate/demote. Skip when the record\n // was already at this tier (no move, nothing to rewrap). `fromKey`/\n // `fromDek` were already resolved above (review-fix-2), before the live\n // write, for the pre-move snapshot — reused here rather than recomputed.\n if (fromKey !== key) {\n await ctx.syncHistory(id, fromDek, dek)\n // #724: this putAtTier moved the record's tier — rehome its blobs too.\n await ctx.syncBlobs(id, existing?._tier ?? 0, tier)\n }\n return { searchResidue }\n}\n\n/**\n * tier-aware get. When the stored record is at a tier the caller cannot\n * decrypt:\n * - `'invisibility'` mode (default) → returns `null`.\n * - `'ghost'` mode → returns a `GhostRecord` placeholder with the tier and\n * the record id (the record exists but contents are withheld).\n *\n * Fully-cleared reads return the plaintext record and fire a cross-tier event\n * when `_tier > 0`.\n */\nexport async function getAtTier<T>(ctx: TiersContext<T>, id: string): Promise<T | GhostRecord | null> {\n assertTiersEnabled(ctx)\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope) return null\n const tier = envelope._tier ?? 0\n if (tier === 0) {\n return ctx.codec.decryptRecord(envelope, { id })\n }\n\n const key = dekKey(ctx.name, tier)\n if (!ctx.keyring.deks.has(key)) {\n if (ctx.tierMode === 'ghost') {\n return { _ghost: true, _tier: tier } as GhostRecord\n }\n return null\n }\n\n const dek = await ctx.getDEK(key)\n // A tiered record may carry a per-record CEK (e.g. a CEK record\n // elevated via `elevate()`): the CEK is wrapped under the TIER DEK, so\n // unwrap under the tier DEK then decrypt the body under the CEK. Legacy\n // tiered records decrypt directly under the tier DEK.\n let plaintext: string\n let cek: EnclaveKey | undefined\n if (envelope._cek !== undefined) {\n cek = await unwrapCek(envelope._cek, dek)\n ctx.cekCache?.set(id, cek, 1)\n plaintext = await decrypt(envelope._iv, envelope._data, cek)\n } else {\n plaintext = await decrypt(envelope._iv, envelope._data, dek)\n }\n let record = JSON.parse(plaintext) as T\n\n // #635: this manual-decrypt leg never went through `decryptRecord`, so it\n // never processed `_sealed` slots — an elevated classified record read\n // back through here would return the body WITHOUT its sealed fields\n // (silent omission). `applySealedSlots` is the same post-processing\n // `decryptRecord`'s tier-0 branch above gets for free, extracted so it can\n // take OUR already-unwrapped `cek` (unwrapped under the TIER dek above)\n // instead of resolving one itself under the collection DEK the way\n // `decryptRecord`/`resolveEnvelopeCek` would — which would be the wrong\n // key for a `_cek` wrapped under a tier DEK. `sealedAsHandles` is omitted\n // (default false) to match this function's OWN tier-0 branch above\n // (`ctx.codec.decryptRecord(envelope, { id })`, no `sealedAsHandles`) —\n // both tiers return sealed fields inline-decrypted, not as handles.\n if (envelope._sealed !== undefined) {\n record = await ctx.codec.applySealedSlots(record, envelope._sealed, cek, { id })\n }\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier,\n authorization: isElevatorOrOwner(ctx) ? 'inherent' : 'delegation',\n op: 'get',\n ts: new Date().toISOString(),\n })\n\n return record\n}\n\n/**\n * list ids grouped by the caller's readability. Returns only ids whose tier the\n * caller can read. Above-tier ids are omitted in `'invisibility'` mode and\n * included (with tier metadata) in `'ghost'` mode.\n */\nexport async function listAtTier<T>(ctx: TiersContext<T>): Promise<Array<{ id: string; tier: number; readable: boolean }>> {\n assertTiersEnabled(ctx)\n const ids = await ctx.adapter.list(ctx.vault, ctx.name)\n const out: Array<{ id: string; tier: number; readable: boolean }> = []\n for (const id of ids) {\n const env = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!env) continue\n const tier = env._tier ?? 0\n const readable = tier === 0 || ctx.keyring.deks.has(dekKey(ctx.name, tier))\n if (!readable && ctx.tierMode === 'invisibility') continue\n out.push({ id, tier, readable })\n }\n return out\n}\n\n/**\n * Result of a completed `elevate()`/`demote()` (#764). The record move\n * (put + indexes/cache/ledger/derived/history/blobs sync) always completes —\n * `searchResidue: true` means only the search-index side (`_vec`/`_ftindex`)\n * was left in a needs-retry state (a stuck `PersistedIndexCompensationError`),\n * mirroring `forget()`'s `indexResidue` posture rather than aborting the move.\n */\nexport interface TierMoveResult {\n readonly searchResidue: boolean\n}\n\n/**\n * Run {@link TiersContext.syncSearch} with #764 resilience: a permanently\n * stuck compensation (`PersistedIndexCompensationError`, thrown by\n * `PersistedIndexStore` when its compensating `remove()` of a stale\n * `_ftindex` blob keeps failing) must not abort `elevate()`/`demote()` — the\n * record's tier-move `put` has already landed, and aborting here would leave\n * `syncLedger`/`syncDerived`/`syncHistory`/`syncBlobs` unsynced behind it (the\n * partial-completion hazard #764 names). Same posture `forget()` takes for\n * `_purgeSearchIndex` (`vault.ts`'s `indexResidue`) — everything else (a\n * genuinely unexpected search-hook error) still aborts, undisturbed.\n */\nasync function syncSearchResilient<T>(ctx: TiersContext<T>, id: string, record: T | null, version?: number): Promise<boolean> {\n try {\n await ctx.syncSearch(id, record, version)\n return false\n } catch (e) {\n if (e instanceof PersistedIndexCompensationError) return true\n throw e\n }\n}\n\n/**\n * elevate a record to a higher tier. Re-encrypts with the target tier's DEK.\n * The caller must hold DEKs for both the current tier (to decrypt) and the\n * target tier (to re-encrypt). Stamps `_elevatedBy` with the caller id so\n * `demote()` can check the reverse operation.\n */\nexport async function elevate<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult> {\n assertTiersEnabled(ctx)\n assertDeclaredTier(ctx, toTier)\n assertTierAccess(ctx.keyring, ctx.name, toTier)\n\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope || isDeleteMarker(envelope) || isTombstoneShape(envelope)) {\n throw new Error(`Record \"${id}\" not found in collection \"${ctx.name}\"`)\n }\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return { searchResidue: false }\n if (toTier < fromTier) {\n throw new Error(`Use demote() to lower the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n // Caller must have access at the existing tier to decrypt.\n if (fromTier > 0) assertTierAccess(ctx.keyring, ctx.name, fromTier)\n\n const fromKey = dekKey(ctx.name, fromTier)\n const toKey = dekKey(ctx.name, toTier)\n const fromDek = await ctx.getDEK(fromKey)\n const toDek = await ctx.getDEK(toKey)\n\n // Per-record CEK composes with tiers: the body key is unchanged (history\n // chain identity preserved); only the wrapping key moves with the tier.\n // Legacy (no `_cek`) records take the direct-DEK path unchanged.\n const now = new Date().toISOString()\n const body = await rewrapBodyToDek(envelope, fromDek, toDek)\n if (body.cek) ctx.cekCache?.set(id, body.cek, 1)\n // #728: snapshot the PRE-move version into `_history` before it's\n // overwritten below — reuses `body` (already fromDek→toDek) via\n // `applyRewrappedBody` (never `ctx.codec.encryptRecord`, which always\n // resolves the tier-0 DEK and would leak this body at rest whenever\n // `fromTier > 0`). Untagged (`_tier`/`_elevatedBy` stripped) so the\n // history read-gate doesn't hide it PERMANENTLY once the record demotes\n // back — at-rest protection comes from the ciphertext being under\n // `toDek`, not from the tag. `_v` stays the PRE-move version (unbumped),\n // exactly as `envelope` already carries it.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from ...snapshot\n const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...snapshot } = applyRewrappedBody(envelope, body)\n await ctx.saveHistorySnapshot(id, snapshot)\n // #662: spread every slot the source carries (_sealed/_det/_vdig/_bidx/\n // _source/_sourceTs/_debug) through UNCHANGED, then override only\n // the fields a tier move manages. rewrapBodyToDek preserves the CEK, so no\n // passenger slot is re-keyed by the move. The old field-literal dropped every\n // unlisted slot.\n const next: EncryptedEnvelope = {\n ...envelope,\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: body._iv,\n _data: body._data,\n _by: ctx.keyring.userId,\n _tier: toTier,\n _elevatedBy: ctx.keyring.userId,\n ...(body._cek !== undefined ? { _cek: body._cek } : {}),\n }\n await ctx.adapter.put(ctx.vault, ctx.name, id, next)\n // Evict/purge only once the write landed — a throwing put must not blind\n // the eager cache or leave a readable index behind for a still-valid\n // tier-0 record (same ordering as demote). #709: syncIndexes runs first —\n // it needs the pre-elevation cache entry as \"previous\" to clean the\n // eager index bucket; the persisted side-car purge is content-free.\n await ctx.syncIndexes(id, null, next._v)\n ctx.syncCache(id, null)\n // #721: same purge law as syncIndexes above — the record left tier 0, so\n // its _vec sidecar is purged and _ftindex is invalidated. #764: a\n // permanently stuck compensation must not abort the move — the record put\n // above already landed — so it is caught and surfaced as residue instead.\n const searchResidue = await syncSearchResilient(ctx, id, null)\n // #729: elevate always lands the record at tier > 0 — purge its\n // tier-0-era plaintext ledger deltas (irreversible; entry metadata stays).\n await ctx.syncLedger(id)\n // #722: recompute this record's derived outputs now that it left tier 0 —\n // same law as putAtTier(tier>0) above. `envelope` is the PRE-move envelope\n // (captured before the rewrap), decoded only here (the rollup edge is the\n // only consumer of the record content). Gated by `hasDerivedOutputs` — no-\n // derivation collections skip this decrypt entirely (perf regression\n // review finding, Arc 9 #722). Whole-branch review: decode under `fromDek`\n // via `codec.decryptRecordAtDek` — `fromTier` may be > 0 here (a prior\n // elevate), and `ctx.codec.decryptRecord`'s tier-unaware CEK resolution\n // threw `TamperedError` whenever this op's own rewrap hadn't just primed\n // the cekCache (non-`perRecordKeys` collections; `_cek`-absent bodies).\n await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek(envelope, fromDek, id) : null, true)\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier: toTier,\n authorization: 'elevation',\n op: 'elevate',\n ts: now,\n })\n\n // #712/whole-branch-fix-2: syncHistory runs LAST — after syncIndexes/\n // syncCache/syncSearch/the cross-tier emit have already landed, so its own\n // throw (adapter I/O, the rewrap) strands only the `_history` artifact\n // instead of leaving indexes/cache/search unsynced behind an already-moved\n // live record (would conditionally reopen #709/#721/#723 on the error\n // path). Rewraps prior-version history snapshots to the SAME toTier DEK —\n // otherwise they stay decryptable at rest under fromDek forever.\n await ctx.syncHistory(id, fromDek, toDek)\n // #724: rehome any solo-owned blob attachments to the target tier's\n // `_blob` DEK — same at-rest law as syncHistory above.\n await ctx.syncBlobs(id, fromTier, toTier)\n return { searchResidue }\n}\n\n/**\n * demote a record to a lower tier. Allowed only for the user who performed the\n * last elevation or an owner.\n */\nexport async function demote<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult> {\n assertTiersEnabled(ctx)\n if (toTier < 0) throw new Error(`Cannot demote to negative tier ${toTier}`)\n\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope || isDeleteMarker(envelope) || isTombstoneShape(envelope)) {\n throw new Error(`Record \"${id}\" not found in collection \"${ctx.name}\"`)\n }\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return { searchResidue: false }\n if (toTier > fromTier) {\n throw new Error(`Use elevate() to raise the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n const isOwner = ctx.keyring.role === 'owner'\n const isOriginalElevator = envelope._elevatedBy === ctx.keyring.userId\n if (!isOwner && !isOriginalElevator) {\n throw new TierDemoteDeniedError(id, fromTier)\n }\n // Caller must still hold the DEK of the current tier to decrypt.\n assertTierAccess(ctx.keyring, ctx.name, fromTier)\n if (toTier > 0) assertDeclaredTier(ctx, toTier)\n\n const fromDek = await ctx.getDEK(dekKey(ctx.name, fromTier))\n const toDek = await ctx.getDEK(dekKey(ctx.name, toTier))\n\n // CEK re-wrap on demote — same body key, moved from the source tier\n // DEK to the target tier DEK. Legacy records take the direct-DEK path.\n const now = new Date().toISOString()\n const body = await rewrapBodyToDek(envelope, fromDek, toDek)\n if (body.cek) ctx.cekCache?.set(id, body.cek, 1)\n // #662: same passenger carry-through as elevate(). demote additionally CLEARS\n // `_elevatedBy` (the demote right is consumed) and OMITS `_tier` at tier 0, so\n // both are destructured out of the spread rather than carried.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from ...carried\n const { _elevatedBy, _tier: _priorTier, ...carried } = envelope\n // #728: snapshot the PRE-move version into `_history` — reuses `body`\n // (already fromDek→toDek) and `carried` (envelope minus `_tier`/\n // `_elevatedBy`, computed above for the live write) via\n // `applyRewrappedBody`, so a tier-0-landing demote's snapshot is untagged\n // just like an ordinary put() entry, same law as elevate()'s snapshot\n // above. `_v` stays the PRE-move version (unbumped), as `carried` already\n // carries it.\n await ctx.saveHistorySnapshot(id, applyRewrappedBody(carried, body))\n const next: EncryptedEnvelope = {\n ...carried,\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: body._iv,\n _data: body._data,\n _by: ctx.keyring.userId,\n ...(toTier > 0 ? { _tier: toTier } : {}),\n ...(body._cek !== undefined ? { _cek: body._cek } : {}),\n }\n await ctx.adapter.put(ctx.vault, ctx.name, id, next)\n\n // #691/#709: landing back at tier 0 makes this a plain tier-0 record\n // again — it must stay plain-get()-readable AND indexed in the same\n // session, so re-seed the eager cache and rebuild its index entries from\n // the just-written (now tier-0-keyed) envelope through the canonical\n // codec path, the same decode collection.ts's own eager hydration / lazy\n // cache-miss paths use — reused for both, no double-decrypt. A demote\n // that lands on an intermediate elevated tier (toTier > 0) still evicts\n // + purges: the record stays above tier 0 and must remain invisible on\n // tier-0 surfaces, unindexed. syncIndexes runs first in both branches —\n // it needs the pre-demote cache entry as \"previous\".\n // #764: a permanently stuck search-index compensation must not abort the\n // move — the record put above already landed — so it is caught in both\n // branches and surfaced as residue on the return value instead.\n let searchResidue: boolean\n if (toTier === 0) {\n const rec = await ctx.codec.decryptRecord(next, { id, sealedAsHandles: true })\n await ctx.syncIndexes(id, rec, next._v, envelope)\n ctx.syncCache(id, rec !== null ? { record: rec, version: next._v } : null)\n // #721: reuse the decode above — no double-decrypt. The record is tier-0\n // again, so re-embed its _vec and invalidate _ftindex to include it.\n searchResidue = await syncSearchResilient(ctx, id, rec, next._v)\n // #722 Task 2: the record rejoined tier 0 — restore its contribution to\n // every derived output (reuse the decode above, no double-decrypt).\n await ctx.syncDerived(id, rec, false, next._v)\n } else {\n await ctx.syncIndexes(id, null, next._v)\n ctx.syncCache(id, null)\n // #721: still above tier 0 — purge _vec, invalidate _ftindex.\n searchResidue = await syncSearchResilient(ctx, id, null)\n // #722 Task 2: still elevated (an intermediate tier) — the source's\n // derived outputs stay recompute-as-removed, same law as elevate()\n // above. `envelope` is the PRE-move envelope captured at function\n // entry; decoded only here (the rollup edge is the only consumer).\n // Gated by `hasDerivedOutputs` — no-derivation collections skip this\n // decrypt entirely (perf regression review finding, Arc 9 #722).\n // Whole-branch review: decode under `fromDek` via\n // `codec.decryptRecordAtDek` — this branch's `fromTier` is always > 0 (a\n // demote FROM tier `fromTier` TO an intermediate `toTier` > 0), so\n // `ctx.codec.decryptRecord`'s tier-unaware CEK resolution threw\n // `TamperedError` here exactly as it did in elevate() above\n // (code-identical bug, same fix).\n await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek(envelope, fromDek, id) : null, true)\n }\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier: fromTier,\n authorization: 'elevation',\n op: 'demote',\n ts: now,\n })\n\n // #712/whole-branch-fix-2: syncHistory runs LAST — after syncIndexes/\n // syncCache/syncSearch/the cross-tier emit have already landed, so its own\n // throw (adapter I/O, the rewrap) strands only the `_history` artifact\n // instead of leaving indexes/cache/search unsynced behind an already-moved\n // live record (would conditionally reopen #709/#721/#723 on the error\n // path). Rewraps history snapshots the same direction as the live body —\n // demote restores tier-0 (or intermediate-tier) readability at rest.\n await ctx.syncHistory(id, fromDek, toDek)\n // #724: rehome any solo-owned blob attachments the same direction as the\n // live body — restores tier-0 (or intermediate-tier) readability at rest.\n await ctx.syncBlobs(id, fromTier, toTier)\n return { searchResidue }\n}\n\n/**\n * Classify a live envelope's `_sealed` slots for crypto-shred completeness\n * (#M-1). Thin delegate to {@link RecordCodec.classifySealedShred}; surfaced\n * here because `vault.ts` forget() reaches in via `collection._classifySealedShred`.\n */\nexport function classifySealedShred<T>(\n ctx: TiersContext<T>,\n live: EncryptedEnvelope,\n): Promise<{ readonly slots: readonly SealedShredSlot[] }> {\n return ctx.codec.classifySealedShred(live)\n}\n"],"mappings":"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","names":["putAtTier","getAtTier","listAtTier","elevate","demote"]}
|
|
1
|
+
{"version":3,"sources":["../src/with-audit/tiers/active.ts","../src/with-audit/tiers/strategy.ts","../src/with-audit/tiers/index.ts"],"sourcesContent":["/**\n * Enable the hierarchical-tiers capability.\n * Pass to `createNoydb({ tiersStrategy: withTiers() })` to make a collection's\n * `putAtTier` / `getAtTier` / `listAtTier` / `elevate` / `demote` methods live.\n * The tier read/write/re-key engine is dynamically imported here, so it stays\n * out of the floor bundle until opted in.\n */\nimport type { TiersStrategy } from './strategy.js'\n\nexport function withTiers(): TiersStrategy {\n return {\n async putAtTier(ctx, id, record, tier, opts) {\n const { putAtTier } = await import('./index.js')\n return putAtTier(ctx, id, record, tier, opts)\n },\n async getAtTier(ctx, id) {\n const { getAtTier } = await import('./index.js')\n return getAtTier(ctx, id)\n },\n async listAtTier(ctx) {\n const { listAtTier } = await import('./index.js')\n return listAtTier(ctx)\n },\n async elevate(ctx, id, toTier) {\n const { elevate } = await import('./index.js')\n return elevate(ctx, id, toTier)\n },\n async demote(ctx, id, toTier) {\n const { demote } = await import('./index.js')\n return demote(ctx, id, toTier)\n },\n }\n}\n","/**\n * Tiers capability strategy — the five on-demand collection-level tier\n * operations the `Collection` routes through. The active engine\n * ({@link withTiers}) dynamically imports the tier cores (keeping them out of\n * the floor bundle); {@link NO_TIERS} throws. Collection always builds its\n * per-call {@link TiersContext} and delegates here, so an un-opted-in caller\n * hits `NO_TIERS`'s throw.\n * @internal\n */\nimport type { GhostRecord } from '../../kernel/types.js'\nimport type { TiersContext, TierMoveResult } from './index.js'\nimport { TiersNotEnabledError } from '../../kernel/errors.js'\n\n// #779 — re-exported so kernel-spine callers (e.g. `Vault._elevatedPut`) can reference the\n// result shape via this grandfathered strategy.js seam instead of statically importing\n// tiers/index.js directly (the port-layering check only grandfathers this file).\nexport type { TierMoveResult }\n\nexport interface TiersStrategy {\n putAtTier<T>(\n ctx: TiersContext<T>,\n id: string,\n record: T,\n tier: number,\n opts?: { elevation?: { reason: string; fromTier: number }; source?: string; sourceTs?: string },\n ): Promise<TierMoveResult>\n getAtTier<T>(ctx: TiersContext<T>, id: string): Promise<T | GhostRecord | null>\n listAtTier<T>(ctx: TiersContext<T>): Promise<Array<{ id: string; tier: number; readable: boolean }>>\n elevate<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult>\n demote<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult>\n}\n\n/**\n * No-op stub — the floor default. Every capability method throws\n * {@link TiersNotEnabledError}; opt in with `tiersStrategy: withTiers()` in\n * createNoydb. @internal\n */\nexport const NO_TIERS: TiersStrategy = {\n async putAtTier() { throw new TiersNotEnabledError() },\n async getAtTier() { throw new TiersNotEnabledError() },\n async listAtTier() { throw new TiersNotEnabledError() },\n async elevate() { throw new TiersNotEnabledError() },\n async demote() { throw new TiersNotEnabledError() },\n}\n","/**\n * Hierarchical access — the collection-level tier operations\n * (`putAtTier` / `getAtTier` / `listAtTier` / `elevate` / `demote`).\n *\n * A collection opted into `{ tiers: [...] }` stamps `_tier: N` on each envelope\n * and encrypts its body under the tier-N DEK. These helpers are the read/write\n * surface for that scheme: write at a tier, read with tier-aware visibility\n * (invisibility vs. ghost), and move a record up (`elevate`) or down (`demote`)\n * the tier ladder by re-wrapping its body key.\n *\n * Each function takes a small {@link TiersContext} (the exact `this.*` the\n * moving methods touched) instead of `this`, mirroring the `record-keys/`\n * siblings. Behaviour is byte-identical to the inline code it replaced.\n *\n * The crux is `cekCache`: `elevate`/`demote`/`getAtTier` write the SAME `Lru`\n * reference `Collection` owns (never a copy) so a per-record CEK re-wrap stays\n * synchronous with the cache the kernel's write/read path also mutates. The\n * cross-tier event sink stays collection-resident and is reached via the\n * `emitCrossTierEvent` callback.\n *\n * Internal service — not exported as a `@noy-db/hub/*` subpath.\n */\nexport { withTiers } from './active.js'\nexport { NO_TIERS, type TiersStrategy } from './strategy.js'\nexport { TiersNotEnabledError } from '../../kernel/errors.js'\nimport { encrypt, decrypt, unwrapCek, rewrapBodyToDek, applyRewrappedBody, isDeleteMarker, isTombstoneShape, type RecordCodec, type EnclaveKey, type SealedShredSlot } from '../../kernel/enclave/index.js'\nimport { TierDemoteDeniedError, UnsupportedTierCompositionError, PersistedIndexCompensationError } from '../../kernel/errors.js'\nimport { dekKey, assertTierAccess } from '../../with-party/team/tiers.js'\nimport type { UnlockedKeyring } from '../../with-party/team/keyring.js'\nimport type { Lru } from '../../kernel/cache/index.js'\nimport type { BlobFieldsConfig } from '../../with-shape/blobs/blob-compaction.js'\nimport {\n NOYDB_FORMAT_VERSION,\n type NoydbStore,\n type EncryptedEnvelope,\n type GhostRecord,\n type TierMode,\n type CrossTierAccessEvent,\n} from '../../kernel/types.js'\n\n/** Everything the moving tier methods touched on `this.*`, as a flat context. */\nexport interface TiersContext<T> {\n /** Collection name — the crypto AAD scope and tier-DEK key prefix. */\n readonly name: string\n /** Vault namespace the records live under. */\n readonly vault: string\n /** The ciphertext store. */\n readonly adapter: NoydbStore\n /** The caller's unlocked keyring (tier-DEK holdings + role + userId). */\n readonly keyring: UnlockedKeyring\n /** The record codec — decrypts a tier-0 envelope to T. */\n readonly codec: RecordCodec<T>\n /**\n * The collection's per-record CEK cache (SHARED reference, not a copy).\n * `elevate`/`demote`/`getAtTier` re-wrap a record's CEK and `set` it here so\n * the move stays synchronous with the cache the kernel's read/write path\n * owns. `null` → no caching.\n */\n readonly cekCache: Lru<string, EnclaveKey> | null\n /**\n * Sync the collection's decoded-record cache after a tier move rewraps the\n * envelope (#691). The lazy LRU is evicted on EVERY call — it is never\n * re-seeded, since a lazy `#getRaw` miss refetches and decodes the fresh\n * envelope via its adapter fallback (a stale LRU entry after a tier-0\n * `putAtTier` overwrite was the #702 lazy-mode gap). `null` → also evict\n * the eager cache entry; an `entry` → set the eager cache to the given\n * decoded record/version (used by `demote()` landing back at tier 0 and\n * `putAtTier`'s tier-0 write, #702 — the record is tier-0, so it must stay\n * plain-`get()`-readable in-session).\n */\n syncCache(id: string, entry: { record: T; version: number } | null): void\n /**\n * Sync the collection's indexes after a tier move rewraps the envelope\n * (#709). Persisted `_idx/<field>/<recordId>` side-cars hold the indexed\n * field's PLAINTEXT value and are always encrypted under the tier-0 DEK,\n * whatever the record's own tier — the same leak class `forget()` fixed\n * via `purgePersistedIndexes` (\"forget() crypto-shreds the body but keeps\n * the collection DEK, under which these side-cars are encrypted — so\n * without this they leave the indexed field VALUES readable\"). `null` →\n * the record just left tier 0: purge its persisted side-cars and drop its\n * in-memory index entries. A record → it is tier-0 again: (re)build its\n * entries from that record. Call this BEFORE {@link syncCache} — the\n * implementation reads the pre-write cached value as \"previous\" to clean\n * up stale index buckets, so it must run while that entry is still live.\n * `version` stamps a rebuilt side-car's own envelope version — every\n * caller has one in scope (the envelope it just wrote), including the\n * purge (`null`) branches, which pass it along even though it's ignored\n * there (#720: keeps the parameter honestly required, no dead default).\n * `priorEnvelope` (#720) — the RAW envelope read BEFORE this call's own\n * overwrite (already in scope as `existing`/`envelope` at every record-\n * branch call site), so a lazy same-tier value change (a `putAtTier(0)`\n * dropping an indexed field, a `demote(0)` off an elevated record) can\n * still be tier-gate-decoded as \"previous\" once the live envelope itself\n * has already moved past it. The `null`-record branches never pass one —\n * see the implementation's doc comment for why they don't need to.\n */\n syncIndexes(id: string, record: T | null, version: number, priorEnvelope?: EncryptedEnvelope): Promise<void>\n /** Sync the collection's SEARCH artifacts after a tier move (#721). Both the\n * lexical `_ftindex` blob and the `_vec/<id>` embedding are encrypted under\n * the tier-0 DEK and hold the record's derived plaintext (full field text /\n * a text-invertible vector), so leaving them means elevation never hid what\n * the record was searchable by — the `forget()` precedent, unapplied to\n * elevate. `null` → the record left tier 0: purge its `_vec`, and\n * invalidate the `_ftindex` blob (the cache-driven rebuild then excludes\n * it). A record → it is tier-0 again: re-embed it, and invalidate\n * `_ftindex` (rebuild includes it). No-op fast when the collection has no\n * search. */\n syncSearch(id: string, record: T | null, version?: number): Promise<void>\n /**\n * Sync a record's `_history` snapshots after a tier move rewraps the live\n * envelope (#712, at-rest hardening). Each snapshot carries its own key\n * material (a direct DEK wrap, or a per-record `_cek` itself wrapped under\n * the tier DEK) — `elevate()`'s live-body rewrap moves only the live\n * envelope, leaving prior versions decryptable at rest under the tier the\n * record has moved away from (the same leak class `forget()`'s\n * `tombstoneHistory` closed for erasure). `fromDek`/`toDek` are the SAME\n * DEKs the caller already resolved for the live rewrap — reuse them, don't\n * recompute. No-op when the strategy is `NO_HISTORY`.\n */\n syncHistory(id: string, fromDek: EnclaveKey, toDek: EnclaveKey): Promise<void>\n /**\n * Save a raw pre-move envelope as a `_history` snapshot (#728). Tier moves\n * (`putAtTier`/`elevate`/`demote`) bump `_v` and overwrite the live\n * envelope without ever snapshotting the version that existed just before\n * the move — so `history()` silently lost it. The caller (`tiers/index.ts`)\n * builds `envelope` by reusing the SAME `rewrapBodyToDek(envelope, fromDek,\n * toDek)` result it already computed for the live write, so the snapshot\n * lands wrapped under the DESTINATION tier's DEK — never `ctx.codec.\n * encryptRecord`, which always resolves the tier-0 DEK and would leak the\n * pre-move body whenever `fromTier > 0`. No-op when history is disabled\n * (folds the `historyConfig.enabled` gate so `tiers/index.ts` stays simple).\n */\n saveHistorySnapshot(id: string, envelope: EncryptedEnvelope): Promise<void>\n /**\n * `true` iff a real history strategy is wired (not `NO_HISTORY`) AND not\n * explicitly disabled via `historyConfig.enabled`. `putAtTier` checks this\n * BEFORE decrypting `existing` to build a snapshot (#728, #737 regression\n * fix) — unlike elevate/demote, which reuse a `body` rewrap their live\n * write needs unconditionally, putAtTier's live write never decrypts the\n * prior envelope, so building a snapshot is the ONLY reason it would\n * touch (and potentially throw decrypting) `existing`'s ciphertext. A\n * derivation-free, history-disabled collection must not pay that decode —\n * or risk it, on a corrupted/inaccessible prior body — same law\n * `hasDerivedOutputs` already enforces for the derived-outputs decode.\n */\n readonly historyEnabled: boolean\n /**\n * Rehome a record's blob attachments after a tier move (#724 Arc 10 Task\n * 2, at-rest hardening). A blob's home tier is its owning record's tier —\n * the `_blob` DEK is tier-scoped (`dekKey('_blob', tier)`), so a solo-\n * owned blob's content CEK must move with the record or it stays\n * tier-0-unwrappable at rest even once the live body has moved (the same\n * leak class `syncHistory` closes for `_history` snapshots). Delegates to\n * `BlobSet.rehomeForTier`; no-op fast when the collection has no blob\n * fields. Order-independent with the other sync hooks — touches only\n * blob side structures, not this collection's own cache/indexes.\n */\n syncBlobs(id: string, fromTier: number, toTier: number): Promise<void>\n /**\n * Purge a record's tier-0-era plaintext ledger deltas after a tier move\n * lands it above tier 0 (#729). The audit ledger is a flat, vault-wide,\n * hash-chained log — a record's reverse-JSON-Patch deltas in\n * `_ledger_deltas` are encrypted under one collection-wide `_ledger` DEK\n * (not the record's own tier DEK), so `elevate()`/`putAtTier()`'s live-body\n * rewrap never moves them: they stay readable at rest to any tier-0\n * caller, the same leak class `syncHistory` closed for `_history`\n * snapshots. Chain-safe: `verify()` reads only `_ledger` entry fields\n * (including `deltaHash`, which lives on the entry, not the deleted delta\n * row), never `_ledger_deltas`, so a purge cannot break it.\n * **Irreversible** — the deleted plaintext cannot be restored by\n * `demote()`. **Metadata-retained** — the `_ledger` entries (that the\n * record was mutated, at which version/timestamp/actor) are untouched;\n * only the delta *content* is purged. No-op when the collection has no\n * ledger (`withHistory()` not enabled).\n */\n syncLedger(id: string): Promise<void>\n /**\n * Sync a record's derived outputs (materialized-view rows, rollup\n * contributions, `withDerivation` outputs) after a tier move (#722).\n * Those outputs are computed from this record and written to OTHER\n * (output) collections via a plain `put` at tier 0 — `elevate()`'s live-\n * body rewrap moves only the source envelope, so the output rows keep\n * holding the source's tier-0-era plaintext, the same leak class\n * `syncSearch`/`syncIndexes` closed for THIS collection's own artifacts.\n * `elevated` (landing tier > 0) reuses the SAME onDelete fanout\n * `forgetDerivedFanout` drives for `forget()` (`kernel/via/dispatch.ts`)\n * — minus its `'ref'` cascade edge (elevate/demote never erase a\n * *different* record) — to recompute this record's derived outputs as\n * REMOVED. Recompute is tier-safe: the fanout's own source scan reads\n * the elevated-excluding cache (#701/#709/#712), so it naturally omits\n * the now-elevated record instead of re-embedding its plaintext. Landing\n * back at tier 0 (`elevated === false`) is the reverse (Task 2, #722):\n * `syncDerivedOutputs` instead runs the ordinary local-write add-\n * dispatchers (`dispatchDerivations`/`dispatchMaterializedViews`, the\n * same ones a plain `put()` fires) to restore the contribution —\n * reversible, since the source's plaintext survives the elevate/demote\n * rewrap round-trip. `record` is the decoded source record (PRE-move on\n * the remove side, POST-move on the add side); only the rollup edge\n * consumes it on remove, both add-dispatchers require it on add; `null`\n * (tombstone/delete-marker) skips both. `version` stamps the add-\n * dispatchers' `_derivedFrom.sourceVersion` metadata — reuses the version\n * the tier op already has, no re-read; unused on the remove side. No-op\n * fast when the collection has no MV/derivation source — each dispatcher\n * below checks its own source before doing any work.\n */\n syncDerived(id: string, record: T | null, elevated: boolean, version?: number): Promise<void>\n /**\n * `true` iff the collection has a materialized-view or derivation source\n * attached. Gates the {@link syncDerived} pre-move decode on the remove\n * direction (`elevate()`, `putAtTier(tier>0)`, `demote()`'s intermediate-\n * tier branch): those sites decode the record SOLELY to feed\n * `syncDerived(..., true)`, whose dispatchers no-op immediately when the\n * collection has no MV/derivation source — so a no-derivation collection\n * would otherwise pay a full record-body decrypt on every tier move for\n * nothing (#722 perf regression). `false` short-circuits the decode to\n * `null`, which `syncDerivedOutputs` already treats safely.\n */\n readonly hasDerivedOutputs: boolean\n /** Emit `_source`/`_sourceTs` provenance fields when a source is supplied. */\n readonly provenance: boolean\n /** Declared tiers, or null when the feature is off. */\n readonly tiers: ReadonlySet<number> | null\n /** Above-tier read visibility mode (`'invisibility'` | `'ghost'`). */\n readonly tierMode: TierMode\n /** Resolve a tier DEK by its `dekKey(name, tier)`. */\n getDEK(key: string): Promise<EnclaveKey>\n /** Fire a cross-tier access event (sink stays collection-resident). */\n emitCrossTierEvent(event: CrossTierAccessEvent): void\n /**\n * Register this record's ref in the encrypted `_subject_index` (#766).\n * `putAtTier`'s raw `ctx.adapter.put` bypasses `Collection.put()`'s write-\n * hook pipeline — the `onAfterWrite` hook that normally does this — so a\n * record whose FIRST persistence is `putAtTier` (sensitive from birth, a\n * documented legitimate use) would otherwise never enter the index and\n * stay unreachable by `vault.forget()`. Idempotent (safe on every call,\n * including a repeat write of an already-registered record) and a no-op\n * when the collection declares no forget-subject field.\n */\n addSubjectRef(id: string, record: T): Promise<void>\n}\n\nexport function assertTiersEnabled<T>(ctx: TiersContext<T>): void {\n if (!ctx.tiers) {\n throw new Error(\n `Collection \"${ctx.name}\": hierarchical tiers are not enabled. ` +\n `Pass { tiers: [0, 1, 2, …] } to vault.collection() to opt in.`,\n )\n }\n}\n\nexport function assertDeclaredTier<T>(ctx: TiersContext<T>, tier: number): void {\n if (tier < 0 || !Number.isInteger(tier)) {\n throw new Error(`Collection \"${ctx.name}\": tier must be a non-negative integer, got ${tier}`)\n }\n if (tier === 0) return\n if (!ctx.tiers || !ctx.tiers.has(tier)) {\n throw new Error(\n `Collection \"${ctx.name}\": tier ${tier} is not declared in { tiers: [...] }`,\n )\n }\n}\n\n/**\n * Tier-composition guard (#724 / Arc 7 of the tier-invisibility campaign).\n *\n * Refuses `tiers` declared together with a derived-artifact feature whose\n * crypto has not yet been made tier-aware — i.e. a feature `elevate()` /\n * `demote()` does not re-key when a record moves tiers, so an elevated\n * record's data for that feature would stay readable at tier 0.\n *\n * **Status:** the original call site (`Collection` constructor, beside\n * `buildUniqueConstraintSet`) was removed by Arc 10 Task 1 (#724) — the\n * `tiers + blobFields` refusal below was superseded by a runtime read gate\n * on `collection.blob(id)` (`with-shape/blobs/blob-set.ts`), so this\n * function currently has no caller. Relocated here from\n * `with-lookup/indexing/unique-constraints.ts` (#733) so the tier-domain\n * guard lives in the tier domain, rather than an indexing file it only\n * borrowed for a grandfathered import specifier.\n *\n * ## #724 verified: `tiers + blobFields` leaks\n *\n * `collection.blob(id)` (`Collection.blob()`) never checks the live\n * record's tier before returning a `BlobSet` handle, and `BlobSet`'s crypto\n * is entirely orthogonal to the tier ladder:\n * - the slot map (`_blob_slots_{collection}/{id}`) is encrypted under the\n * collection's TIER-0 DEK (`getDEK(name)` ≡ `dekKey(name, 0)` — see\n * `dekKey` in `with-party/team/tiers.ts`), never a tier-N DEK;\n * - chunk content (`_blob_index` / `_blob_chunks`) is encrypted under the\n * vault-shared `_blob` DEK (`BLOB_COLLECTION`), which has no tier\n * dimension at all.\n * `elevate()`/`demote()` (this file) rewrap only the live record body,\n * `_history` snapshots (#712), and index side-cars — blob slots and chunks\n * are untouched. A caller whose keyring never held the record's elevated\n * tier DEK can still open `collection.blob(id).get(slot)` and read full\n * plaintext, even though `collection.get(id)` correctly reports the same\n * record as invisible. See `__tests__/tier-composition-guard.test.ts` for\n * the reproduction.\n *\n * ## Safe today — no check needed here\n *\n * Field indexes, search (`textIndexes`/`embeddings`), `withHistory`\n * (snapshot keys are rewrapped by `elevate()`/`demote()`, #712), and the\n * decoded-record cache (evicted on tier moves) are already tier-safe. They\n * are not enumerated below; only known-unsafe features are refused, so any\n * combination not named here passes silently by default.\n *\n * ## Deliberately NOT handled here\n *\n * - `ledger` — `withHistory` threads a ledger writer onto every tiered\n * collection by default in shipped usage; refusing `tiers + ledger` would\n * break that default. A ledger-specific tier-rekey handler (rewrap ledger\n * entries, or scope ledger visibility to tier) is a separate arc.\n * - materialized-view (MV) output — an MV's fanout spec lives on the\n * SOURCE collection, not on the MV collection's own config, so it is not\n * visible at `vault.collection()` registration time and this guard cannot\n * detect it structurally.\n */\nexport function assertTierComposition(\n collectionName: string,\n cfg: { readonly tiers: boolean; readonly blobFields: BlobFieldsConfig | undefined },\n): void {\n if (!cfg.tiers) return\n if (cfg.blobFields !== undefined && Object.keys(cfg.blobFields).length > 0) {\n throw new UnsupportedTierCompositionError(\n 'blobs',\n `Collection \"${collectionName}\": blobFields are not supported together with tiers (#724) — ` +\n `blob chunk content is encrypted under a vault-shared DEK that elevate()/demote() do not ` +\n `re-key, so an elevated record's blob attachments would remain readable at tier 0. Use a ` +\n `non-tiered collection for blob-bearing records until a blob tier-rekey handler ships.`,\n )\n }\n}\n\nfunction isElevatorOrOwner<T>(ctx: TiersContext<T>): boolean {\n return ctx.keyring.role === 'owner' || ctx.keyring.role === 'admin'\n}\n\n/**\n * Structural surface {@link syncDerivedOutputs} needs from the owning\n * collection — the SAME onDelete dispatchers `forgetDerivedFanout` already\n * drives (`kernel/via/dispatch.ts`), reused as-is (#722). Narrower than\n * `Collection<T>` (avoids an import cycle: collection.ts constructs\n * `TiersContext` FROM `this`, which already satisfies this shape\n * structurally — no cast needed at the `tiersContext()` call site).\n */\nexport interface DerivedOutputsHost<T> {\n dispatchMaterializedViewsOnDelete(id: string): Promise<{ deleted: number; residueUndecodable: string[]; residueDeclined: string[] }>\n dispatchArrayDerivationsOnDelete(id: string, eraseRecordShapeToo?: boolean): Promise<number>\n dispatchRollupsOnDelete(id: string, deleted: T): Promise<unknown>\n /** Task 2 (#722) add-direction: the same local-write dispatchers an ordinary `put()` fires. */\n dispatchMaterializedViews(id: string, record: T): Promise<void>\n dispatchDerivations(id: string, record: T, version: number): Promise<void>\n}\n\n/**\n * The {@link TiersContext.syncDerived} implementation collection.ts's\n * `tiersContext()` binds `this` into (#722). See that field's doc comment\n * for the design; `host` is `this` — every dispatcher below is a public\n * Collection method, already self-guarding (no-op) when the collection has\n * no materialized-view/derivation source, so no separate fast-path check is\n * needed here.\n */\nexport async function syncDerivedOutputs<T>(\n host: DerivedOutputsHost<T>,\n id: string,\n record: T | null,\n elevated: boolean,\n version?: number,\n): Promise<void> {\n if (elevated) {\n await host.dispatchMaterializedViewsOnDelete(id)\n await host.dispatchArrayDerivationsOnDelete(id, true)\n if (record !== null) await host.dispatchRollupsOnDelete(id, record)\n return\n }\n // Task 2 (#722) recompute-as-add: the record rejoined tier 0 (demote(→0) /\n // putAtTier(0)) — restore its contribution via the SAME local-write\n // dispatchers an ordinary `put()` fires (`collection.ts`'s\n // `_onRecordMutated('local-write')`), same order. `record === null` only\n // when a caller demotes a tombstone/delete-marker to 0 — nothing to add.\n if (record === null) return\n await host.dispatchDerivations(id, record, version ?? 0)\n await host.dispatchMaterializedViews(id, record)\n}\n\n/**\n * tier-aware put. Encrypts the record with the collection's tier-N DEK and\n * stamps `_tier: N` on the envelope. The caller's keyring must hold the tier-N\n * DEK (directly, by delegation, or by virtue of being the grantor); otherwise\n * throws `TierNotGrantedError`.\n *\n * accepts an optional `elevation` context. When present, the emitted cross-tier\n * event is stamped with `authorization: 'elevation'`, the elevation's reason,\n * and the caller's pre-elevation tier. `vault.elevate(...).collection().put`\n * threads this through; direct `putAtTier` calls leave it undefined and fall\n * back to the inherent-write event shape.\n */\nexport async function putAtTier<T>(\n ctx: TiersContext<T>,\n id: string,\n record: T,\n tier: number,\n opts?: { elevation?: { reason: string; fromTier: number }; source?: string; sourceTs?: string },\n): Promise<TierMoveResult> {\n assertTiersEnabled(ctx)\n assertDeclaredTier(ctx, tier)\n assertTierAccess(ctx.keyring, ctx.name, tier)\n\n const key = dekKey(ctx.name, tier)\n const dek = await ctx.getDEK(key)\n\n const existing = await ctx.adapter.get(ctx.vault, ctx.name, id)\n // #712/whole-branch-fix-1: putAtTier moves the record OFF its existing\n // tier, same as elevate/demote — so the caller must also be cleared for\n // that existing tier, not just the target. Without this, a member holding\n // only the target tier's DEK could putAtTier over a record parked at a\n // tier they've never been granted; the from-tier `getDEK` below would then\n // silently MINT a fresh DEK for that tier into their keyring (a\n // non-cleared caller creating tier key material inside the trust\n // boundary — see `assertTierAccess`'s doc comment). Gate here, BEFORE any\n // from-tier getDEK/syncHistory call, so the mint never happens; owner/\n // admin/custodian still bypass (they may mint, same as elevate/demote).\n assertTierAccess(ctx.keyring, ctx.name, existing?._tier ?? 0)\n const version = existing ? existing._v + 1 : 1\n\n // #728 review-fix-2: resolve the from-tier DEK and snapshot the PRE-move\n // version BEFORE the live write below, mirroring elevate()/demote() —\n // originally this ran much later (after syncIndexes/syncLedger/syncDerived/\n // syncSearchResilient), so a throw in any of those left the live record\n // already moved with `existing` — its sole pre-move copy — gone, silently\n // losing the version #728 exists to preserve. `fromKey`/`fromDek` are\n // resolved ONCE here and reused below for the trailing syncHistory rewrap.\n const fromKey = dekKey(ctx.name, existing?._tier ?? 0)\n const fromDek = await ctx.getDEK(fromKey)\n if (existing && ctx.historyEnabled) {\n // Snapshot the version this write is about to overwrite — same law as\n // Collection.put()'s history save, extended to putAtTier (#728). Rewrap\n // under the DESTINATION `dek` via `applyRewrappedBody` (never\n // `ctx.codec.encryptRecord`, which always resolves the tier-0 DEK and\n // would leak this body at rest whenever the prior tier was > 0).\n // Untagged (`_tier`/`_elevatedBy` stripped), same as elevate()/\n // demote()'s snapshots. `_v` stays the PRE-move version (unbumped), as\n // `preCarried` already carries it.\n const body = await rewrapBodyToDek(existing, fromDek, dek)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from ...preCarried\n const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...preCarried } = existing\n await ctx.saveHistorySnapshot(id, applyRewrappedBody(preCarried, body))\n }\n\n const json = JSON.stringify(record)\n const { iv, data } = await encrypt(json, dek)\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: ctx.keyring.userId,\n ...(tier > 0 && { _tier: tier }),\n ...(ctx.provenance && opts?.source !== undefined ? { _source: opts.source, _sourceTs: opts.sourceTs ?? new Date().toISOString() } : {}),\n }\n\n await ctx.adapter.put(ctx.vault, ctx.name, id, envelope)\n\n // #766: putAtTier bypasses Collection.put()'s onAfterWrite hook — register\n // the subject ref directly so a sensitive-from-birth first write stays\n // reachable by vault.forget(). No-op when no forget-subject field is declared.\n await ctx.addSubjectRef(id, record)\n\n // #702/#709: keep the record cache AND indexes coherent with the raw\n // write — same law as elevate/demote (#691): tier > 0 → invisible on\n // tier-0 surfaces (evict + purge); tier 0 → an ordinary write (re-seed +\n // reindex). syncIndexes runs first — it needs the pre-write cache entry\n // as \"previous\", which syncCache is about to overwrite/evict.\n if (tier > 0) {\n await ctx.syncIndexes(id, null, version)\n ctx.syncCache(id, null)\n // #729: the record lands above tier 0 — purge its tier-0-era plaintext\n // ledger deltas (irreversible; a tier-0 putAtTier(0) has nothing to purge).\n await ctx.syncLedger(id)\n // #722: recompute this record's derived outputs now that it left tier 0\n // — same law as elevate() below. `existing` is the PRE-write envelope;\n // decode it only here (the rollup edge is the only consumer). Gated by\n // `hasDerivedOutputs` — no-derivation collections skip this decrypt\n // entirely (perf regression review finding, Arc 9 #722). Whole-branch\n // review: `existing` may sit at ANY prior tier (including a\n // `putAtTier`-origin tier>0 record with no `_cek` ever minted) — decode\n // it under ITS OWN tier's DEK via `codec.decryptRecordAtDek`, not\n // `ctx.codec.decryptRecord`'s tier-unaware default, which threw\n // `TamperedError` here.\n await ctx.syncDerived(\n id,\n ctx.hasDerivedOutputs && existing\n ? await ctx.codec.decryptRecordAtDek(existing, await ctx.getDEK(dekKey(ctx.name, existing._tier ?? 0)), id)\n : null,\n true,\n )\n } else {\n const rec = await ctx.codec.decryptRecord(envelope, { id, sealedAsHandles: true })\n await ctx.syncIndexes(id, rec, envelope._v, existing ?? undefined)\n ctx.syncCache(id, rec !== null ? { record: rec, version: envelope._v } : null)\n // #722 Task 2: the record is written at tier 0 — restore its\n // contribution to every derived output (reuse the decode above, no\n // double-decrypt).\n await ctx.syncDerived(id, rec, false, envelope._v)\n }\n // #721: search artifacts follow the same tier > 0 → purge / tier 0 →\n // re-embed law as syncIndexes above. No ordering dependency on syncCache —\n // the _ftindex rebuild is deferred to the next retrieve() and _vec re-embed\n // reads `record` directly, not the cache. #774: a permanently stuck\n // compensation must not abort the write — the record put above already\n // landed — so it is caught and surfaced as residue instead, same as\n // elevate()/demote() (#764).\n const searchResidue = await syncSearchResilient(ctx, id, tier > 0 ? null : record, envelope._v)\n\n if (tier > 0) {\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier,\n authorization: opts?.elevation ? 'elevation' : 'inherent',\n op: 'put',\n ts: envelope._ts,\n ...(opts?.elevation && {\n reason: opts.elevation.reason,\n elevatedFrom: opts.elevation.fromTier,\n }),\n })\n }\n\n // #712/whole-branch-fix-2: syncHistory runs LAST — after syncIndexes/\n // syncCache/syncSearch (and the cross-tier emit) have already landed. Its\n // own throw (adapter I/O, the Fix-1 assertion, the rewrap) then strands\n // only the `_history` artifact instead of leaving indexes/cache/search\n // unsynced behind an already-moved live record (would conditionally\n // reopen #709/#721/#723 on the error path). A direct putAtTier also moves\n // the record's tier — its history snapshots (if any exist from an earlier\n // tier-0 put()) must follow, same as elevate/demote. Skip when the record\n // was already at this tier (no move, nothing to rewrap). `fromKey`/\n // `fromDek` were already resolved above (review-fix-2), before the live\n // write, for the pre-move snapshot — reused here rather than recomputed.\n if (fromKey !== key) {\n await ctx.syncHistory(id, fromDek, dek)\n // #724: this putAtTier moved the record's tier — rehome its blobs too.\n await ctx.syncBlobs(id, existing?._tier ?? 0, tier)\n }\n return { searchResidue }\n}\n\n/**\n * tier-aware get. When the stored record is at a tier the caller cannot\n * decrypt:\n * - `'invisibility'` mode (default) → returns `null`.\n * - `'ghost'` mode → returns a `GhostRecord` placeholder with the tier and\n * the record id (the record exists but contents are withheld).\n *\n * Fully-cleared reads return the plaintext record and fire a cross-tier event\n * when `_tier > 0`.\n */\nexport async function getAtTier<T>(ctx: TiersContext<T>, id: string): Promise<T | GhostRecord | null> {\n assertTiersEnabled(ctx)\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope) return null\n const tier = envelope._tier ?? 0\n if (tier === 0) {\n return ctx.codec.decryptRecord(envelope, { id })\n }\n\n const key = dekKey(ctx.name, tier)\n if (!ctx.keyring.deks.has(key)) {\n if (ctx.tierMode === 'ghost') {\n return { _ghost: true, _tier: tier } as GhostRecord\n }\n return null\n }\n\n const dek = await ctx.getDEK(key)\n // A tiered record may carry a per-record CEK (e.g. a CEK record\n // elevated via `elevate()`): the CEK is wrapped under the TIER DEK, so\n // unwrap under the tier DEK then decrypt the body under the CEK. Legacy\n // tiered records decrypt directly under the tier DEK.\n let plaintext: string\n let cek: EnclaveKey | undefined\n if (envelope._cek !== undefined) {\n cek = await unwrapCek(envelope._cek, dek)\n ctx.cekCache?.set(id, cek, 1)\n plaintext = await decrypt(envelope._iv, envelope._data, cek)\n } else {\n plaintext = await decrypt(envelope._iv, envelope._data, dek)\n }\n let record = JSON.parse(plaintext) as T\n\n // #635: this manual-decrypt leg never went through `decryptRecord`, so it\n // never processed `_sealed` slots — an elevated classified record read\n // back through here would return the body WITHOUT its sealed fields\n // (silent omission). `applySealedSlots` is the same post-processing\n // `decryptRecord`'s tier-0 branch above gets for free, extracted so it can\n // take OUR already-unwrapped `cek` (unwrapped under the TIER dek above)\n // instead of resolving one itself under the collection DEK the way\n // `decryptRecord`/`resolveEnvelopeCek` would — which would be the wrong\n // key for a `_cek` wrapped under a tier DEK. `sealedAsHandles` is omitted\n // (default false) to match this function's OWN tier-0 branch above\n // (`ctx.codec.decryptRecord(envelope, { id })`, no `sealedAsHandles`) —\n // both tiers return sealed fields inline-decrypted, not as handles.\n if (envelope._sealed !== undefined) {\n record = await ctx.codec.applySealedSlots(record, envelope._sealed, cek, { id })\n }\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier,\n authorization: isElevatorOrOwner(ctx) ? 'inherent' : 'delegation',\n op: 'get',\n ts: new Date().toISOString(),\n })\n\n return record\n}\n\n/**\n * list ids grouped by the caller's readability. Returns only ids whose tier the\n * caller can read. Above-tier ids are omitted in `'invisibility'` mode and\n * included (with tier metadata) in `'ghost'` mode.\n */\nexport async function listAtTier<T>(ctx: TiersContext<T>): Promise<Array<{ id: string; tier: number; readable: boolean }>> {\n assertTiersEnabled(ctx)\n const ids = await ctx.adapter.list(ctx.vault, ctx.name)\n const out: Array<{ id: string; tier: number; readable: boolean }> = []\n for (const id of ids) {\n const env = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!env) continue\n const tier = env._tier ?? 0\n const readable = tier === 0 || ctx.keyring.deks.has(dekKey(ctx.name, tier))\n if (!readable && ctx.tierMode === 'invisibility') continue\n out.push({ id, tier, readable })\n }\n return out\n}\n\n/**\n * Result of a completed `elevate()`/`demote()` (#764). The record move\n * (put + indexes/cache/ledger/derived/history/blobs sync) always completes —\n * `searchResidue: true` means only the search-index side (`_vec`/`_ftindex`)\n * was left in a needs-retry state (a stuck `PersistedIndexCompensationError`),\n * mirroring `forget()`'s `indexResidue` posture rather than aborting the move.\n */\nexport interface TierMoveResult {\n readonly searchResidue: boolean\n}\n\n/**\n * Run {@link TiersContext.syncSearch} with #764 resilience: a permanently\n * stuck compensation (`PersistedIndexCompensationError`, thrown by\n * `PersistedIndexStore` when its compensating `remove()` of a stale\n * `_ftindex` blob keeps failing) must not abort `elevate()`/`demote()` — the\n * record's tier-move `put` has already landed, and aborting here would leave\n * `syncLedger`/`syncDerived`/`syncHistory`/`syncBlobs` unsynced behind it (the\n * partial-completion hazard #764 names). Same posture `forget()` takes for\n * `_purgeSearchIndex` (`vault.ts`'s `indexResidue`) — everything else (a\n * genuinely unexpected search-hook error) still aborts, undisturbed.\n */\nasync function syncSearchResilient<T>(ctx: TiersContext<T>, id: string, record: T | null, version?: number): Promise<boolean> {\n try {\n await ctx.syncSearch(id, record, version)\n return false\n } catch (e) {\n if (e instanceof PersistedIndexCompensationError) return true\n throw e\n }\n}\n\n/**\n * elevate a record to a higher tier. Re-encrypts with the target tier's DEK.\n * The caller must hold DEKs for both the current tier (to decrypt) and the\n * target tier (to re-encrypt). Stamps `_elevatedBy` with the caller id so\n * `demote()` can check the reverse operation.\n */\nexport async function elevate<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult> {\n assertTiersEnabled(ctx)\n assertDeclaredTier(ctx, toTier)\n assertTierAccess(ctx.keyring, ctx.name, toTier)\n\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope || isDeleteMarker(envelope) || isTombstoneShape(envelope)) {\n throw new Error(`Record \"${id}\" not found in collection \"${ctx.name}\"`)\n }\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return { searchResidue: false }\n if (toTier < fromTier) {\n throw new Error(`Use demote() to lower the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n // Caller must have access at the existing tier to decrypt.\n if (fromTier > 0) assertTierAccess(ctx.keyring, ctx.name, fromTier)\n\n const fromKey = dekKey(ctx.name, fromTier)\n const toKey = dekKey(ctx.name, toTier)\n const fromDek = await ctx.getDEK(fromKey)\n const toDek = await ctx.getDEK(toKey)\n\n // Per-record CEK composes with tiers: the body key is unchanged (history\n // chain identity preserved); only the wrapping key moves with the tier.\n // Legacy (no `_cek`) records take the direct-DEK path unchanged.\n const now = new Date().toISOString()\n const body = await rewrapBodyToDek(envelope, fromDek, toDek)\n if (body.cek) ctx.cekCache?.set(id, body.cek, 1)\n // #728: snapshot the PRE-move version into `_history` before it's\n // overwritten below — reuses `body` (already fromDek→toDek) via\n // `applyRewrappedBody` (never `ctx.codec.encryptRecord`, which always\n // resolves the tier-0 DEK and would leak this body at rest whenever\n // `fromTier > 0`). Untagged (`_tier`/`_elevatedBy` stripped) so the\n // history read-gate doesn't hide it PERMANENTLY once the record demotes\n // back — at-rest protection comes from the ciphertext being under\n // `toDek`, not from the tag. `_v` stays the PRE-move version (unbumped),\n // exactly as `envelope` already carries it.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from ...snapshot\n const { _tier: _preTier, _elevatedBy: _preElevatedBy, ...snapshot } = applyRewrappedBody(envelope, body)\n await ctx.saveHistorySnapshot(id, snapshot)\n // #662: spread every slot the source carries (_sealed/_det/_vdig/_bidx/\n // _source/_sourceTs/_debug) through UNCHANGED, then override only\n // the fields a tier move manages. rewrapBodyToDek preserves the CEK, so no\n // passenger slot is re-keyed by the move. The old field-literal dropped every\n // unlisted slot.\n const next: EncryptedEnvelope = {\n ...envelope,\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: body._iv,\n _data: body._data,\n _by: ctx.keyring.userId,\n _tier: toTier,\n _elevatedBy: ctx.keyring.userId,\n ...(body._cek !== undefined ? { _cek: body._cek } : {}),\n }\n await ctx.adapter.put(ctx.vault, ctx.name, id, next)\n // Evict/purge only once the write landed — a throwing put must not blind\n // the eager cache or leave a readable index behind for a still-valid\n // tier-0 record (same ordering as demote). #709: syncIndexes runs first —\n // it needs the pre-elevation cache entry as \"previous\" to clean the\n // eager index bucket; the persisted side-car purge is content-free.\n await ctx.syncIndexes(id, null, next._v)\n ctx.syncCache(id, null)\n // #721: same purge law as syncIndexes above — the record left tier 0, so\n // its _vec sidecar is purged and _ftindex is invalidated. #764: a\n // permanently stuck compensation must not abort the move — the record put\n // above already landed — so it is caught and surfaced as residue instead.\n const searchResidue = await syncSearchResilient(ctx, id, null)\n // #729: elevate always lands the record at tier > 0 — purge its\n // tier-0-era plaintext ledger deltas (irreversible; entry metadata stays).\n await ctx.syncLedger(id)\n // #722: recompute this record's derived outputs now that it left tier 0 —\n // same law as putAtTier(tier>0) above. `envelope` is the PRE-move envelope\n // (captured before the rewrap), decoded only here (the rollup edge is the\n // only consumer of the record content). Gated by `hasDerivedOutputs` — no-\n // derivation collections skip this decrypt entirely (perf regression\n // review finding, Arc 9 #722). Whole-branch review: decode under `fromDek`\n // via `codec.decryptRecordAtDek` — `fromTier` may be > 0 here (a prior\n // elevate), and `ctx.codec.decryptRecord`'s tier-unaware CEK resolution\n // threw `TamperedError` whenever this op's own rewrap hadn't just primed\n // the cekCache (non-`perRecordKeys` collections; `_cek`-absent bodies).\n await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek(envelope, fromDek, id) : null, true)\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier: toTier,\n authorization: 'elevation',\n op: 'elevate',\n ts: now,\n })\n\n // #712/whole-branch-fix-2: syncHistory runs LAST — after syncIndexes/\n // syncCache/syncSearch/the cross-tier emit have already landed, so its own\n // throw (adapter I/O, the rewrap) strands only the `_history` artifact\n // instead of leaving indexes/cache/search unsynced behind an already-moved\n // live record (would conditionally reopen #709/#721/#723 on the error\n // path). Rewraps prior-version history snapshots to the SAME toTier DEK —\n // otherwise they stay decryptable at rest under fromDek forever.\n await ctx.syncHistory(id, fromDek, toDek)\n // #724: rehome any solo-owned blob attachments to the target tier's\n // `_blob` DEK — same at-rest law as syncHistory above.\n await ctx.syncBlobs(id, fromTier, toTier)\n return { searchResidue }\n}\n\n/**\n * demote a record to a lower tier. Allowed only for the user who performed the\n * last elevation or an owner.\n */\nexport async function demote<T>(ctx: TiersContext<T>, id: string, toTier: number): Promise<TierMoveResult> {\n assertTiersEnabled(ctx)\n if (toTier < 0) throw new Error(`Cannot demote to negative tier ${toTier}`)\n\n const envelope = await ctx.adapter.get(ctx.vault, ctx.name, id)\n if (!envelope || isDeleteMarker(envelope) || isTombstoneShape(envelope)) {\n throw new Error(`Record \"${id}\" not found in collection \"${ctx.name}\"`)\n }\n const fromTier = envelope._tier ?? 0\n if (toTier === fromTier) return { searchResidue: false }\n if (toTier > fromTier) {\n throw new Error(`Use elevate() to raise the tier of \"${id}\" from ${fromTier} to ${toTier}`)\n }\n const isOwner = ctx.keyring.role === 'owner'\n const isOriginalElevator = envelope._elevatedBy === ctx.keyring.userId\n if (!isOwner && !isOriginalElevator) {\n throw new TierDemoteDeniedError(id, fromTier)\n }\n // Caller must still hold the DEK of the current tier to decrypt.\n assertTierAccess(ctx.keyring, ctx.name, fromTier)\n if (toTier > 0) assertDeclaredTier(ctx, toTier)\n\n const fromDek = await ctx.getDEK(dekKey(ctx.name, fromTier))\n const toDek = await ctx.getDEK(dekKey(ctx.name, toTier))\n\n // CEK re-wrap on demote — same body key, moved from the source tier\n // DEK to the target tier DEK. Legacy records take the direct-DEK path.\n const now = new Date().toISOString()\n const body = await rewrapBodyToDek(envelope, fromDek, toDek)\n if (body.cek) ctx.cekCache?.set(id, body.cek, 1)\n // #662: same passenger carry-through as elevate(). demote additionally CLEARS\n // `_elevatedBy` (the demote right is consumed) and OMITS `_tier` at tier 0, so\n // both are destructured out of the spread rather than carried.\n // eslint-disable-next-line @typescript-eslint/no-unused-vars -- excluded from ...carried\n const { _elevatedBy, _tier: _priorTier, ...carried } = envelope\n // #728: snapshot the PRE-move version into `_history` — reuses `body`\n // (already fromDek→toDek) and `carried` (envelope minus `_tier`/\n // `_elevatedBy`, computed above for the live write) via\n // `applyRewrappedBody`, so a tier-0-landing demote's snapshot is untagged\n // just like an ordinary put() entry, same law as elevate()'s snapshot\n // above. `_v` stays the PRE-move version (unbumped), as `carried` already\n // carries it.\n await ctx.saveHistorySnapshot(id, applyRewrappedBody(carried, body))\n const next: EncryptedEnvelope = {\n ...carried,\n _noydb: NOYDB_FORMAT_VERSION,\n _v: envelope._v + 1,\n _ts: now,\n _iv: body._iv,\n _data: body._data,\n _by: ctx.keyring.userId,\n ...(toTier > 0 ? { _tier: toTier } : {}),\n ...(body._cek !== undefined ? { _cek: body._cek } : {}),\n }\n await ctx.adapter.put(ctx.vault, ctx.name, id, next)\n\n // #691/#709: landing back at tier 0 makes this a plain tier-0 record\n // again — it must stay plain-get()-readable AND indexed in the same\n // session, so re-seed the eager cache and rebuild its index entries from\n // the just-written (now tier-0-keyed) envelope through the canonical\n // codec path, the same decode collection.ts's own eager hydration / lazy\n // cache-miss paths use — reused for both, no double-decrypt. A demote\n // that lands on an intermediate elevated tier (toTier > 0) still evicts\n // + purges: the record stays above tier 0 and must remain invisible on\n // tier-0 surfaces, unindexed. syncIndexes runs first in both branches —\n // it needs the pre-demote cache entry as \"previous\".\n // #764: a permanently stuck search-index compensation must not abort the\n // move — the record put above already landed — so it is caught in both\n // branches and surfaced as residue on the return value instead.\n let searchResidue: boolean\n if (toTier === 0) {\n const rec = await ctx.codec.decryptRecord(next, { id, sealedAsHandles: true })\n await ctx.syncIndexes(id, rec, next._v, envelope)\n ctx.syncCache(id, rec !== null ? { record: rec, version: next._v } : null)\n // #721: reuse the decode above — no double-decrypt. The record is tier-0\n // again, so re-embed its _vec and invalidate _ftindex to include it.\n searchResidue = await syncSearchResilient(ctx, id, rec, next._v)\n // #722 Task 2: the record rejoined tier 0 — restore its contribution to\n // every derived output (reuse the decode above, no double-decrypt).\n await ctx.syncDerived(id, rec, false, next._v)\n } else {\n await ctx.syncIndexes(id, null, next._v)\n ctx.syncCache(id, null)\n // #721: still above tier 0 — purge _vec, invalidate _ftindex.\n searchResidue = await syncSearchResilient(ctx, id, null)\n // #722 Task 2: still elevated (an intermediate tier) — the source's\n // derived outputs stay recompute-as-removed, same law as elevate()\n // above. `envelope` is the PRE-move envelope captured at function\n // entry; decoded only here (the rollup edge is the only consumer).\n // Gated by `hasDerivedOutputs` — no-derivation collections skip this\n // decrypt entirely (perf regression review finding, Arc 9 #722).\n // Whole-branch review: decode under `fromDek` via\n // `codec.decryptRecordAtDek` — this branch's `fromTier` is always > 0 (a\n // demote FROM tier `fromTier` TO an intermediate `toTier` > 0), so\n // `ctx.codec.decryptRecord`'s tier-unaware CEK resolution threw\n // `TamperedError` here exactly as it did in elevate() above\n // (code-identical bug, same fix).\n await ctx.syncDerived(id, ctx.hasDerivedOutputs ? await ctx.codec.decryptRecordAtDek(envelope, fromDek, id) : null, true)\n }\n\n ctx.emitCrossTierEvent({\n actor: ctx.keyring.userId,\n collection: ctx.name,\n id,\n tier: fromTier,\n authorization: 'elevation',\n op: 'demote',\n ts: now,\n })\n\n // #712/whole-branch-fix-2: syncHistory runs LAST — after syncIndexes/\n // syncCache/syncSearch/the cross-tier emit have already landed, so its own\n // throw (adapter I/O, the rewrap) strands only the `_history` artifact\n // instead of leaving indexes/cache/search unsynced behind an already-moved\n // live record (would conditionally reopen #709/#721/#723 on the error\n // path). Rewraps history snapshots the same direction as the live body —\n // demote restores tier-0 (or intermediate-tier) readability at rest.\n await ctx.syncHistory(id, fromDek, toDek)\n // #724: rehome any solo-owned blob attachments the same direction as the\n // live body — restores tier-0 (or intermediate-tier) readability at rest.\n await ctx.syncBlobs(id, fromTier, toTier)\n return { searchResidue }\n}\n\n/**\n * Classify a live envelope's `_sealed` slots for crypto-shred completeness\n * (#M-1). Thin delegate to {@link RecordCodec.classifySealedShred}; surfaced\n * here because `vault.ts` forget() reaches in via `collection._classifySealedShred`.\n */\nexport function classifySealedShred<T>(\n ctx: TiersContext<T>,\n live: EncryptedEnvelope,\n): Promise<{ readonly slots: readonly SealedShredSlot[] }> {\n return ctx.codec.classifySealedShred(live)\n}\n\n// #837 — classifySealedShred's slot type must be nameable from this entry.\nexport type { SealedShredSlot } from '../../kernel/enclave/index.js'\n"],"mappings":"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","names":["putAtTier","getAtTier","listAtTier","elevate","demote"]}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "./chunk-MEKAMERX.js";
|
|
12
12
|
import {
|
|
13
13
|
createOwnerKeyring
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WJDYZP3U.js";
|
|
15
15
|
import {
|
|
16
16
|
LedgerStore
|
|
17
17
|
} from "./chunk-ZJGZDOQO.js";
|
|
@@ -241,7 +241,7 @@ async function createOwnerOnAdoptedPartition(store, vaultName, opts) {
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
if (isManaged(opts)) {
|
|
244
|
-
const { createNoydb } = await import("./noydb-
|
|
244
|
+
const { createNoydb } = await import("./noydb-N5IW6OFZ.js");
|
|
245
245
|
const db = await createNoydb({
|
|
246
246
|
store,
|
|
247
247
|
user: userId,
|
|
@@ -296,4 +296,4 @@ export {
|
|
|
296
296
|
withCargo,
|
|
297
297
|
describeExtraction
|
|
298
298
|
};
|
|
299
|
-
//# sourceMappingURL=chunk-
|
|
299
|
+
//# sourceMappingURL=chunk-BLB7P7LS.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
grant,
|
|
5
5
|
revoke,
|
|
6
6
|
rotateKeys
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-WJDYZP3U.js";
|
|
8
8
|
import {
|
|
9
9
|
openEnvelopeJson
|
|
10
10
|
} from "./chunk-PEY6ZTCF.js";
|
|
@@ -96,4 +96,4 @@ export {
|
|
|
96
96
|
listCredentials,
|
|
97
97
|
credentialStatus
|
|
98
98
|
};
|
|
99
|
-
//# sourceMappingURL=chunk-
|
|
99
|
+
//# sourceMappingURL=chunk-EW4HFWGJ.js.map
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
} from "./chunk-XULHFSHO.js";
|
|
7
7
|
import {
|
|
8
8
|
createOwnerKeyring
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-WJDYZP3U.js";
|
|
10
10
|
import {
|
|
11
11
|
wrapKey
|
|
12
12
|
} from "./chunk-5P5AAXYA.js";
|
|
@@ -132,4 +132,4 @@ export {
|
|
|
132
132
|
isDeedVault,
|
|
133
133
|
liberateVault
|
|
134
134
|
};
|
|
135
|
-
//# sourceMappingURL=chunk-
|
|
135
|
+
//# sourceMappingURL=chunk-HKR25MGX.js.map
|