Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
137 lines
8.9 KiB
Markdown
137 lines
8.9 KiB
Markdown
# Thin Access Approval fact reader — owner source contract proposal
|
|
|
|
Owner scope: Approval Engine / PIP. Work: APPROVAL-WP-0005-T01, selected source
|
|
subset of APPROVAL-WP-0002-T01/T05 under CUST-WP-0080. This interprets the existing
|
|
claim schema 0.1, not a new API/schema/signature or policy grant. Access's B2
|
|
implementation stays DENY until its separately reviewed complete successor.
|
|
|
|
## Authoritative source and guarantee
|
|
|
|
Only server-installed, independently admitted provider origin and transport may
|
|
GET `/v1/approvals/{id}/claim`, with least-scope `approval:read` credential and
|
|
exact admitted store tenant. No caller URL/opener/key/verified flag, redirects,
|
|
environment proxy, caller JSON or public same-channel key bootstrap can supply
|
|
this provenance. Owner transport must enforce total deadline/size, exact 200
|
|
application/json UTF-8, no encoding, duplicate-key/nonfinite/malformed JSON
|
|
refusal, no retry and bounded errors without bearer/body leakage. Provider
|
|
unavailability refuses this source join. Never invoke a credential-carrying
|
|
Python subprocess as the Go adapter; reuse Go's existing transport boundary.
|
|
|
|
At the provider observation, `valid_now: true` AND `binding.human_control: true`
|
|
means the configured required count of distinct entries is met and every entry
|
|
has verified `principal_type=human`. `/entries` obtains identities/types only from
|
|
its authenticator, rejects non-human binds for this declaration, and persists
|
|
UNIQUE(approval_id, subject_id); `_evaluate` repeats the threshold/human check.
|
|
It does NOT mean required_count is two, requester/actor/principal is human,
|
|
directory-derived tenant membership, a particular MFA/assurance level, policy
|
|
eligibility, uncompromised source, native issuer admission, or continuing
|
|
validity after the observation. Consumer trusts this owner evaluation instead
|
|
of fetching identities/counting approvers or revalidating human JWTs. Native
|
|
issuer/profile acceptance is still T01, not established by synthetic vectors.
|
|
|
|
## Minimal typed fact validation
|
|
|
|
The submitted claim and fetched observation must each satisfy existing schema
|
|
0.1 with exact JSON types; `human_control` must be present and exactly true on
|
|
this lane despite being historically optional in the generic schema. Require
|
|
schema_version=0.1, kind=approval-claim, issuer=approval-engine, exact expected
|
|
approval UUID, state=valid, valid_now=true, consumed=false, reason_code=ok,
|
|
pdp_path=true and non-null lowercase sha256 pdp_digest. Reject decision-shaped
|
|
keys and unknown schema members (except optional informational yields_to).
|
|
The signed/requesting service must never become a human approver by inference.
|
|
|
|
At one captured UTC validation instant now, require zoned RFC3339 timestamps,
|
|
positive integral ttl_seconds, observed_at<=now<not_after and
|
|
not_after=observed_at+ttl_seconds. Require not_before<=now<expires_at. Do not
|
|
invent skew or extend TTL; a separately admitted clock policy is needed to
|
|
relax the zero-skew default. The provider default TTL is 30 seconds but source
|
|
configuration may differ; an Access-owned maximum may only shorten it.
|
|
|
|
Compare exact five-field act against the owner-derived normalized proposed act,
|
|
including all nested target values; no action vocabulary translation is owned
|
|
here. Recompute native binding.digest from those five fields and require exact
|
|
served and expected digest correspondence. Match pdp_digest to Access's actual
|
|
ApprovalBindingDigest over the exact normalized original request (excludes only
|
|
top-level context.approval). Preserve execution_contract and all other context.
|
|
Never substitute full request_digest, native act digest or a guessed Go digest.
|
|
The PDP digest proves correspondence, not decision authenticity or approval
|
|
source authenticity. Registry/normalization changes must fail or reissue.
|
|
|
|
## Original request is immutable; fresh observation stays out-of-band
|
|
|
|
The original submitted context.approval must remain byte/value-equivalent under
|
|
Access's existing canonical request rules. Do not replace it, update freshness,
|
|
change binding/declarations, or manufacture an evaluated refreshed request.
|
|
Validate its own original freshness/validity too; a newly fetched observation
|
|
cannot rescue a stale original claim. Require all non-freshness authority facts
|
|
to match between the submitted and fetched claims: schema/kind/issuer/id,
|
|
state/valid_now/consumed/reason_code, binding and validity. Optional yields_to
|
|
is informational only. Each native binding is independently recomputed.
|
|
Freshness is validated separately on both observations; a later observation
|
|
may have a later freshness window without changing the signed request.
|
|
|
|
Return a package-private/server-created typed observation handle only after
|
|
these checks. Access may bind its source identity/revision, observed_at,
|
|
not_after, fetched-claim content digest and validation time into owner-produced
|
|
signed provenance separately from unchanged request_digest and
|
|
approval_binding_digest. This does not require changing the Approval schema;
|
|
Access owns the minimal existing provenance representation and signer contract.
|
|
An HTTP caller cannot instantiate this handle or set its verified state.
|
|
|
|
Suggested Go boundary, for Access to implement in its existing process:
|
|
`ApprovalFactSource.Observe(ctx, approvalID) (Observation, error)` reads only
|
|
provider bytes plus source provenance; a separate package-private validator
|
|
joins Observation with the immutable normalized request, submitted claim and
|
|
captured validation time. The handle retains detached raw provider bytes and
|
|
strict parsed numbers (json.Number), not caller-installable validation flags.
|
|
Neither this reader nor its validator evaluates policy. Access's existing
|
|
single policy evaluator consumes the verified factual handle without rewriting
|
|
the request. Callers may not supply a ready-made Observation on the HTTP body.
|
|
|
|
Effective input deadline is the minimum of ORIGINAL submitted freshness,
|
|
fetched freshness, approval expiry, authenticated caller expiry and operation/
|
|
decision deadlines. No equality at an expiry boundary is accepted. Refetching
|
|
does not renew the original full request. Revocation/use may race a read; the
|
|
PEP still performs one original exact consume CAS before effect and retains
|
|
the same live Platform journal admission, deadlines and unknown-outcome rules.
|
|
Access never consumes or executes. Historical confirm_consumption cannot
|
|
substitute for this fresh predecision source validation.
|
|
|
|
## Go canonicalization and golden vectors
|
|
|
|
`examples/access-claim-reader-vectors.json` contains producer-generated native
|
|
canonical UTF-8 bytes/digests and source lifecycle observations, plus explicitly
|
|
mutated refusal fixtures. Python dict equality and Go decoding numbers into
|
|
float64 lose authority distinctions: True/1, 1/1.0 and integers above 2^53 differ.
|
|
Go default json.Marshal additionally escapes HTML/U+2028 differently. Retain
|
|
number tokens with UseNumber and implement/test compatibility with Approval's
|
|
existing Python json.dumps(sort_keys=True,separators=(',', ':'),ensure_ascii=False)
|
|
format; do not silently normalize types or hash whole binding/freshness.
|
|
UseNumber preserves number tokens but is NOT a canonicalizer: producer parsing
|
|
of `1e-7` emits `1e-07`, `1e20` emits `1e+20`, `1.0` remains distinct from `1`,
|
|
and negative floating zero emits `-0.0`. A signed typed Go envelope alone does
|
|
not preserve or authenticate Python's original numeric representation.
|
|
|
|
These finite vectors are a minimum, not proof of a complete Python float/string
|
|
port. A thin pilot may fail closed on unsupported numeric/string domains only
|
|
if its actual owner-compiled operation contract excludes those values and Access
|
|
declares/tests that boundary. It may not silently change the global Approval
|
|
algorithm or accept a digest without checking the proposed act. Required vectors
|
|
cover bool/int/float distinction, large integer, Unicode/HTML/U+2028, reordered
|
|
keys, nested containers, exponent formatting and negative floating zero. These
|
|
are authoritative producer vectors for the domains shown, not arbitrary new
|
|
restrictions on current pilot metadata. No currently authoritative narrow numeric
|
|
restriction was established by this review, so it cannot be presumed. If a
|
|
small shared producer-owned Go canonicalization helper becomes necessary, scope
|
|
it explicitly to the actual consumer need and test against these producer bytes;
|
|
do not invent a second unverified serialization algorithm in the adapter. This
|
|
initial review provided vectors only. Subsequent scoped WP0005-T03 supplies
|
|
`adapters/go/approvalfact`, a producer-owned pure data implementation agreed with
|
|
Access; it adds no credential transport/runtime. The Go consumer owner must
|
|
return exact accepted/refused vector results and transport negatives
|
|
(redirect/proxy/duplicate/size/deadline).
|
|
|
|
The superseded service-http-join-proposal-r1 B3 refreshed-evaluation wording is
|
|
changes-requested for this immutable-context path. B2's source reader can proceed
|
|
against this proposal after Access's own design/conformance review. No foreign
|
|
owner assent, policy activation, live credential or native acceptance is implied.
|