This table is the authoritative public error boundary for the currently
supported package and storage APIs (RC1, package identity 2.0.0rc1).
Boundary rule: every supported public operation surfaces only
methodfactory.domain.errors.MethodFactoryError subclasses (stable
machine-readable code per ADR-0008 / ADR-0012 §B). Raw
JSON/Unicode/recursion/type/sqlite3/OS exceptions are translated at the
public boundary. Internal primitives are documented below with their native
exception contract and are not part of the supported surface.
| Operation | Accepted inputs | Success result | Stable errors (code) | Native exceptions translated |
|---|---|---|---|---|
parse_envelope(raw) |
str (raw envelope, optional prose) |
ActionEnvelope |
InvalidEnvelopeError (INVALID_ENVELOPE) |
UnicodeEncodeError (lone surrogate), json.JSONDecodeError, RecursionError, TypeError (non-string) |
envelope_from_dict(d) |
dict |
ActionEnvelope |
InvalidEnvelopeError (INVALID_ENVELOPE) |
TypeError (non-dict), plus every envelope field failure |
validate_manifest(manifest) |
dict |
list[str] of violations ([] = valid) |
none raised; failures collected | TypeError, RecursionError, UnicodeEncodeError, ValueError (canonicalization -> collected error) |
action_sha256(**semantic) |
str/str/str/str + dict basis + dict payload |
str (64-hex digest) |
SerializationError (SERIALIZATION) |
TypeError, RecursionError, UnicodeEncodeError, ValueError (over MAX_ACTION_JSON_BYTES) |
ArtifactStore(root) |
str/os.PathLike root |
store object | InvalidPayloadError (INVALID_PAYLOAD), InvalidStoreRootError via path helpers |
TypeError/ValueError (root type), OSError (mkdir) |
ArtifactStore.put(package_id, logical_path, content) |
str/str/str | (digest: str, size_bytes: int) |
InvalidPayloadError (INVALID_PAYLOAD), InvalidPackageIdError (INVALID_PACKAGE_ID) |
UnicodeEncodeError (lone surrogate), OSError (open/write/fsync/link/unlink/dir-fsync/close) |
ArtifactStore.get(digest) |
str (64-hex) | str (decoded UTF-8) |
InvalidPayloadError (INVALID_PAYLOAD) |
OSError (read), UnicodeDecodeError, ValueError (bad digest) |
ArtifactStore.artifact_bytes(digest) |
str (64-hex) | bytes |
InvalidPayloadError (INVALID_PAYLOAD) |
OSError (read), ValueError (bad digest) |
ArtifactStore.verify(digest) |
str (64-hex) | bool (never raises) |
— | all failures collapse to False |
open_database(root, read_only=False) |
str/Path root + bool |
sqlite3.Connection (via close_database) |
DatabaseNotFoundError (DATABASE_NOT_FOUND), DatabaseEmptyError (DATABASE_EMPTY), DatabaseIdMismatchError (DATABASE_ID_MISMATCH), UnsupportedSchemaError (UNSUPPORTED_SCHEMA), LegacyStoreDetectedError (LEGACY_STORE_DETECTED), SchemaViolationError (SCHEMA_VIOLATION), StorageError (STORAGE_ERROR) |
sqlite3.Error, OSError, ValueError, TypeError -> StorageError; invalid root -> InvalidStoreRootError (INVALID_STORE_ROOT) |
latest_event(conn, package_id) |
sqlite3.Connection (from open_database) + str |
`dict | None` | ManifestInvalidError (MANIFEST_INVALID), StorageError (STORAGE_ERROR) |
explain_latest_event_plan(conn, package_id) |
sqlite3.Connection + str |
list[tuple] |
StorageError (STORAGE_ERROR) |
sqlite3.Error |
close_database(conn) |
sqlite3.Connection |
None |
StorageError (STORAGE_ERROR) |
sqlite3.Error |
SqliteManifestStore(root, *, artifact_store=None) |
str/Path root, optional ArtifactStore |
store object | InvalidStoreRootError (INVALID_STORE_ROOT), StorageError (STORAGE_ERROR) |
TypeError/ValueError (root), OSError, sqlite3.Error |
SqliteManifestStore.create(package_id, intent_raw, created_at=None) |
str + str + optional str | complete revision-0 manifest dict |
DuplicatePackageError (PACKAGE_EXISTS, non-replay duplicate), InvalidPayloadError (INVALID_PAYLOAD), InvalidPackageIdError (INVALID_PACKAGE_ID), ManifestInvalidError (MANIFEST_INVALID), ConcurrencyError (CONCURRENCY), StorageError (STORAGE_ERROR) |
TypeError/ValueError/UnicodeError/RecursionError, OSError, sqlite3.Error (incl. locked -> CONCURRENCY) |
Create identity (senior review 4882624484, A1):
created_atis SEMANTIC. It is normalized to UTC ISO-8601 (naive/offset-less timestamps rejected) and included in the canonicalcreate_packageaction payload, so it is part ofaction_sha256. Exact replay requires the same normalized instant; a repeat with an explicitly DIFFERENT instant isPACKAGE_EXISTS; a retry that omitscreated_atreplays using the stored creation time. This is a deliberate pre-release breaking change to the SQLite store format (revision-0action_jsonpayload now carriescreated_at); no stores were released under the earlier alpha format (the first release isv2.0.0-rc.1), so no migration is required — any pre-A1 test store must be recreated. The authoritative chain validator bindspayload.created_atto the indexed rowcreated_at. |SqliteManifestStore.apply(envelope)|dict(parsed Action Envelope) | complete resulting manifestdict|InvalidEnvelopeError(INVALID_ENVELOPE),InvalidPayloadError(INVALID_PAYLOAD),PackageNotFoundError(PACKAGE_NOT_FOUND),StaleActionError(STALE_ACTION),IllegalTransitionError(ILLEGAL_TRANSITION),GateUnsatisfiedError(GATE_UNSATISFIED),ActionIdConflictError(ACTION_ID_CONFLICT),SerializationError(SERIALIZATION),ArtifactVerificationError(ARTIFACT_VERIFICATION),ManifestInvalidError(MANIFEST_INVALID),ConcurrencyError(CONCURRENCY),StorageError(STORAGE_ERROR) |TypeError/ValueError/UnicodeError/RecursionError,OSError,sqlite3.Error(incl. locked ->CONCURRENCY) | |SqliteManifestStore.load(package_id)| str | complete current manifestdict|PackageNotFoundError(PACKAGE_NOT_FOUND),ManifestInvalidError(MANIFEST_INVALID),StorageError(STORAGE_ERROR) |TypeError/ValueError/UnicodeError/RecursionError,sqlite3.Error| |SqliteManifestStore.read_events(package_id)| str | orderedlist[dict](decoded action/manifest) |ManifestInvalidError(MANIFEST_INVALID),StorageError(STORAGE_ERROR) |sqlite3.Error, decode/JSON errors | |SqliteManifestStore.validate_chain(package_id, *, verify_artifacts=False)| str + bool |{package_id, events, valid}|ChainViolationError(CHAIN_VIOLATION),StorageError(STORAGE_ERROR) |sqlite3.Error, decode/JSON errors | |SqliteManifestStore.explain_latest_plan(package_id)| str |list[tuple](query plan) |StorageError(STORAGE_ERROR) |sqlite3.Error| |SqliteManifestStore.close()| — |None|StorageError(STORAGE_ERROR) |sqlite3.Error| |migrate_store(source_root, dest=None)| legacy store root str/Path + optional dest SQLite path | receiptdict(see ADR-0012 amendment §15) |LegacySourceInvalidError(LEGACY_SOURCE_INVALID),LegacyChainInvalidError(LEGACY_CHAIN_INVALID),MigrationIncompatibleError(MIGRATION_INCOMPATIBLE),SourceChangedError(SOURCE_CHANGED),MigrationPublishFailedError(MIGRATION_PUBLISH_FAILED),DestinationExistsError(DESTINATION_EXISTS),ConcurrencyError(CONCURRENCY, legacy.lockpresent), plus re-used public errors for current-boundary rejections |OSError,sqlite3.Error,json.JSONDecodeError,UnicodeError,TypeError/ValueError/RecursionError| |export_events(store_root, output, *, fmt=EVENTS_V1_FORMAT)| store root + optional output path + format (method-factory-events-v1|legacy-v012-jsonl) | event countint(events written to stdout or file) |StorageError(STORAGE_ERROR),DatabaseNotFoundError(DATABASE_NOT_FOUND),LegacyStoreDetectedError(LEGACY_STORE_DETECTED),DatabaseEmptyError(DATABASE_EMPTY),DatabaseIdMismatchError(DATABASE_ID_MISMATCH),UnsupportedSchemaError(UNSUPPORTED_SCHEMA) |sqlite3.Error,OSError,UnicodeDecodeError,json.JSONDecodeError| |LegacySource(root)| str/Path legacy v0.1.2 store root | validated read-only source object |LegacySourceInvalidError(LEGACY_SOURCE_INVALID),LegacyChainInvalidError(LEGACY_CHAIN_INVALID) |OSError,json.JSONDecodeError,UnicodeDecodeError|
Migration boundary rule (ADR-0012 amendment §17): every public migration failure stays within the Method Factory typed error boundary. Legacy-valid values that the CURRENT public boundary rejects (identifier grammar, logical-path grammar, intent/input/artifact/objective/ reason limits, control characters, duplicate event IDs, unrecoverable
cancel.reason) surface asMIGRATION_INCOMPATIBLE— never as raw envelope/engine/validator/sqlite errors. The frozen legacy reader (migrations.v012_jsonl) is read-only: it never repairs, truncates, reconciles, rewrites, acquires locks, or performs CAS/tail-repair.
| Code | Meaning |
|---|---|
LEGACY_SOURCE_INVALID |
recognized v0.1.2 layout missing/unsafe (symlink escape, missing dir, corrupt cache, invalid filename grammar) |
LEGACY_CHAIN_INVALID |
legacy history violates public v0.1.2 validation (revision sequence, state continuity, hash chain, snapshot digest, blob digest, cache mismatch) |
MIGRATION_INCOMPATIBLE |
legacy-valid value is not reconstructable or fails the current public boundary (see rule above) |
SOURCE_CHANGED |
semantic source identity changed between the BEFORE and AFTER inventory; nothing published |
MIGRATION_PUBLISH_FAILED |
durable publication (receipt/DB/fsync/final verification) failed |
DESTINATION_EXISTS |
final destination already exists; migration refuses to overwrite |
Legacy
.lockpresence reuses the existingCONCURRENCYsemantics: the migration refuses to start, reports the lock path, and never deletes, repairs, inspects PID, acquires, or includes it in source identity.
| Primitive | Native contract |
|---|---|
storage.serialization.canonical_json / canonical_bytes |
Raise TypeError (unsupported types), ValueError (NaN/Infinity), RecursionError (deep nesting); canonical_bytes additionally UnicodeEncodeError (lone surrogate) |
storage.serialization.canonical_bytes_bounded |
As above plus ValueError when canonical bytes exceed the bound |
storage.serialization.try_canonical_bytes_bounded |
Returns (bytes, None) or (None, error_str) — never raises |
storage.serialization.digest_* |
SHA-256 helpers; native contract inherited from canonical_bytes/encoding |
storage.sqlite._connect / _apply_or_verify_pragmas / _identity / _verify_schema / initialize_database / detect_presence / _open_database_impl |
Internal to open_database; sqlite3 errors may surface if called directly. _connect closes its connection on PRAGMA failure. |
storage.store._begin / _insert_event / _commit / _rollback / FAULT_HOOK |
Transaction seams for fault injection; internal. FAULT_HOOK is a test-only injection point (stage-keyed) |
One bounded BEGIN IMMEDIATE transaction:
- Validate the incoming Action Envelope (
envelope_from_dict) and compute the canonical semantic action bytes once (canonical_action_bytes). - Compute
action_sha256= hash of those exact bytes. - Look up
(package_id, action_id)BEFORE stale-revision rejection. - If the action ID exists: same hash → return the previously committed result (no insert); different hash →
ACTION_ID_CONFLICT. - Load the indexed latest event (primary-key lookup, never a history scan).
- Compare
expected_revisionto the authoritative current revision →STALE_ACTIONon mismatch. - Apply the deterministic transition (engine.next_manifest: legality → gate → mutation → revision/lineage).
- Produce the complete resulting manifest.
- Validate the manifest and chain invariants (single kernel).
- Write and verify every newly referenced artifact blob (immutable, content-addressed).
- Canonicalize stored action + manifest once.
- Insert exactly one immutable event row.
COMMIT.
On any pre-commit failure: rollback, no event inserted, no historical row mutated, prewritten content-addressed blobs are NOT deleted.
Validation failure handling is deliberate and split by surface:
validate_manifest()collects violations into alist[str](read-only validator; caller-friendly).parse_envelope()/envelope_from_dict()/action_sha256()/ArtifactStore/ storage helpers raise typed errors (first failure aborts the operation).
Both surfaces are stable; neither leaks raw native exceptions.
Recorded here so the acceptance is repository-durable (verified by the code-review verify lane on 2026-08-07; each is genuine in the code):
-
Action payload → manifest consequence cross-binding (A3): for revisions > 0 the semantic-action binding anchors the frozen field set,
protocol_version,package_id,action_id,action, and basis/payload object types, but NOT payload content against manifest consequence digests (e.g.record_inputcontent vsinputs[].content_sha256). A store-writer who recomputesaction_sha256could rewrite action payload content and still passvalidate_chain; the manifest's own digests are independently verified against the blob store. Not in the reviewer's explicit A3 "at minimum" list; recommended for the next invariant slice.CLOSED by the invariant closure (senior review 4885538290, Lane 1).
validate_chainnow reconstructs the normal internal ActionEnvelope from the stored canonicalaction_jsonand replays the SINGLE deterministic transition engine (engine.apply.next_manifest) over the predecessor manifest with the indexedevent_idand rowcreated_at; canonical equality with the stored resulting manifest proves every consequence-bearing field (input digest/size/path, objective, summary digest/size/preview/presented_at/confirmation incl. confirmed_at/operator_id/confirmed digest, artifact digest/byte_count/ path, state/revision/lineage, updated_at) with one rule — no per-action consequence validators. Adversarial tests recompute all immediate hashes so only the replay invariant can fail. -
Manifest created_at/updated_at ↔ row binding:
_bind_manifest_fieldsbinds package_id/revision/state and rev>0 lineage, but not the manifest's owncreated_at/updated_atto row timestamps (correct binding forcreated_atrequires threading the revision-0 row timestamp through the kernel walk). Residual; recommended with the above.CLOSED by the invariant closure (senior review 4885538290, Lane 1). Revision 0:
created_at == updated_at == row created_at == action payload.created_at. Revision > 0:updated_at == row created_atandcreated_at == revision-0 row created_at(threaded through the walk). Action-specific timestamps (presented_at,confirmed_at) are derived from the event timestamp by the transition and proven by replay — not bound indiscriminately. -
validate_chaindouble canonicalization (audit path): withcheck_schema=True, each event's manifest is canonicalized once by the A2 decode and again inside the schema validator. Bounded to the explicit audit path (not load/apply); the perf-1 single-pass fix coveredcheck_current_row_consistencyonly. Retained as an accepted performance residual (senior review 4885538290 §8: the audit-path double canonicalization may remain unless measurements show a meaningful problem). -
Test tamper-pattern duplication (nit):
test_transactional_store.pyandtest_chain_validator.pyeach carry an inline drop-triggers/UPDATE tamper pattern rather than a shared helper; cosmetic. Retained (the replay module keeps a self-contained local helper).