Skip to content

Commit 0c11971

Browse files
committed
refactor(backend): shape W11 review nits ahead of CR
- records() takes ContentScope like store(); limit defaults to None and resolves to the repository protocol's DEFAULT_RECORD_LIMIT — one definition of the bound, no drifting literal in the service protocol - read() docs state the whole-bytes return and its ~2x-blob peak as a deliberate v1 trade instead of claiming "streaming"; README matched - StoredContentRecord.env is required — no default environment exists outside a scope
1 parent 82b09a5 commit 0c11971

5 files changed

Lines changed: 46 additions & 20 deletions

File tree

src/backend/src/agentclaw/community/core/bot_config_manifest/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,9 @@ store behind all three consumers of the requirement (audit, delivery,
195195
twice is two audit events, not one row to overwrite. Dedup lives in the
196196
blob layer by content address; repetition in the log is the fact.
197197
- **`read(digest)` is the one read path**, shared by delivery and audit:
198-
streaming with the hash computed on the same pass, so the store returns
198+
read in chunks with the hash computed on the same pass, returned whole
199+
(peak ≈ 2× the blob at the §5 cap — a stated v1 trade; the consumer
200+
materialises the full payload anyway), so the store returns
199201
bytes it can prove or fails — a re-delivery that "mostly" matches its
200202
address would defeat the receipt contract exactly where it matters. A
201203
missing address is terminal; this layer **never re-fetches** (§2.8's

src/backend/src/agentclaw/community/core/bot_config_manifest/content/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class StoredContentRecord(BaseModel):
6161
"""溯源行业务模型(对应 ac_manifest_content 表)。"""
6262

6363
id: Optional[int] = Field(default=None, description="主键ID")
64-
env: str = Field(default="dev", description="环境标识")
64+
env: str = Field(..., description="环境标识(必填,无默认——scope 之外不存在「默认环境」)")
6565
entity_id: str = Field(..., description="实体ID(存储键,非公开字段)")
6666
bot_id: str = Field(..., description="Bot ID")
6767
digest: str = Field(..., description="内容地址 sha256:<hex64>")

src/backend/src/agentclaw/community/core/bot_config_manifest/content/service.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,19 @@ def store(
234234
def read(self, digest: str) -> bytes:
235235
"""The one read path, shared by delivery and audit (§2.8).
236236
237-
Streaming with the hash on the same pass: the store returns bytes it
238-
can prove, or it raises — a re-delivery that "mostly" matches its
239-
address would defeat the receipt contract exactly where it matters.
240-
A missing address is terminal (``ContentMissingError``); this layer
241-
never re-fetches.
237+
The blob is read in chunks with the hash computed on the same pass,
238+
and returned whole: the store hands back bytes it can prove, or it
239+
raises — a re-delivery that "mostly" matches its address would
240+
defeat the receipt contract exactly where it matters. A missing
241+
address is terminal (``ContentMissingError``); this layer never
242+
re-fetches.
243+
244+
Whole-bytes on purpose, at a stated cost: at the schema §5 cap
245+
(100–200 MiB an entry) the peak is ~2× the blob (the chunk list and
246+
the joined return). Apply is rare, and the read's consumer — W4's
247+
delivery — materialises the full payload into the artifact anyway;
248+
a chunk-wise contract is a decision for when a consumer exists that
249+
wants one, not a default to speculate at now.
242250
"""
243251
digest = _require_valid_digest(digest)
244252
blob = self._blob_path(digest)
@@ -259,15 +267,16 @@ def read(self, digest: str) -> bytes:
259267

260268
def records(
261269
self,
270+
scope: ContentScope,
262271
*,
263-
env: str,
264-
entity_id: str,
265-
bot_id: str,
266-
limit: int = DEFAULT_RECORD_LIMIT,
272+
limit: Optional[int] = None,
267273
) -> list[StoredContentRecord]:
268274
"""The audit read: one bot's receipts, newest first."""
269275
return self._repository.records_for(
270-
env=env, entity_id=entity_id, bot_id=bot_id, limit=limit
276+
env=scope.env,
277+
entity_id=scope.entity_id,
278+
bot_id=scope.bot_id,
279+
limit=DEFAULT_RECORD_LIMIT if limit is None else limit,
271280
)
272281

273282
# --- the blob tree ----------------------------------------------------

src/backend/src/agentclaw/community/core/bot_config_manifest/content/service_protocol.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,15 @@ def read(self, digest: str) -> bytes:
8080
@abstractmethod
8181
def records(
8282
self,
83+
scope: ContentScope,
8384
*,
84-
env: str,
85-
entity_id: str,
86-
bot_id: str,
87-
limit: int = 50, # == repository protocol's DEFAULT_RECORD_LIMIT; a literal keeps this protocol file domain-import-free at runtime
85+
limit: Optional[int] = None,
8886
) -> list[StoredContentRecord]:
89-
"""The audit read: one bot's receipts, newest first."""
87+
"""The audit read: one bot's receipts, newest first.
88+
89+
Same scope shape as ``store`` — the bot a store event was on behalf
90+
of. ``limit=None`` means the repository protocol's
91+
``DEFAULT_RECORD_LIMIT``; that constant stays the one definition, so
92+
this file carries no drifting copy of its value.
93+
"""
9094
...

src/backend/tests/community/core/bot_config_manifest/content/test_content_store.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,20 @@ def test_store_then_read_roundtrips_the_platform_copy(tmp_path):
106106
)
107107
assert stored.digest == BODY_SHA
108108
assert service.read(BODY_SHA) == BODY
109-
# The audit read surfaces the same receipt.
110-
assert [r.digest for r in service.records(
111-
env=SCOPE.env, entity_id=SCOPE.entity_id, bot_id=SCOPE.bot_id)] == [BODY_SHA]
109+
# The audit read surfaces the same receipt — scope-shaped, like store().
110+
assert [r.digest for r in service.records(scope=SCOPE)] == [BODY_SHA]
111+
112+
113+
def test_records_limit_none_defaults_and_an_explicit_value_bounds(tmp_path):
114+
# None means the repository protocol's DEFAULT_RECORD_LIMIT (the one
115+
# definition of the bound — no drifting literal here); an explicit
116+
# value bounds the audit read itself.
117+
service, _ = _service(tmp_path)
118+
service.store(_fetched(), scope=SCOPE, source_url="https://content.example/a.bin")
119+
service.store(_fetched(url="https://mirror.example/a.bin"), scope=SCOPE,
120+
source_url="https://mirror.example/a.bin")
121+
assert len(service.records(scope=SCOPE)) == 2
122+
assert len(service.records(scope=SCOPE, limit=1)) == 1
112123

113124

114125
def test_a_receipt_that_disagrees_with_its_bytes_is_refused(tmp_path):

0 commit comments

Comments
 (0)