edge.bridge
edge.bridge
Zone B — research-airlock bridge (BUILD-SPEC §16; Invariant 2 & 11).
The only component that touches the airlock S3 bucket. It carries de-identified criteria out and public literature in, as opaque JSON, over the filesystem handoff the sealed core writes. It has no vault handle and the core never imports it — network and private data never share a component.
ResearchBridge
dataclass
handoff
instance-attribute
s3
instance-attribute
bucket
instance-attribute
requests_prefix = 'requests/'
class-attribute
instance-attribute
results_prefix = 'results/'
class-attribute
instance-attribute
push_requests()
PUT every pending criteria request to S3 requests/. Returns pushed ids.
pull_results()
GET any finished literature results for pending ids; write them to the handoff.
sync_once()
One push + pull cycle. Returns (#pushed, #pulled).
run(*, poll_interval_s=5.0, max_cycles=None)
S3Client
Bases: Protocol
The minimal S3 surface the bridge needs — satisfied by a boto3 client or a fake.
Deliberately tiny: no delete, no bucket admin. The bridge can PUT requests and GET results and nothing else; cleanup is handled by the bucket lifecycle, not the bridge, so the bridge's IAM stays least-privilege (Invariant: tight IAM).
put_object(*, Bucket, Key, Body)
get_object(*, Bucket, Key)
list_objects_v2(*, Bucket, Prefix)
build_bridge(config=None)
Wire a real bridge from config. boto3 is imported LAZILY here so importing this module
(e.g. in tests with a fake S3 client) never requires boto3 — and so boto3 never appears
in the sealed core's dependency surface.
bridge
The research-airlock bridge (Zone B) — the ONLY component that touches S3 (§16).
It carries de-identified criteria OUT (handoff → S3 requests/) and public literature back
IN (S3 results/ → handoff), and does nothing else. Critically:
- It has no vault handle. It is constructed with a handoff directory and an S3 client — not the vault path, not any store. The private corpus is structurally unreachable here (Invariant 2): network and private data never share a component.
- It is a dumb pipe. It never parses the criteria or the papers; it moves opaque JSON bytes. So even a bug here cannot leak corpus content — there is no corpus to leak, and the outbound bytes are whatever the sealed core already de-identified.
corenever imports this module (the static import-firewall enforces it). The core reaches the bridge only through files on disk.
Lifecycle of one request
- core writes
handoff/requests/<id>.json(de-identified criteria). - bridge PUTs it to
s3://bucket/requests/<id>.json, records it pending (handoff/sent/). - cloud Lambda fetches public literature, writes
s3://bucket/results/<id>.json. - bridge GETs results for pending ids, writes
handoff/results/<id>.json, clears pending. - core reads
handoff/results/<id>.jsonand ranks inside the walls.
REQUESTS = 'requests'
module-attribute
RESULTS = 'results'
module-attribute
SENT = 'sent'
module-attribute
ResearchBridge
dataclass
handoff
instance-attribute
s3
instance-attribute
bucket
instance-attribute
requests_prefix = 'requests/'
class-attribute
instance-attribute
results_prefix = 'results/'
class-attribute
instance-attribute
push_requests()
PUT every pending criteria request to S3 requests/. Returns pushed ids.
pull_results()
GET any finished literature results for pending ids; write them to the handoff.
sync_once()
One push + pull cycle. Returns (#pushed, #pulled).
run(*, poll_interval_s=5.0, max_cycles=None)
build_bridge(config=None)
Wire a real bridge from config. boto3 is imported LAZILY here so importing this module
(e.g. in tests with a fake S3 client) never requires boto3 — and so boto3 never appears
in the sealed core's dependency surface.
protocol
Airlock wire layout (BUILD-SPEC §16). Mirrored, not imported, by the sealed core.
The bridge is a dumb pipe: it shuttles opaque JSON between the filesystem handoff and S3 and never interprets the payloads (it can read neither the vault nor the de-identified criteria's meaning — it just moves bytes). The only thing it needs to agree on with the core and the cloud fetcher is the key layout, kept here so an edit can't silently drift it:
handoff/requests/<id>.json <-> s3://<bucket>/requests/<id>.json (outbound)
handoff/results/<id>.json <-> s3://<bucket>/results/<id>.json (inbound)
Outbound is the firewall direction: the bridge only ever PUTs whatever the core placed in
requests/, which by construction is de-identified criteria (the corpus cannot reach here —
this component has no vault handle and core never imports it; Invariant 2 & 11).
S3Client
Bases: Protocol
The minimal S3 surface the bridge needs — satisfied by a boto3 client or a fake.
Deliberately tiny: no delete, no bucket admin. The bridge can PUT requests and GET results and nothing else; cleanup is handled by the bucket lifecycle, not the bridge, so the bridge's IAM stays least-privilege (Invariant: tight IAM).
put_object(*, Bucket, Key, Body)
get_object(*, Bucket, Key)
list_objects_v2(*, Bucket, Prefix)
request_key(prefix, request_id)
result_id_from_key(prefix, key)
results/abc.json -> abc; None if the key isn't a result object.