Object Store Helper
AppTheory exposes a narrow bounded object-store helper for framework-owned byte payload storage across Go, TypeScript, and Python. It is intentionally small: one strict object reference type, one bounded store interface, one S3-backed implementation per runtime, and deterministic local fakes for contract tests.
This helper exists so AppTheory-owned code paths can share one fail-closed S3 access pattern instead of reimplementing S3 get/put/delete, URL parsing, encryption headers, and bounded reads in each package. It is not a general storage SDK.
Surface
ObjectRefidentifies exactly one object (bucket,key, optional version ID).ParseObjectRef/parseObjectRef/parse_object_refaccepts only stricts3://bucket/keyreferences.- Bucket and key are required.
- No default bucket or default key is inferred.
- Query strings and fragments are rejected.
- Valid bucket and key values are preserved exactly; the parser does not normalize or URL-decode them.
- The store supports only:
- Put
- bounded Get with a required positive byte cap
- Delete
- Local fakes provide call recording, failure injection, and copy-on-write safety:
- Go:
testkit/objectstore.NewStore() - TypeScript:
createFakeObjectStore()/FakeObjectStore - Python:
create_fake_object_store()/FakeObjectStore
- Go:
S3 implementation
Create the S3 implementation through AppTheory, not by injecting an AWS client:
store, err := objectstore.NewS3Store(ctx, objectstore.S3StoreConfig{
Encryption: objectstore.S3EncryptionConfig{Mode: objectstore.S3EncryptionS3Managed},
})
if err != nil {
return err
}
const store = await createS3ObjectStore({
encryption: { mode: S3Encryption.S3Managed },
});
store = create_s3_object_store(
S3ObjectStoreConfig(encryption=S3EncryptionConfig(mode=S3_ENCRYPTION_S3_MANAGED))
)
Each runtime keeps the cloud-client seam private to AppTheory tests and exposes only the bounded ObjectStore contract.
Dependency posture
The runtime dependency posture is deliberately asymmetric but explicit:
- TypeScript:
@aws-sdk/client-s3is a hard package dependency becausets/src/objectstore.tsimports the S3 client at module load. GitHub Release consumers get the S3 implementation with the package, but the public surface still exposes only boundedput/get/deleteoperations. - Python:
boto3is optional and lazy. Importingapptheory, parsing object refs, and using the fake store do not require boto3.create_s3_object_storefails closed withObjectStoreError(objectstore.invalid_store_config)when boto3 or the requiredput_object/get_object/delete_objectmethods are unavailable. - Go: AWS SDK dependencies are compiled into the Go helper through the normal module dependency graph.
This asymmetry is a distribution policy choice only. It does not add a second object-store contract and it does not permit raw S3 client injection or exposure.
Bounded reads
Every Get call must provide a positive byte cap:
out, err := store.Get(ctx, objectstore.GetInput{
Ref: ref,
MaxBytes: 1 << 20,
})
If the object body would exceed the cap, the helper returns the runtime’s stable objectstore.object_too_large error.
There is no unbounded read method.
Fail-closed encryption
S3 encryption configuration is validated before any S3 operation can be built:
- Bucket-default mode emits no SSE header and relies on the bucket’s default encryption policy.
- S3-managed mode emits the S3-managed AES256 SSE header.
- KMS mode emits the AWS KMS SSE header and requires a KMS key ID.
Invalid combinations fail closed with the runtime’s stable objectstore.invalid_encryption_config error:
- KMS mode without a key.
- A blank or whitespace-padded KMS key.
- A KMS key supplied for bucket-default or S3-managed mode.
- Any unknown encryption mode.
There is no silent fallback from KMS to bucket-default or S3-managed encryption.
Non-goals
The helper deliberately does not provide:
- listing
- presigning
- public URLs
- multipart upload
- copy or head operations
- raw S3 client injection or exposure
- client-side encryption
- product-specific schemas such as TheoryMCP records
If an AppTheory-owned code path needs a new object-store behavior, grow this contract with fixtures and all three runtime implementations instead of bypassing it with package-local S3 calls.