@substrat-run/kernel 0.147.0 → 0.149.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/attachment-sweep.d.ts +208 -0
  2. package/dist/attachment-sweep.d.ts.map +1 -0
  3. package/dist/attachment-sweep.js +318 -0
  4. package/dist/attachment-sweep.js.map +1 -0
  5. package/dist/audit-outcome.d.ts +1 -1
  6. package/dist/audit-outcome.d.ts.map +1 -1
  7. package/dist/audit-outcome.js +4 -2
  8. package/dist/audit-outcome.js.map +1 -1
  9. package/dist/export-break.d.ts +137 -0
  10. package/dist/export-break.d.ts.map +1 -0
  11. package/dist/export-break.js +219 -0
  12. package/dist/export-break.js.map +1 -0
  13. package/dist/index.d.ts +24 -10
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +16 -7
  16. package/dist/index.js.map +1 -1
  17. package/dist/install-receipts.d.ts +116 -0
  18. package/dist/install-receipts.d.ts.map +1 -0
  19. package/dist/install-receipts.js +162 -0
  20. package/dist/install-receipts.js.map +1 -0
  21. package/dist/invocation-line.d.ts +85 -0
  22. package/dist/invocation-line.d.ts.map +1 -1
  23. package/dist/invocation-line.js +43 -0
  24. package/dist/invocation-line.js.map +1 -1
  25. package/dist/map-bounded.d.ts +8 -0
  26. package/dist/map-bounded.d.ts.map +1 -0
  27. package/dist/map-bounded.js +16 -0
  28. package/dist/map-bounded.js.map +1 -0
  29. package/dist/migration-digest.d.ts +56 -0
  30. package/dist/migration-digest.d.ts.map +1 -1
  31. package/dist/migration-digest.js +92 -0
  32. package/dist/migration-digest.js.map +1 -1
  33. package/dist/migration-fence.d.ts +14 -0
  34. package/dist/migration-fence.d.ts.map +1 -0
  35. package/dist/migration-fence.js +515 -0
  36. package/dist/migration-fence.js.map +1 -0
  37. package/dist/migration-replay.d.ts +52 -0
  38. package/dist/migration-replay.d.ts.map +1 -0
  39. package/dist/migration-replay.js +140 -0
  40. package/dist/migration-replay.js.map +1 -0
  41. package/dist/module-migrations.d.ts +3 -3
  42. package/dist/module-migrations.js +3 -3
  43. package/dist/peer.d.ts +32 -2
  44. package/dist/peer.d.ts.map +1 -1
  45. package/dist/peer.js +100 -4
  46. package/dist/peer.js.map +1 -1
  47. package/dist/permission-eval.d.ts +8 -0
  48. package/dist/permission-eval.d.ts.map +1 -1
  49. package/dist/permission-eval.js +14 -0
  50. package/dist/permission-eval.js.map +1 -1
  51. package/dist/platform-sweep.d.ts +114 -361
  52. package/dist/platform-sweep.d.ts.map +1 -1
  53. package/dist/platform-sweep.js +134 -561
  54. package/dist/platform-sweep.js.map +1 -1
  55. package/dist/promote-review.d.ts +125 -0
  56. package/dist/promote-review.d.ts.map +1 -0
  57. package/dist/promote-review.js +181 -0
  58. package/dist/promote-review.js.map +1 -0
  59. package/dist/schedule-state.d.ts +91 -0
  60. package/dist/schedule-state.d.ts.map +1 -0
  61. package/dist/schedule-state.js +118 -0
  62. package/dist/schedule-state.js.map +1 -0
  63. package/dist/scope-copy-ledger.d.ts +29 -0
  64. package/dist/scope-copy-ledger.d.ts.map +1 -1
  65. package/dist/scope-copy-ledger.js +23 -0
  66. package/dist/scope-copy-ledger.js.map +1 -1
  67. package/dist/scope-copy.d.ts.map +1 -1
  68. package/dist/scope-copy.js +3 -2
  69. package/dist/scope-copy.js.map +1 -1
  70. package/dist/scope-host.d.ts +303 -11
  71. package/dist/scope-host.d.ts.map +1 -1
  72. package/dist/scope-host.js.map +1 -1
  73. package/dist/scope-identity.d.ts +68 -0
  74. package/dist/scope-identity.d.ts.map +1 -0
  75. package/dist/scope-identity.js +75 -0
  76. package/dist/scope-identity.js.map +1 -0
  77. package/dist/scope-store.d.ts +9 -6
  78. package/dist/scope-store.d.ts.map +1 -1
  79. package/dist/scope-store.js.map +1 -1
  80. package/dist/spine-restore.d.ts +2 -0
  81. package/dist/spine-restore.d.ts.map +1 -1
  82. package/dist/spine-restore.js +6 -1
  83. package/dist/spine-restore.js.map +1 -1
  84. package/dist/store-driver-releases.d.ts +306 -0
  85. package/dist/store-driver-releases.d.ts.map +1 -0
  86. package/dist/store-driver-releases.js +581 -0
  87. package/dist/store-driver-releases.js.map +1 -0
  88. package/dist/table-ownership.d.ts +5 -2
  89. package/dist/table-ownership.d.ts.map +1 -1
  90. package/dist/table-ownership.js +19 -10
  91. package/dist/table-ownership.js.map +1 -1
  92. package/dist/testing.d.ts +2 -0
  93. package/dist/testing.d.ts.map +1 -1
  94. package/dist/testing.js +2 -0
  95. package/dist/testing.js.map +1 -1
  96. package/dist/upload-link.d.ts +142 -0
  97. package/dist/upload-link.d.ts.map +1 -0
  98. package/dist/upload-link.js +441 -0
  99. package/dist/upload-link.js.map +1 -0
  100. package/dist/version-migrations.d.ts +63 -1
  101. package/dist/version-migrations.d.ts.map +1 -1
  102. package/dist/version-migrations.js +174 -8
  103. package/dist/version-migrations.js.map +1 -1
  104. package/dist/vertical-scope-stores.d.ts +43 -0
  105. package/dist/vertical-scope-stores.d.ts.map +1 -0
  106. package/dist/vertical-scope-stores.js +195 -0
  107. package/dist/vertical-scope-stores.js.map +1 -0
  108. package/package.json +2 -2
@@ -1,13 +1,17 @@
1
+ import type { OrphanSweepReport, OrphanSweepRequest } from './attachment-sweep.js';
1
2
  import type { ScopeStoreStubOptions, StoreErasureReplay, StoreStub } from './scope-store.js';
2
3
  import type { ModuleLog } from './module-log.js';
3
4
  import type { BoundOperations } from './module-operations.js';
4
5
  import type { DeliveryRefusal } from './delivery-refusal.js';
5
6
  import type { ScopeRoleHolder } from './scope-role-admin.js';
6
- import type { ScopeCopyBackfillResult } from './scope-copy-ledger.js';
7
+ import type { ScopeCopyBackfillResult, SubjectErasureFence } from './scope-copy-ledger.js';
8
+ import type { VerticalScopeStore } from './vertical-scope-stores.js';
9
+ import type { StoreDriverHoldMode, StoreDriverRebuildFailure, StoreDriverRebuildRecord, StoreDriverReleaseRecord, StoreDriverRolloutAction, StoreDriverRolloutRecord, StoreDriverRolloutState, StoreDriverRolloutTarget, StoreDriverState } from './store-driver-releases.js';
7
10
  import type { SubjectRedactionCounts } from './subject-redaction.js';
8
- import type { OnBehalfOf, ExportReadInput, ExportedBatch, ImportBatch, ImportedEvent, ImportResult, ImportState, ImportCursorMove, ImportCursorMoved, AdminAction, BecomeCapabilityInput, CapabilityExchange, CapabilityFilter, CapabilityId, CapabilityPage, MintedCapability, ListPage, ConnectLink, ConnectLinkConsume, ConnectLinkFilter, ConnectLinkKey, ConsumeConnectLinkInput, MintConnectLinkInput, Connection, ConnectionFilter, ConnectionId, ConnectionGrant, ConnectionGrantRecord, ConnectionSecret, CreateConnectionInput, OpenConnection, ProjectedConnectionGrant, ProjectedConnectionKey, AccessLogEntry, DelegatedReadRecord, OwnerTransferAudit, MemberChangeAudit, PeerBindingAudit, CopyMarkAudit, BindHostnameInput, AdminLogEntry, OpsFailureEntry, PlatformRequestDrainTotalsEntry, SweepRunEntry, SweepRunKind, SweepRunOutcome, ModelUsageEntry, ModelUsageSummary, CapabilityGrant, CreateTenantInput, Decision, Instant, DomainEvent, DomainEventInput, PlatformRequestInput, PlatformRequestId, PlatformRequestEntry, PlatformRequestFilter, PlatformRequestStatus, PlatformRequestFailure, PlatformOutcomeEvent, EntitlementGrant, EntitlementGrantInput, EntitlementView, MeterReading, ScopeStorageSample, EntityRef, IdentityLink, IdentityPool, Jurisdiction, ModuleId, ModuleManifest, ScheduleSpec, SystemGrant, PeerGrantsStatusEntry, SystemGrantsStatusEntry, SystemSwitchRecord, SystemSwitch, SystemSwitchResult, PeerCoverage, PeerSwitch, PeerSwitchResult, VerticalCaller, VerticalResolution, PeerBinding, PeerInstanceResolution, CreateOrgInput, Node, Org, OrgId, OrgMembership, PermissionKey, PlatformActorId, ChannelName, ExportBreak, ChannelHistoryEntry, DnsRecord, HostnameBinding, HostnameStatus, PromotionAcknowledgement, BindAcknowledgement, PublishVersionInput, RegisterVerticalInput, VerticalServingState, RouteTarget, DirectoryDump, IdentityMembership, PrincipalId, ResolvedIdentity, RoleAssignment, RoleDefinition, QueryScopeInput, ReadScopeTableInput, Scope, ScopeDump, ScopeDumpInput, SubjectShredReceipt, ScopeQueryResult, ScopeId, ScopeStatus, ScopeTable, DenialFilter, DenialSummary, RefusalFilter, RefusalRecord, PermissionDenial, Coverage, BeginImpersonationInput, ImpersonationFilter, ImpersonationSession, ImpersonationSessionId, ScopeTablePage, StorageShape, Tenant, TenantId, TenantRole, TenantStoreHandle, AttachmentRecord, BlobStoreHandle, Visibility, Vertical, VerticalChannel, VerticalVersion, TenantStatus, Page, CountedPage, FreshnessSpec, EntityHistoryInput, EventFacetInput, EventCauseInput, EventEffectsInput, EffectsTree, InvocationEventsInput, InvocationEvents, DeadLettersInput, DeadLetter, LifecycleFlowInput, LifecycleFlowResult, OperationSeriesInput, OperationSeriesResult, CauseChain, EventFacetResult, HistoryEntry, ErrorCode, PlatformRequestFailureOrigin, IssueEntry, IssueStatus, IssueStatusInput, FindingEntry, FindingFilter, FindingRuleEntry, FindingRuleInput, FindingStatusInput, DeclaredMigration, CheckSubject, EntityGrantShape } from '@substrat-run/contracts';
11
+ import type { OnBehalfOf, ExportReadInput, ExportedBatch, ImportBatch, ImportedEvent, ImportResult, ImportState, ImportCursorMove, ImportCursorMoved, AdminAction, BecomeCapabilityInput, CapabilityExchange, CapabilityFilter, CapabilityId, CapabilityPage, MintedCapability, ListPage, ConnectLink, ConnectLinkConsume, ConnectLinkFilter, ConnectLinkKey, ConsumeConnectLinkInput, MintConnectLinkInput, Connection, ConnectionFilter, ConnectionId, ConnectionGrant, ConnectionGrantRecord, ConnectionSecret, CreateConnectionInput, OpenConnection, ProjectedConnectionGrant, ProjectedConnectionKey, AccessLogEntry, DelegatedReadRecord, OwnerTransferAudit, MemberChangeAudit, PeerBindingAudit, ConnectionBindingAudit, CopyMarkAudit, OrphanSweepAudit, BindHostnameInput, AdminLogEntry, OpsFailureEntry, PlatformRequestDrainTotalsEntry, SweepRunEntry, SweepRunKind, SweepRunOutcome, ModelUsageEntry, ModelUsageSummary, CapabilityGrant, CreateTenantInput, Decision, Instant, DomainEvent, DomainEventInput, PlatformRequestInput, PlatformRequestId, PlatformRequestEntry, PlatformRequestFilter, PlatformRequestStatus, PlatformRequestFailure, PlatformOutcomeEvent, EntitlementGrant, EntitlementGrantInput, EntitlementView, MeterReading, ScopeStorageSample, EntityRef, IdentityLink, IdentityPool, Jurisdiction, ModuleId, ModuleManifest, ScheduleSpec, SystemGrant, PeerGrantsStatusEntry, SystemGrantsStatusEntry, SystemSwitchRecord, SystemSwitch, SystemSwitchResult, PeerCoverage, PeerSwitch, PeerSwitchResult, VerticalCaller, VerticalResolution, PeerBinding, PeerInstanceResolution, CreateOrgInput, Node, Org, OrgId, OrgMembership, PermissionKey, PlatformActorId, ChannelName, ExportBreak, ChannelHistoryEntry, DnsRecord, HostnameBinding, HostnameStatus, PromotionAcknowledgement, BindAcknowledgement, PublishVersionInput, RegisterVerticalInput, VerticalServingState, RouteTarget, DirectoryDump, IdentityMembership, PrincipalId, ResolvedIdentity, RoleAssignment, RoleDefinition, QueryScopeInput, ReadScopeTableInput, Scope, ScopeDump, ScopeDumpInput, SubjectShredReceipt, ScopeQueryResult, ScopeId, ScopeStatus, ScopeTable, DenialFilter, DenialSummary, RefusalFilter, RefusalRecord, PermissionDenial, Coverage, BeginImpersonationInput, ImpersonationFilter, ImpersonationSession, ImpersonationSessionId, ScopeTablePage, StorageShape, Tenant, TenantId, TenantRole, TenantStoreHandle, AttachmentRecord, UploadLinkView, BlobStoreHandle, Visibility, Vertical, VerticalChannel, VerticalVersion, TenantStatus, Page, CountedPage, FreshnessSpec, EntityHistoryInput, EventFacetInput, EventCauseInput, EventEffectsInput, EffectsTree, InvocationEventsInput, InvocationEvents, DeadLettersInput, DeadLetter, LifecycleFlowInput, LifecycleFlowResult, OperationSeriesInput, OperationSeriesResult, CauseChain, EventFacetResult, HistoryEntry, ErrorCode, PlatformRequestFailureOrigin, IssueEntry, IssueStatus, IssueStatusInput, FindingEntry, FindingFilter, FindingRuleEntry, FindingRuleInput, FindingStatusInput, DeclaredMigration, CheckSubject, EntityGrantShape } from '@substrat-run/contracts';
9
12
  import type { ConnectionUseOutcome } from './connector-calls.js';
10
13
  import type { CapabilityVerbs } from './capability.js';
14
+ import type { AttachmentVerbs } from './upload-link.js';
11
15
  import type { AuditedOperationRef, AuditedOperationRow } from './audit-outcome.js';
12
16
  import { type EntityStateName } from '@substrat-run/contracts';
13
17
  import type { ModelUsageFilter, ModelUsageInput, ModelUsageWindow } from './model-usage.js';
@@ -577,6 +581,14 @@ export interface OperationContext {
577
581
  * like `ctx.grant`.
578
582
  */
579
583
  readonly capabilities: CapabilityVerbs;
584
+ /**
585
+ * Attachment verbs an operation may use — today, minting a single-use URL through which a
586
+ * caller with no session uploads ONE file to an entity (`mintUploadUrl`). The bytes
587
+ * themselves never ride an operation: they go to the redemption route, which writes them
588
+ * through `ScopeHost.attachments`' own upload as the principal who minted the link.
589
+ * See {@link AttachmentVerbs}.
590
+ */
591
+ readonly attachments: AttachmentVerbs;
580
592
  /**
581
593
  * Run `fn` as a SUB-TRANSACTION of this operation (#770,
582
594
  * docs/architecture/sub-transactions.md) — the boundary that makes catching an
@@ -800,6 +812,23 @@ export interface SqlMigration {
800
812
  /** Ordered, unique per module, e.g. '0001-init'. Journaled per (module, version). */
801
813
  version: string;
802
814
  sql: string;
815
+ /**
816
+ * Data this migration MOVES, verified (#115): `{ source: target }`, both `table` or both
817
+ * `table.column`. The kernel refuses a migration that loses rows or values a scope holds
818
+ * (`migration-fence.ts`); a move excuses the source's loss only when the target gained at least
819
+ * what the source held, and a table moved whole only when each of its columns that held values
820
+ * arrives under the same name or is declared by name. Reviewed in the migration diff beside the SQL, and not part of the
821
+ * SQL's digest. The planner never writes it: a generated migration loses nothing.
822
+ */
823
+ moves?: Readonly<Record<string, string>>;
824
+ /**
825
+ * Data this migration deliberately LOSES (#115): `{ target: why }`, where target is `table`
826
+ * (its rows, or the table itself) or `table.column` (its values, or their storage class — a
827
+ * deliberate retype, a widening included, is declared here). The
828
+ * reason is required and is what a reviewer reads. Anything lost and not declared here or in
829
+ * `moves` refuses the migration in a scope that holds it.
830
+ */
831
+ discards?: Readonly<Record<string, string>>;
803
832
  }
804
833
  /**
805
834
  * The deployed migration frontier — what "up to date" means for THIS host build
@@ -2058,6 +2087,9 @@ export interface HostAdmin {
2058
2087
  * permission diffs are the admission gates, and binding a scope is a separate step
2059
2088
  * (`bindScopeVersion`), so the two human checkpoints fire where the blast radius is
2060
2089
  * rather than where the typing was.
2090
+ *
2091
+ * Refuses (`conflict`) a version that declares a scope-store binding the vertical already
2092
+ * declared as another driver (#2286): see `verticalScopeStores`.
2061
2093
  */
2062
2094
  publishVersion(actor: PlatformActorId, input: PublishVersionInput): Promise<void>;
2063
2095
  /** Ordered by id (ULID = publish order), `asc` unless `page.order` says otherwise. */
@@ -2203,6 +2235,16 @@ export interface HostAdmin {
2203
2235
  * (`bindExportBreaksOf`), asked of one install. Judged on what the scope RUNS before and
2204
2236
  * after, so re-pointing a scope that runs its vertical's serving script, a fork, a preview,
2205
2237
  * or a first bind is never refused. `bindingImpact` is the listing.
2238
+ *
2239
+ * **#2347: a rebind across lineages moves the route and the pointer in ONE write.**
2240
+ * `opts.servingRef` routes the scope onto that serving script in the same write as the bind
2241
+ * (judged for what the scope runs there, as `bindingImpact` with `servingRef` is), and
2242
+ * `opts.expectedServing` makes that write conditional on the target lineage's serving script
2243
+ * still running the version the rebind was judged against. Two writes would leave a window
2244
+ * where a promote on the target lands between them: the route on the new script, the pointer
2245
+ * on the old lineage. When `expectedServing` is given, losing either compare-and-set (it, or
2246
+ * `expectedVersionId`) is refused as `rebind_review_stale`. Both options are additive: a
2247
+ * caller that sends neither binds exactly as before.
2206
2248
  */
2207
2249
  bindScopeVersion(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, versionId: string, opts?: {
2208
2250
  snapshot?: boolean;
@@ -2211,6 +2253,14 @@ export interface HostAdmin {
2211
2253
  expectedErasureEpoch?: number;
2212
2254
  /** #1722: the copy move this bind routes the scope onto, confirmed in the same write. */
2213
2255
  confirmMove?: ScopeCopyMoveConfirmation;
2256
+ /** #2347: the serving script the scope routes to from this same write. */
2257
+ servingRef?: string;
2258
+ /** #2347: what the target lineage's serving script must still run for the write to land. */
2259
+ expectedServing?: {
2260
+ vertical: string;
2261
+ ref: string;
2262
+ versionId: string;
2263
+ };
2214
2264
  }): Promise<void>;
2215
2265
  /**
2216
2266
  * Which apps in the scope's tenant binding `versionId` would break (#1756): the answer
@@ -2269,13 +2319,24 @@ export interface HostAdmin {
2269
2319
  * version can be bound per-version but never served in place.
2270
2320
  */
2271
2321
  versionManifest(actor: PlatformActorId, verticalSlug: string, versionId: string): Promise<string | null>;
2322
+ /**
2323
+ * Every scope-store binding any version of the vertical has declared, with its driver (#2286,
2324
+ * RFC scope-stores L17): what export, wipe, erasure and the meter reach, so an object under a
2325
+ * binding a later version dropped is still reached. Read by its keys, with no per-version work
2326
+ * once the vertical is indexed. A binding keeps its driver for the life of the vertical:
2327
+ * `publishVersion` refuses a version that moves one to another driver, or declares one twice (`conflict`).
2328
+ * Refuses (`unavailable`) when a version's manifest cannot be read, since that version may
2329
+ * declare a store nobody can name. Empty for an unknown vertical.
2330
+ */
2331
+ verticalScopeStores(actor: PlatformActorId, verticalSlug: string): Promise<VerticalScopeStore[]>;
2272
2332
  /**
2273
2333
  * One version's SQL migrations (#1764), in the order the host runs them. They are stored
2274
2334
  * apart from the manifest, which `versionManifest` returns without them. Null for a version
2275
2335
  * that carries none that can be shown (pushed before #1677, or over the push's caps), which
2276
2336
  * a reader must show as "not available", never as "no migrations". `[]` is a version whose
2277
2337
  * modules ship none. Refuses a version of another vertical, like `versionManifest`.
2278
- * Access-logged: an owner read for the promote review, never a bulk or background path.
2338
+ * Access-logged. Read by the promote review, and by every serving upload, which injects the
2339
+ * digest of the served version's migrations (#2349) — one read per upload, never a scan.
2279
2340
  */
2280
2341
  versionMigrations(actor: PlatformActorId, verticalSlug: string, versionId: string): Promise<DeclaredMigration[] | null>;
2281
2342
  /**
@@ -2461,8 +2522,15 @@ export interface HostAdmin {
2461
2522
  * the append-only admin log (K-21 protects tuples, not the route table).
2462
2523
  * Idempotent: unbinding an unknown hostname is a silent no-op, not an error —
2463
2524
  * so a cleanup pass can re-run over a partial failure.
2525
+ *
2526
+ * `owner` (#2285): remove the binding only while it still names this tenant and scope,
2527
+ * decided by the delete itself. A name rebound to another scope since the caller listed it
2528
+ * is left bound to that scope, and the call is a silent no-op, as for an unknown name.
2464
2529
  */
2465
- unbindHostname(actor: PlatformActorId, hostname: string): Promise<void>;
2530
+ unbindHostname(actor: PlatformActorId, hostname: string, owner?: {
2531
+ tenantId: TenantId;
2532
+ scopeId: ScopeId;
2533
+ }): Promise<void>;
2466
2534
  /**
2467
2535
  * Ordered by hostname; the cursor is a hostname.
2468
2536
  *
@@ -2587,6 +2655,154 @@ export interface HostAdmin {
2587
2655
  tenantId?: TenantId;
2588
2656
  vertical?: string;
2589
2657
  }): Promise<BlobStoreRecord[]>;
2658
+ /**
2659
+ * Record that a store driver's release bytes are stored, keyed by their sha256 (D-63, #2300).
2660
+ * Insert-only: the same sha256 again answers the row as it stands, and from another source
2661
+ * commit is refused (`conflict`). Audited `uploadStoreDriverRelease` when it writes.
2662
+ */
2663
+ recordStoreDriverRelease(actor: PlatformActorId, input: {
2664
+ driver: string;
2665
+ sha256: string;
2666
+ sourceCommit: string;
2667
+ }): Promise<StoreDriverReleaseRecord>;
2668
+ /** One stored release, or undefined. */
2669
+ getStoreDriverRelease(actor: PlatformActorId, driver: string, sha256: string): Promise<StoreDriverReleaseRecord | undefined>;
2670
+ /**
2671
+ * A driver's current good release, every rollout of it, oldest first, and where a rollback goes.
2672
+ * A `rolling` row whose holder holds the driver no longer reads as `diverged`.
2673
+ */
2674
+ storeDriverState(actor: PlatformActorId, driver: string): Promise<StoreDriverState>;
2675
+ /**
2676
+ * Every vertical any version of which declared a store of `driver`, with those bindings: the
2677
+ * scripts a rollout reaches, and in `unreadable` every vertical that cannot say (a manifest that
2678
+ * does not parse, or versions from before the record that moved a binding to another driver): the caller that
2679
+ * must reach every script refuses while it is not empty.
2680
+ */
2681
+ storeDriverDeclarations(actor: PlatformActorId, driver: string): Promise<{
2682
+ verticals: {
2683
+ vertical: string;
2684
+ stores: VerticalScopeStore[];
2685
+ }[];
2686
+ unreadable: string[];
2687
+ }>;
2688
+ /**
2689
+ * What the next request of a rollout of `sha256` (or a rollback to it) walks (#2312): the first
2690
+ * `limit` verticals, in (vertical, binding) order, holding a declaration of the driver the attempt
2691
+ * it would continue has not walked yet; `pending` counts them all and `walked` the verticals that
2692
+ * attempt walked. A request that continues no attempt (the rows before it are not `paused` at the
2693
+ * same release and action) walks every declaration. `unreadable` as `storeDriverDeclarations`.
2694
+ */
2695
+ storeDriverRolloutPending(actor: PlatformActorId, input: {
2696
+ driver: string;
2697
+ sha256: string;
2698
+ action: StoreDriverRolloutAction;
2699
+ limit: number;
2700
+ }): Promise<{
2701
+ verticals: StoreDriverRolloutTarget[];
2702
+ pending: number;
2703
+ walked: number;
2704
+ unreadable: string[];
2705
+ }>;
2706
+ /**
2707
+ * What the attempt of the rollout at `seq` walked in its earlier requests, in (vertical, binding)
2708
+ * order, at most `limit` (#2312): what a failure part way returns besides its own request's.
2709
+ */
2710
+ storeDriverRolloutWalked(actor: PlatformActorId, input: {
2711
+ driver: string;
2712
+ seq: number;
2713
+ limit: number;
2714
+ }): Promise<{
2715
+ verticals: StoreDriverRolloutTarget[];
2716
+ walked: number;
2717
+ }>;
2718
+ /**
2719
+ * Take or renew a lease on a driver (#2300): `exclusive` for a rollout or rollback, `shared` for
2720
+ * a push or serve rebuilding a script. False when a live hold of another holder excludes it.
2721
+ * `renew` extends only a hold the holder still holds (a lapsed one answers false, never a fresh
2722
+ * hold), and `rolloutSeq` also needs that rollout to be `rolling` still.
2723
+ */
2724
+ holdStoreDriver(actor: PlatformActorId, input: {
2725
+ driver: string;
2726
+ holder: string;
2727
+ mode: StoreDriverHoldMode;
2728
+ ttlMs: number;
2729
+ renew?: boolean;
2730
+ rolloutSeq?: number;
2731
+ }): Promise<boolean>;
2732
+ releaseStoreDriverHold(actor: PlatformActorId, input: {
2733
+ driver: string;
2734
+ holder: string;
2735
+ }): Promise<void>;
2736
+ /**
2737
+ * Begin a rollout (or a rollback) of a stored release, as the driver's exclusive holder; refused
2738
+ * (`conflict`) otherwise. A rollback must name `storeDriverState`'s `rollbackTarget`. Audited
2739
+ * under `rollOutStoreDriver` / `rollBackStoreDriver`.
2740
+ */
2741
+ beginStoreDriverRollout(actor: PlatformActorId, input: {
2742
+ driver: string;
2743
+ sha256: string;
2744
+ action: StoreDriverRolloutAction;
2745
+ holder: string;
2746
+ }): Promise<StoreDriverRolloutRecord>;
2747
+ /**
2748
+ * End a rollout as its exclusive holder; a `good` end makes its release current. Audited as its
2749
+ * begin. A `paused` end records `walked`, what its request walked, in the attempt's ledger, in
2750
+ * the same transaction (#2312); any other end drops the ledger, and `walked` with it is refused.
2751
+ */
2752
+ finishStoreDriverRollout(actor: PlatformActorId, input: {
2753
+ driver: string;
2754
+ seq: number;
2755
+ holder: string;
2756
+ state: Exclude<StoreDriverRolloutState, 'rolling'>;
2757
+ detail: string | null;
2758
+ walked?: readonly StoreDriverRolloutTarget[];
2759
+ }): Promise<StoreDriverRolloutRecord>;
2760
+ /** The platform's rebuild of every script of a driver (#2309), or undefined before its first. */
2761
+ storeDriverRebuild(actor: PlatformActorId, driver: string): Promise<StoreDriverRebuildRecord | undefined>;
2762
+ /**
2763
+ * Take the lease on a driver's fleet rebuild towards `fingerprint` (#2309): the rebuild as it
2764
+ * stands, started over when it was towards another fingerprint (or, with `restart`, when it is
2765
+ * done; with `attestedBy`, always, carrying that attestation), or null while another holder's
2766
+ * lease is live.
2767
+ */
2768
+ claimStoreDriverRebuild(actor: PlatformActorId, input: {
2769
+ driver: string;
2770
+ fingerprint: string;
2771
+ holder: string;
2772
+ ttlMs: number;
2773
+ restart?: boolean;
2774
+ attestedBy?: string;
2775
+ }): Promise<StoreDriverRebuildRecord | null>;
2776
+ /**
2777
+ * Record the lease holder's progress and renew its lease (or drop it with `release`); refused
2778
+ * (`conflict`) once another pass claimed the row or it was restarted at another fingerprint.
2779
+ */
2780
+ advanceStoreDriverRebuild(actor: PlatformActorId, input: {
2781
+ driver: string;
2782
+ fingerprint: string;
2783
+ holder: string;
2784
+ cursor: string | null;
2785
+ failures: readonly StoreDriverRebuildFailure[];
2786
+ done: boolean;
2787
+ /** With `done`: the walk proved every script current. */
2788
+ verified?: boolean;
2789
+ ttlMs: number;
2790
+ release?: boolean;
2791
+ }): Promise<StoreDriverRebuildRecord>;
2792
+ /**
2793
+ * Audit a staff-run pass of a driver's fleet rebuild (#2309) as `rebuildStoreDriver`: the driver,
2794
+ * how the pass ended and what it walked, and `deployFinished` when the person attested that no
2795
+ * control-plane deploy was rolling out. The scheduled pass writes no admin row; its failures are
2796
+ * ops-failure rows.
2797
+ */
2798
+ recordStoreDriverRebuildRun(actor: PlatformActorId, entry: {
2799
+ driver: string;
2800
+ outcome: string;
2801
+ walked: readonly string[];
2802
+ uploaded: readonly string[];
2803
+ error?: string;
2804
+ deployFinished?: true;
2805
+ }): Promise<void>;
2590
2806
  /**
2591
2807
  * One scope's directory record. Cross-checks the (tenantId, scopeId) pair and
2592
2808
  * returns undefined on a mismatch rather than another tenant's scope (K-3) —
@@ -3019,8 +3235,13 @@ export interface HostAdmin {
3019
3235
  * A scope store is reached, by `substrat.erase`, after the scope-side redaction and
3020
3236
  * before the key is destroyed. A store that answers with an error throws, and the key
3021
3237
  * stays, so a retry converges. The receipt's `storeErasures` names each binding.
3238
+ *
3239
+ * `fence` (#2285): the scope state the caller read before it erased the platform's stores. Given,
3240
+ * the shred takes the same claim `finalizeSubjectShred` takes, just before the key goes, and
3241
+ * refuses (`precondition_failed`) with the key kept when the scope moved. Omitted, nothing is
3242
+ * claimed: the caller reached no store outside this host.
3022
3243
  */
3023
- shredSubject(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, subjectId: string): Promise<SubjectShredReceipt>;
3244
+ shredSubject(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, subjectId: string, fence?: SubjectErasureFence): Promise<SubjectShredReceipt>;
3024
3245
  /**
3025
3246
  * Subjects whose key row is tombstoned (`shredded_at` set), in subject-id order.
3026
3247
  * A page is at most 100. `cursor` is the last id the caller already has.
@@ -3034,12 +3255,7 @@ export interface HostAdmin {
3034
3255
  /** Finish an orchestrated erasure only after every script holding a copy has redacted it.
3035
3256
  * The copy count fences a move that was recorded after the coordinator listed scripts;
3036
3257
  * ledger rows are append-only, so a changed count means that inventory is stale. */
3037
- finalizeSubjectShred(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, subjectId: string, redactions: readonly SubjectRedactionCounts[], expected: {
3038
- versionId: string | null;
3039
- servingRef: string | null;
3040
- epoch: number;
3041
- copyCount: number;
3042
- },
3258
+ finalizeSubjectShred(actor: PlatformActorId, tenantId: TenantId, scopeId: ScopeId, subjectId: string, redactions: readonly SubjectRedactionCounts[], expected: SubjectErasureFence,
3043
3259
  /**
3044
3260
  * What the serving script's stores erased (#2068). The coordinator calls
3045
3261
  * `substrat.erase` once — store objects are per vertical, not per script copy —
@@ -3565,6 +3781,14 @@ export interface HostAdmin {
3565
3781
  * cannot be written.
3566
3782
  */
3567
3783
  recordCopyMark(actor: PlatformActorId, entry: CopyMarkAudit): Promise<void>;
3784
+ /**
3785
+ * Record one pass of the platform's orphan-attachment sweep over one scope (#2314) on the admin
3786
+ * log, as `sweepOrphanAttachments`. The pass runs in the vertical's deployment, so the platform's
3787
+ * sweep writes this after it, when the pass found or deleted an orphan. A dry-run row is also
3788
+ * what licenses a later destructive pass on the scope (`OrphanAttachmentSweepOptions`).
3789
+ * `recordCopyMark`'s shape. Throws when the row cannot be written.
3790
+ */
3791
+ recordOrphanSweep(actor: PlatformActorId, entry: OrphanSweepAudit): Promise<void>;
3568
3792
  /**
3569
3793
  * Record one phase of a dashboard member change (#1150) — an invite, a role move or a removal
3570
3794
  * on an installed vertical's scope — as `manageScopeMember`. The change runs in the vertical's
@@ -3574,6 +3798,7 @@ export interface HostAdmin {
3574
3798
  */
3575
3799
  recordMemberChange(actor: PlatformActorId, entry: MemberChangeAudit): Promise<void>;
3576
3800
  /** One intent or outcome of a tenant's peer-instance binding change (#1720). */
3801
+ recordConnectionBindingChange(actor: PlatformActorId, entry: ConnectionBindingAudit): Promise<void>;
3577
3802
  recordPeerBindingChange(actor: PlatformActorId, entry: PeerBindingAudit): Promise<void>;
3578
3803
  /**
3579
3804
  * Close one audited change whose intent has no outcome (#2064): in ONE transaction, read the
@@ -3863,6 +4088,28 @@ export interface AttachmentUploadInput {
3863
4088
  visibility: Visibility;
3864
4089
  body: Uint8Array;
3865
4090
  }
4091
+ /**
4092
+ * Input to `ScopeHost.redeemUploadLink`. No `visibility`: the link carries it, as it carries
4093
+ * the author. `entity` is what the URL named, and must be the link's own.
4094
+ */
4095
+ export interface UploadLinkRedeemInput {
4096
+ entity: EntityRef;
4097
+ filename: string;
4098
+ contentType: string;
4099
+ body: Uint8Array;
4100
+ /**
4101
+ * #2315: the redeemer's own id for this redemption (the route reads `Idempotency-Key`). The
4102
+ * spend records its hash; a later redemption of the SAME link with the same key, the same
4103
+ * bytes and inside the link's lifetime plus `UPLOAD_LINK_REPLAY_GRACE_SECONDS` gets the stored
4104
+ * record back (`replayed: true`) and writes nothing. Without it, a spent link is 410 forever.
4105
+ */
4106
+ idempotencyKey?: string;
4107
+ }
4108
+ /** What `ScopeHost.redeemUploadLink` answers: the record, and whether it was stored by an earlier request. */
4109
+ export type RedeemedUploadLink = AttachmentRecord & {
4110
+ /** Present only on a replay (#2315): the file was stored by the request that spent the link, and nothing was written now. */
4111
+ replayed?: true;
4112
+ };
3866
4113
  /** An opened attachment: the metadata fact plus the bytes it witnesses. */
3867
4114
  export interface OpenedAttachment {
3868
4115
  record: AttachmentRecord;
@@ -4392,6 +4639,51 @@ export interface ScopeHost {
4392
4639
  * extraction job has no reason to mutate attachment metadata or bytes.
4393
4640
  */
4394
4641
  getSystemAttachments(moduleId: ModuleId, tenantId: TenantId, scopeId: ScopeId): Promise<Pick<ScopeAttachments, 'open'>>;
4642
+ /**
4643
+ * Look an upload link up WITHOUT spending it, so the route can refuse an oversize body or a
4644
+ * wrong type before it reads a byte. `not_found` for a secret that names nothing (and for
4645
+ * one that cannot be ours); `not_found` with reason `upload_link_wrong_target` when `target`
4646
+ * is given and the link was minted for another, judged before its state, so a wrong path is
4647
+ * the same 404 for a pending link and a spent one; `conflict` with reason `upload_link_gone`
4648
+ * for one that is used, expired or revoked. The mint is `ctx.attachments.mintUploadUrl`.
4649
+ *
4650
+ * Optional, like `getCapabilityAttachments`: a host that has not wired the link table says so
4651
+ * by leaving it out, and `mountUploadLinkRedeem` refuses to mount over one.
4652
+ */
4653
+ inspectUploadLink?(tenantId: TenantId, scopeId: ScopeId, secret: string, target?: EntityRef, idempotencyKey?: string): Promise<UploadLinkView>;
4654
+ /**
4655
+ * Redeem an upload link: write the bytes as ONE attachment, AS the principal who minted the
4656
+ * link — the target's `writePermission` is checked as them now, they are the author, and
4657
+ * `attachment.added` is emitted exactly as for any upload. The link is spent in the same
4658
+ * transaction as the attachment row, so a redemption that fails anywhere leaves it unspent;
4659
+ * the size and the type are the LINK's to refuse (`validation_failed`, reasons
4660
+ * `upload_link_too_large` / `upload_link_type_refused`) and a link used by another request
4661
+ * first is `conflict` / `upload_link_gone`. The bytes are written to the blob store before
4662
+ * the row, as an upload's are, and removed again if the row is refused.
4663
+ *
4664
+ * #2315: with `input.idempotencyKey`, a link spent by a redemption that carried the same key
4665
+ * is a REPLAY — `inspectUploadLink` with that key answers the view instead of 410, and this
4666
+ * answers the stored record with `replayed: true` once the body matches it by size and
4667
+ * SHA-256 (`conflict` / `upload_link_replay_mismatch` when it does not). A replay puts no
4668
+ * bytes, writes no row and emits nothing. A retry that races the first request and loses the
4669
+ * spend has its own bytes removed and is answered as a replay too.
4670
+ */
4671
+ redeemUploadLink?(tenantId: TenantId, scopeId: ScopeId, secret: string, input: UploadLinkRedeemInput): Promise<RedeemedUploadLink>;
4672
+ /**
4673
+ * One orphan-sweep pass over a scope's attachment bytes (#2314): delete the objects under the
4674
+ * scope's attachment prefix that no `_substrat_attachments` row names, once they are older than
4675
+ * the grace period and the scope is clear of its restore fence. The scope tombstones each id
4676
+ * before its object is deleted, and the attachment insert refuses a tombstoned id, so a row
4677
+ * that commits never loses its bytes (`sweepOrphanAttachments`, attachment-sweep.ts).
4678
+ *
4679
+ * Platform-only, and judged by THIS host's copy of the scope: only an active primary of
4680
+ * `tenantId` that this host holds is swept, and anything else refuses (`conflict`), as does a
4681
+ * scope with an unsettled copy in another script. On a host whose scopes live in several
4682
+ * deployments, the caller asks only the deployment serving the scope. The age floor and the
4683
+ * fence are judged against the host's own clock, never the caller's.
4684
+ * Optional: a host that has not wired the sweep leaves it out.
4685
+ */
4686
+ sweepOrphanAttachments?(tenantId: TenantId, scopeId: ScopeId, request?: OrphanSweepRequest): Promise<OrphanSweepReport>;
4395
4687
  /**
4396
4688
  * Provision a NEW scope and load a `ScopeDump` into it — the write side of
4397
4689
  * `exportScope` and the fork primitive (docs/architecture/preview-and-snapshots.md §3):