Skip to content

fix(interview): admit a fleet node's window only on the request sent to it - #1792

Merged
eldad-caura-ai merged 1 commit into
mainfrom
fix/interview-fleet-window
Oct 3, 2026
Merged

eldad-caura-ai merged 1 commit into
mainfrom
fix/interview-fleet-window

Conversation

@eldad-caura-ai

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

Copy link
Copy Markdown
Member

Audit finding

Finishes M-86. caura PR #1788 bound adapter streams (cc: / cursor: node ids) to the agent that advances them. Fleet-node windows were still open: an agent or install credential could submit a window for any fleet node of its tenant by naming the node and any command_id. That advanced the node's watermark, which only moves forward, and wrote its job doc.

Change

  • Gate. A window for a fleet node (UUID node_id) from an agent or install credential must cite the interview_request the scheduler queued for that node, and the node's heartbeat must have delivered it (acked). Anything else gets 403 INTERVIEW_REQUEST_REQUIRED before the watermark or job doc is touched. This is the same "agent or install credential" test fix(interview): accept adapter streams and bind them to their agent #1788 applies to adapter streams.
  • Used once. Storage claims the request in one conditional UPDATE: this tenant, this node, interview_request, acked, no result yet. The claim writes a marker into result, so a second window citing the same id is refused. A used id stops being secret, because the job doc and watermark record it. The node's own result report still overwrites the marker and closes the command.
  • Unchanged. Tenant credentials keep tenant-wide authority, as they do for adapter streams. The plugin already submits once per delivered command and cites its id (plugin/src/heartbeat.ts), and the scheduler only looks at pending commands, so neither changes. A failed submit is retried by the scheduler as a fresh command, as before.

Why this relies on caura PR #1789

The request id is the credential here. Since #1789, an agent or install credential can only list or receive commands for nodes bound to it, so it cannot read another node's pending interview_request before that node uses it.

Verification

  • Route tests cover an uninvited agent and an uninvited install, a queued but undelivered request, one window per request, a request for another node, and a guard that tenant credentials still submit directly. The existing attribution test now cites a delivered request.
  • Storage tests cover claim once, undelivered, wrong node, wrong tenant, non-interview command, an already completed request, and the node's result report closing a claimed request.
  • Tests first: the tests-only push (run 37126305662) failed exactly the five attack tests against main, with 8644 passing. In each one the unfixed route answered accepted and moved the node's watermark. The tenant guard and the updated attribution test passed. That step stopped the job before the storage suite ran. The fix is now amended into the same single commit.
  • No repository scripts, dependency installs, tests or builds were run on Eldad's Mac under its safety hold. CI runs the suites.

One signed-off commit. Do not merge or enable auto-merge; Eldad merges after review.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Claude Code Review — skipped: draft PR

@eldad-caura-ai
eldad-caura-ai marked this pull request as ready for review October 3, 2026 13:30
@eldad-caura-ai
eldad-caura-ai requested a review from a team as a code owner October 3, 2026 13:30
@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

🤖 Review by Claude Code

Claude Code Review ✅ No issues found.


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

@eldad-caura-ai
eldad-caura-ai force-pushed the fix/interview-fleet-window branch 2 times, most recently from 74fbcab to 0a2fae7 Compare October 3, 2026 18:39
@eldad-caura-ai

Copy link
Copy Markdown
Member Author

@erni-a plear rpprove

erni-a
erni-a previously approved these changes Oct 3, 2026
@eldad-caura-ai

Copy link
Copy Markdown
Member Author

@claude please review b1ce35b. The earlier review ran on fa462a3, the tests-only commit, before the fix existed.

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

🤖 Review by Claude Code

Claude Code Review ✅ No issues found.


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

…to it

An agent or install credential could submit an interview window for any fleet node of its tenant by naming the node and any command_id, which advanced that node's watermark and wrote its job doc (M-86, fleet-node half; #1788 bound adapter streams). A fleet node's window from an agent or install credential must now cite an interview_request the scheduler queued for that node and the node's heartbeat delivered. Storage claims the request in one conditional UPDATE, so each request admits one window, and the node's own result report still closes the command. Tenant credentials keep tenant-wide authority. The plugin already cites the delivered command's id, so its flow is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: eldad-caura <eldad@caura.ai>
@eldad-caura-ai
eldad-caura-ai force-pushed the fix/interview-fleet-window branch from b1ce35b to b56ccd7 Compare October 3, 2026 19:03
@eldad-caura-ai

Copy link
Copy Markdown
Member Author

@erni-a pl;ara rprpove

@eldad-caura-ai
eldad-caura-ai merged commit 4c56b8e into main Oct 3, 2026
14 checks passed
@eldad-caura-ai
eldad-caura-ai deleted the fix/interview-fleet-window branch October 3, 2026 19:12
@caura-deploy-bot caura-deploy-bot Bot mentioned this pull request Oct 3, 2026
eldad-caura-ai added a commit that referenced this pull request Oct 3, 2026
…1794)

## Problem

Migration 001 creates seventeen columns as `json`, but their models
declare `JSONB` (the CAURA-595 drift). Every database built by the
migration chain, CI's included, has `json`. Code written from the models
fails only there:

- `jsonb_typeof(fleet_commands.result)` raised `UndefinedFunctionError`
in caura PR #1792's CI;
- `backfill_embeddings` hit the same error on the `?` operator;
- the metadata merge raised `CannotCoerceError` on `COALESCE(metadata,
'{}'::jsonb)` until it gained a `::jsonb` cast.

Nothing after migration 001 converts these columns. The existing
model-vs-schema test (`test_models_match_the_migrated_schema.py`) checks
tables and indexes only, so it never noticed.

| Table | Columns |
|---|---|
| `memories` | `metadata` |
| `documents` | `data` |
| `audit_log` | `detail` |
| `entities` | `attributes` |
| `agents` | `search_profile` |
| `analysis_reports` | `summary`, `hygiene`, `health`, `usage_data`,
`issues`, `crystallization` |
| `fleet_nodes` | `agents_json`, `tools_json`, `channels_json`,
`metadata` |
| `fleet_commands` | `payload`, `result` |

The other eleven JSON columns, all added after 001, are `jsonb` in both
the models and the migrations. They are unchanged.

## Change

- **Models.** The seventeen columns now declare `postgresql.JSON`, which
matches what the migration chain builds and what CI tests.
`postgresql.JSON` keeps `.astext`; `.as_float()` and `.as_boolean()`
come from the generic JSON type. Those three are the only JSON operators
the ORM uses on these columns.
- **Regression test.** A new storage test compares every JSON-family
model column with `information_schema.columns` in the migrated schema.
It fails on any `json`/`jsonb` disagreement, so the next drift fails CI.
- **Comments.** Comments that said the model declared `JSONB` now
describe the real situation. That includes the claim filter caura PR
#1792 added, which compares `fleet_commands.result` as text so it works
on both types.

## Not in this PR (approved choice)

There is no migration. Converting these columns to `jsonb` would rewrite
`memories`, `documents`, `audit_log` and `entities` under an exclusive
lock, and `jsonb` also normalises stored JSON. A database that
`create_all` built and that was stamped before the chain was
authoritative may still hold `jsonb`. So statements on these columns
must keep working on both types, which is why the existing `::jsonb`
casts stay.

## Verification

- Tests first: the test-only push ([run
37146396849](https://github.com/caura-ai/caura/actions/runs/37146396849/job/111271126592))
failed only the new test, with 674 passing. It listed exactly the
seventeen columns above as `model jsonb, schema json`, matching the
inventory taken from the files. The model change is now amended into the
same single commit.
- No repository scripts, dependency installs, tests or builds were run
on Eldad's Mac under its safety hold. The inventory came from parsing
the model and migration files. CI runs the suites.

One signed-off commit. Do not merge or enable auto-merge; Eldad merges
after review.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: eldad-caura <eldad@caura.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
eldad-caura-ai pushed a commit that referenced this pull request Oct 3, 2026
🤖 I have created a release *beep* *boop*
---


<details><summary>backend: 3.21.2</summary>

##
[3.21.2](backend-v3.21.1...backend-v3.21.2)
(2026-10-03)


### Bug Fixes

* **graph:** hide graph data mined from soft-deleted memories from every
reader ([#1796](#1796))
([97315d0](97315d0))
* **interview:** admit a fleet node's window only on the request sent to
it ([#1792](#1792))
([4c56b8e](4c56b8e))
* **models:** declare the JSON columns migration 001 creates as json
([#1794](#1794))
([e0a0a9b](e0a0a9b))
</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