Context
BSFG stores large artifacts out-of-band and references them from facts. This creates a design question: should the boundary contract expose a single convenience operation that both uploads an artifact and appends the referencing fact, or should these remain separate primitives?
The architecture already distinguishes:
- artifact storage as object-store durability
- fact append as semantic log history
The decision must preserve conceptual clarity without making common client flows unnecessarily awkward.
Options Considered
| Option | Description | Benefits | Drawbacks |
|---|---|---|---|
| Single combined primitive only | Expose one core RPC that uploads the artifact and appends the fact in one operation. |
- simple client experience
- one obvious path for attachments
|
- mixes artifact durability with fact semantics
- hides two distinct failure modes behind one call
- encourages false expectations of atomicity
| | Best-effort wrapper as the only public API | Expose only a convenience wrapper that internally calls object storage and fact append separately. |
- pleasant client ergonomics
- server can enforce policy centrally
|
- core model becomes obscured
- wrapper semantics can be misunderstood as fundamental
- less explicit failure handling
|
| Separate core operations (Selected) | Keep PutObject and AppendFact as distinct primitives; allow wrappers only as optional conveniences outside the core contract. |
- preserves conceptual clarity
- makes failure modes explicit
- keeps the core boundary contract minimal
- matches the architecture’s fact-vs-artifact distinction
|
- two-step client flow
- orphaned uploads must be cleaned up if append never occurs
| | Artifact-first implicit append | Make object upload itself create a fact automatically. |
- very small client surface
- artifact provenance is automatic
|
- wrong abstraction boundary
- not every stored object implies a fact worth appending
- semantic control is lost
|
Decision
BSFG keeps artifact upload and fact append as separate core operations.
PutObject(blob) -> object store durability
AppendFact(ref) -> semantic history
The canonical boundary contract therefore remains:
AppendFact
FetchFacts
ConfirmReceipt
PutObject
A fact that references an artifact is appended only after the artifact reference is known. The reference must include sufficient integrity and retrieval metadata, such as bucket, key, digest, size, and media type.
Convenience wrappers such as AppendWithAttachment may exist in higher-level SDKs or service facades, but they are not part of the core BSFG architectural primitive.
Consequences
Benefits:
- the fact log remains semantically explicit
- artifact durability and fact durability are modeled separately
- operators can diagnose storage failures independently from append failures
- the core protocol stays narrow and principled
Tradeoffs:
- clients perform a two-step write path for large artifacts
- implementations need orphan cleanup for uploaded-but-unreferenced objects
- SDKs may add convenience layers that must not be mistaken for the core model