Skip to content

Declare in source what each in-process agent may do, and give the status page a name - #764

Merged
xmap merged 1 commit into
mainfrom
in-process-grants
Sep 1, 2026
Merged

Declare in source what each in-process agent may do, and give the status page a name#764
xmap merged 1 commit into
mainfrom
in-process-grants

Conversation

@xmap

@xmap xmap commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Parts 2 and 3 of the plan behind #762. Groundwork for moving CORA's background runtimes onto the in-process door. Neither half changes what the running application does.

The grant table

cora.api.in_process_grants.IN_PROCESS_GRANTS names, per principal, the exact commands that principal issues through the back door.

  20 principals × 28 distinct commands = 560  ← what a pre-#762 rulebook granted
  44 real grant pairs                         ← what the code actually does
                                                 92% of it was fiction

It lives in the composition root, not cora.trust. tach.toml restricts Trust to infrastructure, shared, and its own aggregates, so a table there importing agent-id constants from cora.agent does not build. That turned out to be the right home anyway: which agents a deployment runs is wiring, not domain.

Inert by construction, and that is the load-bearing property. Nothing in src/ imports it. Only the fitness test (which AST-parses rather than imports) and tools/gen_policy_grants.py, which emits the POST /policies body an operator pipes into the API. If the running app read this table and defined a Policy from it, code would be granting itself its own authority, and a merged commit would be the only thing standing between an edit here and live authority. A human still posts it; nothing arms itself.

The fitness test extends #750's registry, so every command name here must appear in the real wire surface. That closes the gap #750 opened but could not fill: it guards hand-typed lists in the repo, while the two rulebooks actually armed at 2-BM were typed into a curl body and live only as events in a database CI cannot see. Generating the body from a CI-checked table is what turns "these names were correct the day I checked" into a property.

StatusPublisher

_status_push.py acts as SYSTEM_PRINCIPAL_ID, which is also the fallback identity an unauthenticated HTTP request receives. Safe, since the back door demands a surface no HTTP request can claim, but the verdict log would read "nobody in particular" about a thousand times an hour. It now has a seeded Agent of its own, following seed_run_witness.py's shape.

_status_push.py is untouched: it still issues every read as SYSTEM until the call-site sweep. This creates the identity that sweep will use.

Two things the brief got wrong

The eight synthetic names did not need an allow-list. I expected CampaignWatcherTick and its siblings to need explicit exemption from the fitness test. They are command_name= values on to_new_event(...) envelopes — audit labels on a Decision record — and never reach authorize(). Verified at every site.

My site inventory was stale within hours. The plan said 33 nil-sentinel call sites; #763 pushed it to 35 while the plan was being written. The table was derived fresh from the call sites rather than from the plan.

Verification

49,189 unit + architecture green. ruff, pyright, tach clean.

Independently re-verified rather than taken on trust:

  • All 28 command names resolve against the 301-name wire surface.
  • Spot-checked the derivation at the call sites for the entries that could not be read off a constant (ExperimentSteerer's two, StatusPublisher's eleven, and the read_command values the flag-watcher helper receives from its callers). All matched.
  • Nothing in src/ imports the table.
  • The generator's output validates against the real DefinePolicyRequest schema and round-trips to a 44-pair command bound to the in-process surface and the local conduit.
  • Mutation check run myself, not inherited: renaming a real command in the table turns the fitness test red with a message naming the stale entry.

Deployment note

Nothing to deploy here. The table is inert, and the new agent seeds itself at boot (seed_agent emits both AgentDefined and AgentVersioned, so it arrives ready rather than needing promotion). arcturus is unaffected until the sweep lands and an operator posts a generated rulebook.

Process

Designed by Opus, implemented by a Sonnet subagent from a scoped brief, then independently verified above. The subagent reported both places the brief's premises were wrong instead of silently working around them, which is what the brief asked for.

Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com

…tus page a name

Groundwork for moving CORA's background runtimes onto the in-process
door. Neither half changes what the running application does.

## The grant table

`cora.api.in_process_grants.IN_PROCESS_GRANTS` names, per principal, the
exact commands that principal issues through the back door: 20
principals, 28 distinct commands, 44 grant pairs. The cross-product a
pre-#762 rulebook would have granted is 560, so 92% of it was fiction.

It lives in the composition root, not in `cora.trust`, because
`tach.toml` restricts Trust to infrastructure, shared, and its own
aggregates: a table there importing agent-id constants from `cora.agent`
does not build. The composition root is also where it belongs, since
which agents a deployment runs is wiring rather than domain.

Inert by construction, which is the load-bearing property. Nothing in
`src/` imports it; only the fitness test (which AST-parses rather than
imports) and `tools/gen_policy_grants.py`, which emits the
`POST /policies` body an operator pipes into the API. If the running app
read this table and defined a Policy from it, code would be granting
itself its own authority, and a merged commit would be the only thing
between an edit here and live authority. A human still posts it; nothing
arms itself.

The fitness test extends #750's registry, so every command name here
must appear in the real wire surface. That closes the gap #750 opened
but could not fill: it guards hand-typed command lists in the repo,
while the two rulebooks actually armed at 2-BM were typed into a curl
body and live only as events in a database CI cannot see. Generating the
body from a CI-checked table is what turns "these names were correct on
the day I checked" into a property.

## StatusPublisher

`_status_push.py` acts as `SYSTEM_PRINCIPAL_ID`, which is also the
fallback identity an unauthenticated HTTP request receives. Safe, since
the back door demands a surface no HTTP request can claim, but the
verdict log would read "nobody in particular" about a thousand times an
hour. It now has a seeded Agent of its own, following
`seed_run_witness.py`'s shape.

`_status_push.py` itself is untouched: it still issues every read as
SYSTEM until the call-site sweep. This commit creates the identity that
sweep will use.

## Notes

The eight synthetic names (`CampaignWatcherTick` and siblings) turned
out NOT to need the allow-list the brief anticipated. They are
`command_name=` values on `to_new_event(...)` envelopes, audit labels on
a Decision record, and never reach `authorize()`. Verified at every
site before omitting them.

Two principals in the table, RunDebriefer and CautionDrafter, are
overridable by env var and are overridden at 2-BM to deployment-specific
Agent ids. The table names the source-code constants; a deployment that
overrides them must grant the ids it actually runs.

Co-Authored-By: Claude Sonnet 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  apps/api/src/cora/agent
  __init__.py
  _seeded_fleet.py
  seed_status_publisher.py
  apps/api/src/cora/api
  in_process_grants.py
  main.py
Project Total  

This report was generated by python-coverage-comment-action

@xmap
xmap merged commit a6eebd3 into main Sep 1, 2026
19 checks passed
@xmap
xmap deleted the in-process-grants branch September 1, 2026 18:04
xmap added a commit that referenced this pull request Sep 1, 2026
…#765)

Thirty-five call sites across fifteen files still passed NIL_SENTINEL_ID
as their arrival Surface. That resolves to the FRONT policy, which is
bound to the HTTP door, so every one of them strict-denied at the
surface check. On the live 2-BM deployment those were the overwhelming
majority of shadow-mode near-misses, and they are the reason
POLICY_POSTURE=enforce could not be turned on: enforcing today would
darken the status page and stop the durable-copy sweep from registering
finished scans.

They now pass SYSTEM_IN_PROCESS_SURFACE_ID. conduit_id stays nil at the
same sites deliberately, since that one resolves through
trust_conduit_id and is a different axis.

The status page also stops signing as SYSTEM_PRINCIPAL_ID, the fallback
identity an unauthenticated HTTP request receives, and acts as the
StatusPublisher agent #764 seeded for it. Twelve reads an interval that
used to be attributed to nobody in particular now name the runtime that
issued them.

A new architecture test keeps it swept. It is an AST walk over
`surface_id=` keyword bindings, not a grep: a grep would match the
docstring in `in_process_grants` that quotes the very command used to
find these sites, and the natural response to that misfire would be an
exclusion that quietly weakens the check. Its allowlist is empty, and a
second test asserts the file enumerator actually found files, so the
parametrized case cannot pass vacuously over an empty list.

## What the sweep broke, and why that was the point

`test_run_initiator_with_start_grant_starts_run` failed: its fixture
policy takes seed_policy's default nil surface, so once the initiator
moved doors the grant no longer matched and no Run was created. The test
was right and the fixture was stale; both policies in that file now bind
the in-process surface, exactly as the deployment's generated back-door
rulebook does.

Its companion `..._without_start_grant_is_denied_no_run` did NOT fail,
and that was worse. Post-sweep it was refused at the surface check
before the command check was ever reached, so it would have passed just
as well with StartRun granted. A test that has silently stopped testing
its own name is the failure this repo keeps re-learning. Binding its
surface restores the refusal to the command grant, verified by granting
StartRun and watching it fail.

Co-authored-by: xmap <16776958+xmap@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 (1M context) <noreply@anthropic.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.

1 participant