Conversation
tnunamak
force-pushed
the
feat/cimd-trust-signal-retention
branch
from
September 14, 2026 15:35
6c3fa28 to
5f473ad
Compare
When the authorization server accepts a client whose client_id is an https URL, it satisfies itself of that client's identity by retrieving the metadata document from that URL and confirming the document names the same client_id back — verified domain control, spec-core.md#client-display obligation 5. It then discarded the fact. A relying party reading a grant back could see which client held it but not which signal the server relied on to believe that, nor when it looked. spec-core.md#trust-registry-queries requires the record: "An authorization server records the trust signal it relied on ... on its acceptance record or resulting grant. The lookup time matters because a status may be withdrawn later, and the record has to show what was true when the server relied on it." Persist the reliance record on the grant and report it under `pdpp.trust_signal` in introspection. It is captured at the moment of reliance, in the one function that resolves a URL-hosted identity, so the lookup time is truthful rather than re-derived at issuance. A grant issued to a pre-registered client carries no signal and reports none: the server relied on its own registration table, not on an assertion, and absence is the honest record of that. The record lives in a new `grants.trust_signal_json` column rather than inside `grant_json`, because the resolved-grant contract is `additionalProperties: false` and ships from a vendored package this repo does not author; an extra member there would fail every existing grant read. The column is additive and nullable on both backends, so grants issued before it existed read as NULL. No signal is back-filled, because none was relied on. The record never rides the pending request. Approval re-resolves the client a moment before it issues, so the grant takes the signal from that resolution on every path, and the approval review artifact keeps its closed client shape. The reliance record does not belong in that artifact in any case: it is grant provenance, not a term the owner agreed to. Beyond the tuple the spec names, the record carries one field: `method`, distinguishing a same-origin document from an https fetch. A relying party needs it to reproduce the decision the server made. Forward the request's base URL through the staged-batch approval as well. The re-resolution needs the issuer origin to read a same-origin metadata document from local storage; without it the batch path falls through to a network self-fetch of the server's own origin, which is what a CIMD client staging a batch hits today. The forward is conditional on a base URL being present, so every other batch makes the same call as before. Carry the spec oracle this makes pass, and assert the record on a batch-issued child grant under both storage backends. Assisted-by: AI Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
tnunamak
force-pushed
the
feat/cimd-trust-signal-retention
branch
from
September 14, 2026 16:12
5f473ad to
6714cf4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
When a client presents an https URL as its
client_id, the AS verifies domain control and then throws the result away. The resolver returns a bareRegisteredClient, so nothing on the issued grant records which signal the AS relied on, or when it looked it up.spec-core.md#trust-registry-queriesrequires both. Onmain,trust_signal,looked_up_atandframework_urimatch zero times in the tree, so anyone auditing a grant after the fact cannot tell whether the client's identity was ever checked.This stamps the record where the check happens, inside
resolveCimdClientForGrant, and reports it underpdpp.trust_signalin introspection. It carriesmethodas well, separating the same-origin branch from the https fetch, so the decision can be reproduced later rather than re-derived.The record goes in a new nullable
grants.trust_signal_jsoncolumn, not insidegrant_json.ResolvedGrantSchemais vendored withadditionalProperties: false, so an extra member there would fail every existing grant read. Pre-existing grants read NULL and are not back-filled. No signal was relied on for them, and inventing one would be the opposite of the point.Staged-batch approval also now forwards the request's base URL into its re-resolution. That drop is pre-existing on
main. Without the base URL, CIMD batches fall through to a network self-fetch and fail at approve, so the batch case below cannot be exercised without fixing it.What the evidence shows
The leg-3 oracle from #56 fails 1 of 3 on
mainand passes 3/3 here, on SQLite and Postgres 16 — a grant issued through the https-document path reads its signal back through introspection.cimd-trust-signal-batch-path.test.tsissues a staged batch and asserts each child reports the record through its own token's introspection, so the stamp survives the re-resolution path rather than only the direct one. Both backends.Two controls were run and reverted after. Dropping
g.trust_signal_jsonfrom the Postgres join reddens Postgres alone. Restoring the record onto the pending request's client, rather than the resolved child, reddens the batch test at review. Each assertion therefore depends on the change it claims to cover.For the upgrade path, the column was dropped, a legacy row inserted, and bootstrap re-run on both backends: the column returns, the row survives, and it reads NULL.
Not verified
method: "https_document_fetch"is not exercised end to end. Both harnesses are same-origin by construction, so that branch is covered only bycimd.test.tswith an injectedfetchImpl.Assisted-by: AI