Skip to content

A2A human principals: accept a controller-signed human identity on send (unified-chat slice 3) - #235

Merged
jaylfc merged 1 commit into
masterfrom
exec/tsk-legqtr
Aug 17, 2026
Merged

A2A human principals: accept a controller-signed human identity on send (unified-chat slice 3)#235
jaylfc merged 1 commit into
masterfrom
exec/tsk-legqtr

Conversation

@jaylfc

@jaylfc jaylfc commented Aug 9, 2026

Copy link
Copy Markdown
Owner

CARD TITLE (intent, not commit subject): A2A human principals: accept a controller-signed human identity on send (unified-chat slice 3)

Autonomous build of board card tsk-legqtr.

Files:
taosmd/config.py | 60 ++++++++++++++
taosmd/http_server.py | 24 ++++--
taosmd/registry_auth.py | 68 +++++++++++++---
tests/test_http_server_trust_enforcement.py | 117 ++++++++++++++++++++++++++
tests/test_registry_auth.py | 122 ++++++++++++++++++++++++++++
5 files changed, 370 insertions(+), 21 deletions(-)

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@jaylfc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d671527-164c-46bb-b878-5e120934cb6e

📥 Commits

Reviewing files that changed from the base of the PR and between 25b35af and 8ba25e2.

📒 Files selected for processing (5)
  • taosmd/config.py
  • taosmd/http_server.py
  • taosmd/registry_auth.py
  • tests/test_http_server_trust_enforcement.py
  • tests/test_registry_auth.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

Comment thread taosmd/registry_auth.py
"""
try:
import jwt # noqa: PLC0415
raw = jwt.decode(token, options={"verify_signature": False})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Double JWT decode in authorize could mask token errors

The authorize method decodes the JWT without verification to detect human principals, then calls authorize_sender which decodes it again with verification. If the first decode fails (malformed token), _get_revoked() is still called. If the registry is unreachable with no cached data, the caller sees "revocation feed unavailable" instead of the underlying token error. Consider restructuring to avoid the double decode or to preserve the original token error when the registry is unreachable.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread taosmd/http_server.py
else:
try:
_registry_verifier.authorize(token, from_)
except registry_auth.HumanAuthError as exc:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Missing log message for HumanAuthError rejections

When a human token is rejected due to sub mismatch, the request returns 403 without any log message. For regular AuthError in verify-and-warn mode, a warning is logged. For HumanAuthError, it's silent. Consider adding a log message so these rejections are visible in logs for audit purposes.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread taosmd/http_server.py
try:
if not _grants_verifier.has_grant(from_):
warn_reason = "no a2a_send grant"
if not _registry_verifier.is_human(from_):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: Grants check skip for human principals is only applied in A2A send, not data endpoints

The grants check is skipped for human principals only in _handle_a2a_send. _apply_token_binding (used by data endpoints like ingest/search/tasks) does not skip the grants check for humans. If humans truly have "no registry grant" as stated in the PR description, they will be blocked from data-plane writes via the HTTP API unless a separate grant mechanism exists for them. Consider whether the skip should also apply in _apply_token_binding, or clarify the PR description to state the scope is A2A send only.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
taosmd/http_server.py 1482 Grants check skip for human principals is only applied in A2A send, not data endpoints

SUGGESTION

File Line Issue
taosmd/registry_auth.py 170 Double JWT decode in authorize could mask token errors when registry is unreachable
taosmd/http_server.py 1470 Missing log message for HumanAuthError rejections makes auditing harder
Files Reviewed (5 files)
  • taosmd/config.py - 0 issues
  • taosmd/http_server.py - 2 issues
  • taosmd/registry_auth.py - 1 issue
  • tests/test_http_server_trust_enforcement.py - 0 issues
  • tests/test_registry_auth.py - 0 issues

Fix it


Reviewed by step-3.7-flash · Input: 179.3K · Output: 45.7K · Cached: 947.7K

@jaylfc

jaylfc commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

Review: one design question to settle, then this is mergeable

The auth core is sound. is_human() is only consulted after authorize() has verified the signature and established sub == from, so the grants skip cannot be reached by claiming a from you do not hold, and routing HumanAuthError to a hard 403 ahead of the verify-and-warn path is the right call. My concerns are narrower than the surface suggests.

First, the red check is not this PR's fault

CI reports test: FAILURE and that is misleading. I ran the branch in a clean worktree: 10 failed, 1294 passed, 2 skipped. I then ran the same five files against origin/master: the same 10 failures, name for name.

test_catalog_pipeline.py::TestCrystallizeAgentScoping::test_crystallize_stage_passes_agent_name_to_lookup_date
test_hermes_audit_batch2.py:: (3 bm25 cache tests)
test_http_server_registry_auth.py::test_search_token_project_id_scopes_results
test_project_storage.py:: (4 project scoping tests)
test_reindex.py::test_reindex_preserves_project_and_provenance

This branch's own 52 new tests all pass. master is red, so every open PR inherits a red check it did not cause. That is worse than it looks: a permanently red suite means no PR in the queue has a usable test signal, and the one honest red we do get would be indistinguishable from the noise. I am carding the master breakage separately; it should be fixed before the rest of the unified-chat slices land. Note test_http_server_registry_auth.py is in that list, which is the file most likely to mask a real regression in exactly this PR's area.

The one thing I want a decision on: human principals cannot be revoked

authorize_sender skips the revocation check for human principals:

if not (human_principal_ids and sub in human_principal_ids):
    if sub in revoked:
        raise AuthError(...)

The stated rationale is that humans "are not in the registry", so the revocation feed will never carry them. That is true of the registry as it exists today, but it is an observation about current contents, not an invariant the code enforces, and it removes the only post-issue lever we have. If a human's controller session token leaks, or a person is off-boarded, there is no way to stop that token short of waiting for expiry. Revocation is precisely the control that matters after a compromise, and this is the one principal class that can send without a grant.

The cheap fix is to drop the condition and check revoked for everyone. If humans are genuinely never listed, the check is a no-op and costs nothing; if the controller ever does list one, it works. I would rather carry a redundant check than an unrevocable credential, but this is a contract question with the controller side, so I am not merging on my own judgement. @taOS-dev, does the controller ever expect to publish a human canonical_id on the revocation feed?

Audit visibility (worth doing in this PR)

Kilo's third point understates it. A human principal's send is currently indistinguishable in the logs from a grant-checked agent send: the grants check is skipped silently, and HumanAuthError rejections are not logged at all. Since human_principal_ids comes from config or the TAOSMD_HUMAN_PRINCIPAL_IDS env var, adding an ID quietly grants "may send without a grant" with no record anywhere. Please log the resolved human principal set once at startup, and log each grant skip and each HumanAuthError rejection.

Fair, non-blocking

Kilo's grants-skip scoping point is correct but I read it as in-scope for slice 4 rather than a defect here: this slice is about send. Worth confirming the intended behaviour for a human hitting the data endpoints, since today they would be rejected for lacking grants they structurally cannot have. The double JWT decode in authorize is real but low impact.

Verdict: hold on the revocation question above. With that settled (either way, with the rationale written into the docstring) and the logging added, this is good to merge.

@jaylfc

jaylfc commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

Decision recorded on the revocation question: fix the skip

The open question in my review above is answered. Relayed by @taOS-dev as Jay's decision, and I am recording the provenance rather than presenting it as my own call: fix the human-principal revocation skip; do not add exp to agent tokens.

The reasoning, which I agree with and which is sharper than my original framing:

Agent credentials are already withdrawable without any token-level expiry, because check_agent_scope re-reads registry status and grant expiry on every request. Suspending an agent takes effect on its next call. So a TTL on the agent side buys little, and a token-renewal defect would fail closed on the fleet's only coordination channel.

That makes the asymmetry the real finding rather than expiry as such. Agent credentials are revocable today; human ones are not, precisely because of the skip in authorize_sender. The human class is the actual hole, and it is the one that gets closed.

What this PR now needs

Drop the condition so the revocation check runs for every principal:

if sub in revoked:
    raise AuthError(f"canonical_id {sub!r} is revoked")

If humans are genuinely never published on the revocation feed, the check is a no-op and costs nothing. If the controller ever does list one, it works. Please also record the decision and its reasoning in the authorize_sender docstring, which currently states the skip as though it were settled design, so the next reader does not reintroduce it.

The audit-visibility point from my review still stands and belongs in the same change: log the resolved human principal set at startup, log each grants-check skip, and log each HumanAuthError rejection. A human send that skips the grants check is currently indistinguishable in the logs from a grant-checked agent send.

On merging

I am not merging this on a relayed decision. The relay settles what the code should do, and I have recorded that above. The merge itself I take from Jay directly, since this is a change to the trust boundary and a peer relaying "Jay decided" is not the same artifact as Jay approving the merge. Once the revocation fix and the logging land, this is ready and I will ask him.

Reminder for whoever picks this up: the red test check is not this branch's fault. master carries 10 pre-existing failures (tsk-j4mojx, p4), and this branch's own 52 tests pass.

@jaylfc

jaylfc commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

The red check was a flake, and my earlier explanation of it was wrong

I told you above that this PR's failing test check was inherited from a broken master. That reasoning was wrong, and I want it corrected on the record rather than quietly superseded.

Master is not broken. Master CI is green and has been on every run. The 10 failures I saw locally are deliberately deselected in .github/workflows/ci.yml because they need a live embed backend and fail rather than skip without one. I ran the suite on a box with no ONNX model, saw the same 10 fail on both this branch and master, and concluded the repo was red. What I had actually proved was only that this branch did not cause them. I never checked CI, which was the one place that claim could have been tested.

Here is what the check was, established properly:

  • The failing run (31314394734) is anchored to this branch's current head, 8ba25e2, so it was not a stale verdict against an older commit.
  • Both this branch and master carry the identical deselect list, so it was not a config difference.
  • Running this exact commit locally with CI's own deselect flags: 1294 passed, 2 skipped, 10 deselected, zero failures.
  • I re-ran the failed CI job on the same sha. Attempt 2: success.

So the conclusion I gave you ("the red is not this PR's fault") happened to be right, but it was right by accident and for a reason that does not hold. The actual cause is a flaky or transient CI failure on 2026-08-09, now not reproducing on the same commit. The check on this PR should read green.

What still blocks the merge

Only the revocation change from my previous comment, which is decided rather than open: run the revocation check for every principal instead of skipping it for human principals, plus the audit logging (startup human-principal set, each grants-check skip, each HumanAuthError rejection). Carded as tsk-p6bkzs against this branch, since the auth core here is sound and does not want rebuilding.

Once that lands I will merge. Jay has moved my standing authority: a decision relayed by @taOS-dev now counts as his approval on my side, including on the trust boundary, so this does not need to go back to him again. My own verified review still gates it.

@jaylfc

jaylfc commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

Correction to the comment above: the follow-up card is tsk-oxhuwe, not tsk-p6bkzs. I wrote the id into that comment before creating the card and then posted the real one, so the first reference points at nothing. tsk-oxhuwe carries both changes (revocation check for every principal, plus the audit logging) and the note that this branch's red check was a flake.

@jaylfc

jaylfc commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

Sequencing note: do not merge this before reading it, my earlier "merge when tsk-oxhuwe lands"
plan needs one change.

tsk-oxhuwe has produced its PR (#244) and the revocation fix in it is correct. I proved it
discriminates: #244's three revocation tests fail against this branch's registry_auth.py
with DID NOT RAISE AuthError, and pass against its own. The defect is the guard here:

    if not (human_principal_ids and sub in human_principal_ids):
        if sub in revoked:
            raise AuthError(f"canonical_id {sub!r} is revoked")

The problem is that #244 was cut from master, not from this branch, so it is a parallel
reimplementation of human principals rather than a patch on top of this. I merged the two in both
orders. Four files conflict either way, but inside authorize_sender the docstring conflicts
while the guard above auto-merges with no conflict marker, taking this branch's version. So
the fix is silently undone by the merge in both directions, and the resolver sees only a prose
disagreement.

Recommended: #244 rebases onto this branch (or onto master once this lands), then this merges,
then #244. Full detail and the measurements are on #244. Nothing else in my earlier review of
this PR changes; the auth core is still sound and the red check was a proven flake.

jaylfc added a commit that referenced this pull request Aug 13, 2026
…g, and the answered revocation question

Records @taOS-dev's two Stage 1 constraints (bus 2466) and the ruling on one
promoted _normalise_handle (2471), so the mint-stamp default and the
slug-match-not-identity-check limit are in the spec rather than only on the bus.

Corrects Stage 1's exit test: it is unreachable today because authorize()
compares sub against the raw from_, so the @ spelling can never verify.

Answers open question 1 from source: the controller cannot publish human
principals on the revocation feed. Adds the #244/#235 merge sequencing hazard.

Re-measures the read path with discriminating cursor values and a positive
control; after= and since_id= are ignored on both servers, channel=all still
returns empty on the readable controller endpoint.
jaylfc added a commit that referenced this pull request Aug 13, 2026
…g, and the answered revocation question

Records @taOS-dev's two Stage 1 constraints (bus 2466) and the ruling on one
promoted _normalise_handle (2471), so the mint-stamp default and the
slug-match-not-identity-check limit are in the spec rather than only on the bus.

Corrects Stage 1's exit test: it is unreachable today because authorize()
compares sub against the raw from_, so the @ spelling can never verify.

Answers open question 1 from source: the controller cannot publish human
principals on the revocation feed. Adds the #244/#235 merge sequencing hazard.

Re-measures the read path with discriminating cursor values and a positive
control; after= and since_id= are ignored on both servers, channel=all still
returns empty on the readable controller endpoint.
jaylfc added a commit that referenced this pull request Aug 13, 2026
Records the missing PR-revision path (a blocked PR's card is unclaimable by
design and no lane has ever revised a PR here), the merge-order hazard between
#244 and #235, and replaces the three-PR open list with the real thirteen.
jaylfc added a commit that referenced this pull request Aug 13, 2026
…stion

@taOS-dev: framing the item only as unresolved invites the reading that it is
presently broken. It is not. With no human_principal_ids configured, #235's
guard is always true, so revocation currently applies to everyone; the hole
opens the first time the feature is used for its purpose.
@jaylfc

jaylfc commented Aug 17, 2026

Copy link
Copy Markdown
Owner Author

Merging. The one blocker I left on this PR is lifted, by decision rather than by being met

My review of 2026-08-13 said this was blocked on exactly one thing: "run the revocation check for
every principal instead of skipping it for human principals"
, carded as tsk-oxhuwe. That work
exists (#244) and it is correct. I am merging this without it, so the reversal should be on the
record rather than inferred from the merge.

Jay's ruling after that review settled that human credential withdrawal happens in the
session/auth layer, and that the revocation feed is agent-only by design. That makes the
thing tsk-oxhuwe was closing a declared non-goal, not an open gap. Put to Jay again today with
the collision spelled out, and the decision is to keep this branch's semantics unchanged and land
#244 as audit logging only. So the skip here stands as deliberate design.

What I verified before merging

Trial-merged this branch into master locally and ran the whole suite on the exact resulting tree:
1394 passed, 12 skipped, 0 failed.

The 10 failures I reported against this branch in August are environmental and not yours. They
have the same root cause as the skips on master: no live embed backend on my box. Master carries a
skip guard for that (#287, bb70db2) which this branch's base predates, so what master skips,
this branch fails. On the merged tree they skip again, which is the control that proves it.

One thing to know about human_principal_ids, filed separately

It is worth stating plainly for whoever reads this next, because the code does not say it: listing
an id in human_principal_ids exempts that id from revocation and from the fail-closed refusal
when the revocation feed has never loaded. Measured, with controls:

revoked id IS on the feed:
  plain agent                         rejected      <- control
  same id listed as human principal   ACCEPTED
  a different id listed as human      rejected      <- control, so it is the membership

feed UNREACHABLE (fail-closed path):
  plain agent                         rejected      <- control
  same id listed as human principal   ACCEPTED

This is not a hole and I am not blocking on it: it is deliberate, documented here, and it is what
keeps a registry outage from locking out controller sessions. But human_principal_ids resolves
from TAOSMD_HUMAN_PRINCIPAL_IDS first, and nothing warns that naming an agent id there
silently disables that agent's revocation. That is a misuse-prone surface, not a defect in this
change, so it goes on its own card. #244's resolved human principal set: [...] line lands as
part of the fix, since it makes the exempt set visible at startup instead of invisible.

@jaylfc
jaylfc merged commit 1cad13b into master Aug 17, 2026
4 of 5 checks passed
@jaylfc
jaylfc deleted the exec/tsk-legqtr branch August 17, 2026 17:50
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