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:
parent
6bcb54f0d9
commit
bfa1762107
7 changed files with 429 additions and 5 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue