feat: registered-blob family contract — declared index blobs are undeletable (ADR-004 Pass 2)

The class-killer for the lost-main.dkann incident (ADR-004 §7). A provider can
declare a derived-index blob FAMILY (a set of members that are load-bearing
together, e.g. vector-base = main.dkann + main.slotmap + main.slotrev). Once
declared:

- deleteBinaryBlob / removeRawPrefix REFUSE to remove a declared member, throwing
  the new ProtectedArtifactError — an in-process GC / sweeper is now INCAPABLE of
  deleting a load-bearing index file (COLD != DEAD). Intentional retirement is an
  explicit unregisterDerivedFamily(name) first.
- The declaration persists to _system/derived-artifacts.json, so protection
  survives a reopen; clear() resets it with the rest of the derived footprint.
- checkDerivedFamiliesPresent() names any member missing on open (the catch for an
  EXTERNAL deleter that bypasses the in-process refusal) → rebuild from canonical.
- Transients (*.tmp.*, *.rebuild-tmp, *.rotate-tmp) are never protected; a
  namespace family protects a growing prefix (seg-*).

New StorageAdapter surface (optional): registerDerivedFamily / unregisterDerivedFamily
/ listDerivedFamilies + DerivedFamilyDeclaration; new exported errors
ProtectedArtifactError / DerivedArtifactMissingError. Enforcement is inert until a
provider declares a family (no regression). Cor declares its 6 families + does the
atomic set-swap in 3.0.15 (M2); brainy builds the contract now. 8 tests.
This commit is contained in:
David Snelling 2026-07-13 14:52:06 -07:00
parent 6bcb54f0d9
commit bfa1762107
7 changed files with 429 additions and 5 deletions

View file

@ -15,6 +15,8 @@ export type BrainyErrorType =
| 'GRAPH_INDEX_NOT_READY'
| 'METADATA_INDEX_NOT_READY'
| 'VECTOR_INDEX_NOT_READY'
| 'PROTECTED_ARTIFACT'
| 'DERIVED_ARTIFACT_MISSING'
| 'MIGRATION_IN_PROGRESS'
/**
@ -307,6 +309,60 @@ export class VectorIndexNotReadyError extends BrainyError {
}
}
/**
* Thrown when a delete (`deleteBinaryBlob` / `removeRawPrefix`) would remove a
* blob that is a declared member of a protected derived-index FAMILY (ADR-004 §7
* registered-blob contract). Declared derived artifacts are undeletable through
* the storage layer this makes an in-process GC / sweeper INCAPABLE of removing
* a load-bearing index file (the lost-`main.dkann` class). Intentional retirement
* is the explicit `unregisterDerivedFamily(name)` step, then the delete.
*/
export class ProtectedArtifactError extends BrainyError {
/** The blob key the delete targeted. */
public readonly key: string
/** The protected family the key belongs to. */
public readonly family: string
constructor(key: string, family: string) {
super(
`Refused to delete '${key}': it is a declared member of the protected ` +
`derived-index family '${family}'. Declared derived artifacts are undeletable ` +
`through the storage layer (COLD ≠ DEAD) — unregisterDerivedFamily('${family}') ` +
`first if retirement is intentional.`,
'PROTECTED_ARTIFACT',
false
)
this.name = 'ProtectedArtifactError'
this.key = key
this.family = family
}
}
/**
* Raised (loudly) when a declared derived-index family is missing one or more of
* its members on open i.e. a load-bearing blob was deleted OUTSIDE the write
* path (an external sweeper the in-process refusal cannot stop). The index must
* be rebuilt from canonical; "healthy-while-broken" is impossible because the
* missing member is named, not silently tolerated.
*/
export class DerivedArtifactMissingError extends BrainyError {
/** The family with missing members. */
public readonly family: string
/** The member keys that are absent. */
public readonly missing: string[]
constructor(family: string, missing: string[]) {
super(
`Derived-index family '${family}' is missing ${missing.length} declared ` +
`member(s) on open (${missing.join(', ')}) — deleted outside the write path. ` +
`The index must be rebuilt from canonical.`,
'DERIVED_ARTIFACT_MISSING',
false
)
this.name = 'DerivedArtifactMissingError'
this.family = family
this.missing = missing
}
}
/**
* Thrown when a data-plane read or write is issued against a brain that is
* running its one-time, automatic 7.x 8.0 on-disk upgrade the coordinated