Skip to content

feat(memories): hold the broker's gate-refused writes for review (g2.5) - #1985

Merged
eldad-caura-ai merged 3 commits into
mainfrom
feat/gate-held-writes
Oct 7, 2026
Merged

eldad-caura-ai merged 3 commits into
mainfrom
feat/gate-held-writes

Conversation

@eldad-caura-ai

@eldad-caura-ai eldad-caura-ai commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Summary

This is the cloud side of governance row g2.5. caura-daemon's write gate refuses an agent's write to a memory file, such as CLAUDE.md, when the fleet policy denies it or requires approval (g2.3, g2.4). With this PR, the broker can send that refused write here to be held for a person's review (decisions G7 and G8).

GET /memories/held then shows each held write's provenance:

  • the file paths and the tool;
  • the policy's action and the rules that decided it;
  • the session and the agent;
  • the hash of the rules the session was delivered.

What changed

  • POST /memories/bulk accepts "status": "quarantined" on an item from the broker's install credential, and from no one else. From anyone else it's an item error, as any status outside the vocabulary is.
  • The gate's account is kept where no caller writes. That's the item's metadata.write_gate: tool, paths, action and rule_ids.
    • It's stored in _system.hold, with the reason write_gate, so GET /memories/held shows it as system_metadata.hold, beside the session's rules_receipt (g2.8).
    • It's normalised in services/write_gate_hold.py. A malformed part is dropped with a warning; the hold isn't, because the status, not the account, is what holds the write.
  • write_gate is a platform-only key. Anyone else's is stripped, so a write can't make up its own provenance.
  • Trust holds: when the organization's trust level holds a whole batch (g2.8), the gate's account names the hold of the write it refused, and the batch's other items keep below_trust.
  • Rename: create_memories_bulk's trusted_receipts becomes from_broker, since it now covers both.
  • Docs: docs/api-reference.md (the bulk and held-list rows).

Release, reject, rollback and the release replay (g2.7–g2.9) are unchanged: a gate-held write is stored exactly as a trust-held one, only with another reason.

Safety on an older cloud: a cloud without this refuses the status, so a held write is never stored live there.

Verification

  • New tests: tests/test_write_gate_holds.py has 19, through the routes and storage plus the account's shape:
    • a refused write is held with the gate's account and the receipt;
    • the review queue shows all the provenance (the row's done criterion);
    • a release makes the write live;
    • only the broker may ask for the status;
    • a writer's own account is dropped;
    • a malformed account still holds the write;
    • the gate's account names the hold under a trust hold;
    • the account's normalisation, including that deny is held too.
  • Related suites: bulk, system metadata, holds, receipts, broker ownership and quarantine: 265 passed.
  • Full root suite: 9,661 passed and 27 failed. All 27 are search, FTS, ANN and ranking tests, and the same 27 fail on main in this local setup, so they predate this change.
  • 12 mutations, all caught:
    • the broker can't ask for the status, or anyone can;
    • no gate holds, or the account read after sanitising;
    • the trust hold wins;
    • write_gate not a platform key;
    • the route not saying the caller is the broker;
    • deny not held;
    • empty lists kept;
    • no bound on paths, or on the tool;
    • a non-object account not refused.
  • Gates: ruff check and format, mypy for core-api, and the broker OpenAPI baseline (unchanged) all pass.

Pairs with

The broker side is caura-ai/caura-daemon#267. Merge and deploy this first: until then, the broker's held writes are refused by the cloud, and never stored live.

🤖 Generated with Claude Code

@eldad-caura-ai
eldad-caura-ai requested a review from a team as a code owner October 7, 2026 12:50
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Review by Claude Code

Claude Code Review ✅ No issues found.


Reviewed by claude-sonnet-5-5 · cost $0.37086374999999994

The broker's write gate (caura-daemon, g2.3 and g2.4) refuses an
agent's write to a memory file, such as CLAUDE.md, when the fleet policy
denies it or requires approval. The broker can now send the refused
write here to be held for a person's review, and the review queue shows
where it came from.

- POST /memories/bulk accepts `status: "quarantined"` on an item from
  the broker's install credential, and from no one else: anyone else's
  is an item error, as any status outside the vocabulary is.
- The item's `metadata.write_gate`, the gate's account of the write
  (`tool`, `paths`, `action`, `rule_ids`), is kept in `_system.hold`
  with the reason `write_gate`. GET /memories/held shows it as
  `system_metadata.hold`, beside the session's `rules_receipt` (g2.8).
- `write_gate` is a platform-only key: anyone else's is stripped, so a
  write can't make up its own provenance. A malformed account loses its
  malformed parts, not the hold.
- Where the organization's trust level holds the whole batch (g2.8),
  the gate's account names the hold of the write it refused.
- create_memories_bulk's `trusted_receipts` becomes `from_broker`, as
  it now covers both.

A cloud without this refuses the status, so a held write never lands
live on one.

Signed-off-by: eldad-caura <eldad@caura.ai>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eldad-caura-ai

Copy link
Copy Markdown
Member Author

@erni-a plear rprpove

erni-a
erni-a previously approved these changes Oct 7, 2026
eldad-caura-ai and others added 2 commits October 7, 2026 16:11
Brings in #1984. The one textual conflict is the write_hold import in
memory_service.py; both sides are kept.

Signed-off-by: eldad-caura <eldad@caura.ai>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1984 decides a bulk batch again when storage refuses it because the
settings changed, and it held every item with the batch's new answer.
A broker batch can carry captures beside a write its gate refused
(g2.5). When the trust level was raised by another process, the
captures' insert was refused, the batch was decided again, and the
refused write lost the gate's account: it came back held as
below_trust.

decide_again now applies the hold as the first decision does: the
gate's hold for a write it refused, the batch's for the rest.

Signed-off-by: eldad-caura <eldad@caura.ai>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@eldad-caura-ai

Copy link
Copy Markdown
Member Author

Resolved the conflict with #1984 by merging main into the branch (6f30fde). There was no force-push, so the original commit is unchanged. There is also one fix, 3ec0ea4.

  • The textual conflict was the write_hold import in memory_service.py. I kept both sides.
  • A semantic conflict underneath, fixed in 3ec0ea4.
    • fix(memories): decide a write again when the hold changed after it was decided #1984 decides a bulk batch again when storage refuses it because the settings changed. Its decide_again held every item with the batch's new answer.
    • A broker batch can carry captures, which go live, beside a write its gate refused, which is held with write_gate.
    • Suppose another process raises the trust level. The captures' insert is refused, the batch is decided again, and the refused write's hold is overwritten with below_trust. The gate's account is lost.
    • decide_again now applies gate_holds.get(i) or hold, as the first decision does.
  • New test: test_the_gates_account_survives_the_batch_being_decided_again sets the trust level in storage behind this process's settings cache, so the real storage refuses the first insert.
    • It fails on a plain merge: the gate-held write comes back with {"reason": "below_trust", ...}.
    • It passes with the fix.
  • Checks:

@claude please review the two new commits, the merge resolution and the decide_again fix.

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Review by Claude Code

Claude Code Review ✅ No issues found.


Reviewed by claude-sonnet-5-5 · cost $0.33305575

@eldad-caura-ai

Copy link
Copy Markdown
Member Author

@erni-a plasr arprpove

@eldad-caura-ai
eldad-caura-ai merged commit 2892bc9 into main Oct 7, 2026
16 checks passed
@eldad-caura-ai
eldad-caura-ai deleted the feat/gate-held-writes branch October 7, 2026 13:26
@caura-deploy-bot caura-deploy-bot Bot mentioned this pull request Oct 7, 2026
eldad-caura-ai pushed a commit that referenced this pull request Oct 7, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>backend: 3.27.0</summary>

##
[3.27.0](backend-v3.26.4...backend-v3.27.0)
(2026-10-07)


### Features

* **memories:** hold the broker's gate-refused writes for review (g2.5)
([#1985](#1985))
([2892bc9](2892bc9))


### Bug Fixes

* **ci:** check every service lock and build core-worker's image (M-01)
([#1983](#1983))
([54040d8](54040d8))
* **memories:** decide a write again when the hold changed after it was
decided ([#1984](#1984))
([7ce0212](7ce0212))
</details>

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Signed-off-by: release-please[bot] <release-please[bot]@users.noreply.github.com>
Co-authored-by: caura-deploy-bot[bot] <265395343+caura-deploy-bot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants