Repository navigation
App Attest step 4: the six device routes on the approval broker as body-authenticated POSTs; wire header rows deleted, vectors regenerated, Swift mirror (stacked on #11989) - #12000
Merged
Conversation
…thn authenticator-data decoders in .dag, claimed over a real Apple-issued attestation The first of the pure byte decoders the App Attest verifier needs (extdeps.apple.app_attest attestation_verification_from_implementation has no supplier on main). Three extdeps modules, each cited: extdeps.standards.rfc_8949 (strict CBOR: definite-length only, floats and reserved additional info refuse, integers beyond 2^53 refuse before the multiply, nesting bounded, unique text-key member read), extdeps.standards.rfc_9052 (COSE_Key EC2 P-256 ES256 -> SEC 1 point), extdeps.standards.webauthn_authenticator_data (the §6.1 layout, whole and 37-octet prefix). The fixture is a real attestation object and assertion Apple issued to a development build (veehaitch/devicecheck-appattest ios-14.4.yaml at cb26211f), transcribed as octets with its published facts; over it the decoders establish Apple's steps 4, 5, 6, 7 and 8 from real bytes. One upstream fact the sample surfaced: Apple's assertion authenticatorData is 37 octets with the AT flag set and nothing following, so assertions are read as the prefix, never the full layout. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ers in .dag and the pinned Apple App Attestation Root CA, claimed over the sample's real certificate chain extdeps.standards.x690_der reads the DER profile and refuses BER's freedoms by name: indefinite lengths, non-minimal long-form lengths, high tag numbers, unused BIT STRING bits, non-minimal or negative INTEGERs. extdeps.standards.rfc_5280 reads Certificate/TBSCertificate/ AlgorithmIdentifier/Validity/SubjectPublicKeyInfo/Extension, refuses an outer signature algorithm that disagrees with the signed one, looks extensions up uniquely, and renders ECDSA-Sig-Value as fixed-width r||s for the curve verifiers. extdeps.standards.rfc_7468 reads one PEM block. extdeps.apple.app_attest gains the root CA pinned byte for byte from apple.com. Over the sample chain the readers establish, from real bytes: Apple's steps 2 and 3 (the leaf's nonce extension equals SHA-256(authData || clientDataHash)); the leaf's SPKI point IS the credential key the authData carries; leaf -> CA 1 -> pinned root join by DER Name equality; CA 1 and the root are secp384r1 / ecdsa-with-SHA384, so the chain step needs P-384. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ther than re-matching CborValue (review 69585) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…P-256 and P-384 as parameter rows, SHA-384, and the frontier retargeted to the decided shape extdeps.crypto.nist_prime_curve carries the a = -3 Jacobian group law, Shamir double-scalar ladder, partial public-key validation and ECDSA verification over a SUPPLIED digest, written once (the formulas #11645 landed for P-256, moved not rewritten). extdeps.crypto.nist_p256 is now its parameter row plus the SHA-256-bound verifier the device signature uses; the new extdeps.crypto.nist_p384 is the second row, consumed by Apple's chain step, whose two links are a P-384 key signing with SHA-256 and a P-384 key signing with SHA-384. extdeps.crypto.sha2 gains SHA-384 (the SHA-512 computation with its own initial state, truncated) over std.bitwise Word64, its constants derived from the definition rather than transcribed. P-256 semantics preserved by execution: the RFC 6979 sample signature verifies and the different-message control is false through the family fold (88 s / 86 s, 56M / 55M eval steps, peak 15.5 GiB), and n.G = infinity holds. P-384 established on real bytes: Apple's CA 1 and root keys are on the curve, a neighbouring point is not; the two chain-link verifications are enrolled and NOT executed here (at 17 limbs they exceed this container's memory), the same disposition as #11645's eight P-256 rows: a dedicated native gunbc test row is their executing evidence. gunbc.auth.approval_device_redemption ecdsa_verification_realization_frontier no longer names the host-Rust primitive the operator refused; it names the .dag readers landed by #11970/#11975/ this change, the verifier folds still owed, and the execution route decision (natively emitted handler once MachineWidth<N> reification lands; interpreter cost is the v1-performance row). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ture's ByteSize authority; the two guard-shadowed zero defaults (CBOR argument octets, authenticator-data octets) now refuse as located truncations Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t or a second block; OID subidentifiers refuse a leading 0x80 (X.690 §8.19.2), and the witness now asserts that refusal instead of announcing it Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dag, Apple's real assertion signature verified by execution, and the signature-seam join extdeps.apple.app_attest verify_attestation runs Apple's ten steps over the readers: CBOR shape and fmt, the nonce extension against SHA-256(authData || SHA-256(clientData)), the credential certificate's public key against the key id, RP ID, counter 0, AAGUID for the environment, credentialId, chain names and validity at the observed instant against the pinned root, the validation-category and bundle-version extensions, and last -- because they cost minutes -- the two chain-link ECDSA verifications. verify_assertion runs steps 1-4, 7, 8 and carries the counter out. Both supply the seams; nothing else may mint VerifiedAttestation / AuthenticAssertion. Established on Apple's real objects: the attestation passes every step up to the validation category the 2021 sample predates (AttestationValidationCategoryAbsent is the first refusal), each earlier step reds by name when its expectation is wrong, and observing it in 2026 finds the credential certificate expired at 2021-01-25T12:13:35Z. THE ASSERTION SIGNATURE VERIFIES under the credential key -- after a defect found only by executing it: Apple's nonce is the signed MESSAGE (ECDSA-with-SHA256 over the 32 nonce octets), not the digest; the first cut refused and openssl confirmed the reading. extdeps.crypto.signature verify_signature is the join the frontier called (b): base64url carriers -> measured sizes -> the seam -> nist_p256; RFC 6979's sample verifies through it and is SignatureInvalid over another message. rfc_5280 gains UTCTime/GeneralizedTime -> Timestamp and validity at an instant. The frontier row now names the execution route as the only remainder. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-> 11, 384 -> 16), not ceiling + 1; annotations corrected. Re-measured: one P-256 verify 73-75 s / 48.5M eval steps, from 88 s / 56M at the extra limb Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…read (X.690 §8.6.2.3); every positional TBS, outer, SPKI, AlgorithmIdentifier, Extension and ECDSA-Sig-Value read asserts its ASN.1 type through der_element_expect, consuming the tag constants that were imported and unused Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ture fields (the placeholder-filled certificate has no constructor); left_pad consumes std.bignat bignat_pad_front instead of re-minting a zero fill Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mily fold reads the octet rows), the witness's limb count corrected to 16, and the SHA-256/SHA-512 two-pipeline divergence stated on the carrier with its collapsing capability (MachineWidth<N> reification) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the authenticator data are decoded once (decode_attestation) and carried to the refusal fold and the success mint; the assertion document is decoded once for its counter and its refusal Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…of re-declaring its fields; extnValue and Time reads assert class and form through the typed reads; an explicitly encoded critical FALSE refuses (X.690 §11.5); the never-called der_expect_universal deleted and der_element_expect_universal consumed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…in counter, key or receipt reaches the seam), a non-unsigned validation category is Undecodable rather than category -1, chain_link_verifies is claimed at its producer (three refusing arms executed over the real leaf and CA; the two verifying links enrolled with the P-384 cost stated), and nist_p256's header says its consumers are the declared frontier; the validity fold reads the nested tbs Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…y one a POST whose body carries its authentication; the wire's header rows deleted; vectors regenerated; Swift mirror
Decision msg_5b415336 (B): gunbc serve hands a route no header but the tailnet login, and the
seed's purpose standing refuses growing it for the approval product, so the enrolment id, the
claimed time and the App Attest assertion ride in the body (ReadAuthentication; PushUpdateRequest
{ auth, push }). The assertion's client data is unchanged.
gunbc.auth.approval_device_routes: enrol (writer gate, verify_attestation over the enrolment
transcript, enrolment_admission, commit, push registration), redeem (writer gate, verify_signature
under the decision key and verify_assertion over the signing input into redeem_device_over_store),
pending / one request / enrolment readback (admit_device_read: active enrolment, +-60 s skew,
assertion over the route's framed client data; the request answer mints a MAC-keyed challenge
and both verb capabilities), push update, and the operator's code-issuing verb as a CliWire
answer. gunbc.auth.approval_broker_serve mounts the six with their own markers; gunbc.roadmap_serve
classifies all six as never mounted by the roadmap. gunbc.durable_cas_file_store cas_slot_keys
(the store's own inverse of its layout) and approval_decision_store pending_escalations list what
the phone lists. gunbc.auth.approval_app_attest_config is AwaitingOperator until the App ID prefix
and bundle version land; every verifier-needing route refuses 503 naming that until then.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e), the pinned root's SHA-256 is a data row computed by a claim over the decoded PEM, and a Certificate, TBS tail or AlgorithmIdentifier with children the profile does not name refuses instead of reading past them Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…/ SignatureUndecodable, not Malformed at a fabricated size 0
decoded_or_empty mapped a non-base64 key or signature to [], so verify_signature reported
VerifyingKeyMalformed/SignatureMalformed { declared: 0 } -- a size never observed (review 70731,
DESIGN section 5). Deleted it; verify_signature now refuses with a typed arm naming the carrier.
Consumers of SignatureVerification all match with wildcards. Claims: non-base64 key and signature
each reach their arm, and "==" (decodes to zero octets) stays VerifyingKeyMalformed at declared 0.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # docs/design-rung-drops.md
Ledger-Repair-Judged: docs/design-rung-drops.md Ledger-Rows-Repaired: docs/design-rung-drops.md microvm_shakedown_slot_row_above_fleet_row Ledger-Rows-Repaired: docs/design-rung-drops.md network_requirement_unrepresented_after_uses_cut Ledger-Rows-Repaired: docs/design-rung-drops.md v41_row_store_host_realized_by_sample Ledger-Rows-Repaired: docs/design-rung-drops.md admit_callers_discarded_on_the_native_route Heal-Candidate-Run: 35951131764
Closed
6 tasks
The merge conflict on the generated projection was resolved from its authority (docs_projection_gate regen), not by picking a side. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ct-bound claims to the wet lane The floor on 7b217bc refused three identities in test.claim.approval_device_routes_witness_test: - the_redemption_refuses_503_before_any_crypto_while_the_native_handler_is_unbound ran 76957 eval steps against the 72300 new-witness budget. Nearly all of that was fx_signing_input building the redemption vectors, which mint real capability tags through interpreted hashing. This claim is about the gate and never looks at that input. It now supplies a well-formed signed redemption body and runs 21357 steps. The real vector's decode stays exercised by test.claim.approval_device_wire_witness_test, so no execution of the real path is lost. - the_read_skew_window_brackets_sixty_seconds (Clock.TimestampAdd) and a_bound_realization_passes_the_read_past_the_gate (the enrolment store's Filesystem.Read) can't reach their subjects on the hermetic route. The skew claim's own comment already said it was meant for the wet route, but it was never enrolled. Both move to test.claim.approval_device_routes_wet_witness_test. Each gets the full triple enrolment: a floor_route_gap expectation, a WetScheduledClaim, and a ci_layer_roots LocalRepoWetLane row with its own reason and dissolution condition. Local receipts: claim_batch passes all 13 hermetic claims. claim_batch --wet passes both moved claims. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Sep 24, 2026
…12000's head (which already carries main); elevation's command rows kept; mode roster unioned; fleet-converge.yml main's side pending regen Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Sep 24, 2026
…fter the main + #12000 merges Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…minus, skew claim back to hermetic device_instant_minus handed 0 - 60 to Clock.TimestampAdd's `offset: Nat`, which is a carrier violation. It only worked because GNU date accepts "+-60 seconds". The skew window never needed a shifted instant. It needs the signed distance between two canonical instants. So gunbc.auth.approval_capability now carries utc_instant_seconds and utc_instant_seconds_after beside utc_instant_before. Both are pure folds over the fields utc_instant_is_canonical already validates, counted from a Gregorian origin shifted one 400-year cycle so every term stays non-negative. requested_at_within_skew compares integers, and now also refuses a non-canonical observed instant. device_instant_minus had no other callers and is deleted. device_instant_plus keeps its positive, effectful uses (challenge and enrolment-code expiry off a live Clock.Now). the_read_skew_window_brackets_sixty_seconds is back on the hermetic route, with its wet schedule row and route-gap expectation removed. It also gains a leap-day crossing, a year crossing, and a non-canonical observed instant. The bound-realization control stays wet. device_pending_response_at takes no store, so the store Read can't be supplied at that interface. Its exclusion row now names only that Read, and its dissolution condition names either route that would bring it back to hermetic. Local: claim_batch 14/14 hermetic (skew claim 5982 eval steps); claim_batch --wet passes the control. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 24, 2026
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Sep 24, 2026
…2942, route_gap_unenrolled=3) The two enrolment-code claims and the ntfy readback root move to *_wet_witness_test.dag files with the triple enrolment #12000 uses: FloorRouteGapExpectation rows (chunk_25), WetScheduledClaim rows, and LocalRepoWetLane rows in gunbc.ci_layer_roots with named reasons and dissolution conditions. Per the section 3 witness rule the mint's encoding is split out as enrolment_code_of_entropy and witnessed hermetically over supplied octets (16 -> exact hex, 15/17 -> refused); the real Urandom mint stays as the one wet inhabitance claim. The root claim now asserts only what holds on every host (refused before minting, store never created): every reading's verdict is a refusal, ending at ActiveProcessJoinsUnbound. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
briansrls
approved these changes
Sep 24, 2026
briansrls
left a comment
Contributor
There was a problem hiding this comment.
APPROVE-MERGE at exact head 70687a30413d3797c53b3e870762f2fd2750f68c.
The current main-based tree preserves the reviewed App Attest step-4 design and resolves the two source conflicts correctly.
approval_broker_serveconsumes the relocatedHttpMethod/GET/POSTauthority and still carries exactly six device handlers, each mounted only as POST and dispatched to its corresponding route fold.floor_route_gapkeeps main's chunk-13 population and appends exactly the bound-realization wet control. That identity is also inlocal_repo_wet_schedule; its first unmocked effect is the enrolment-storeFilesystem.Read, so the wet standing is honest rather than a designed substitute.docs/design-rung-drops.mdis regenerated, and the exact-head generated-artifact gate passes.
The security-critical route seams remain coherent:
- the three unreadable
X-Approval-*header authorities are deleted; reads carry strictReadAuthenticationJSON and push carries{ auth, push }; - the Swift client sends the same six POST shapes and signs the same canonical route/client-data projections the server reconstructs;
- writer admission precedes decoding on enrolment, redemption and push-update writes;
- native crypto admission precedes store reads or verifier calls and remains fail-closed until one receipt covers P-256 signature verification, App Attest attestation and assertion verification, and the P-256 base-point-order fact;
- read admission requires an active enrolment, canonical timestamps in the
[-60s,+60s)window, an assertion over the exact route bytes, and an atomic per-enrolment counter advance before the operation; - path identities are canonicalized and the store rejects non-slot-addressable keys before any filesystem read.
The later repairs are accepted:
device_instant_minusis deleted. Skew is now pure signed-second arithmetic over fully canonical UTC instants, with leap-day, year-boundary, exact-boundary and non-canonical controls.- The redemption-gate claim supplies a decodable signed-redemption body at the route interface instead of paying to mint capability tags it never inspects. The real body decoder remains executed by the wire witness, so this is a legitimate witness split rather than lost evidence.
- The one bound-realization control remains wet for the structural reason stated:
device_pending_response_atowns the store read and accepts no store observation. Its route-gap and wet-schedule enrollments agree. CasGenerationSpaceExhaustedis carried throughgunbc.compute.attempt_lifecycleas its own typed store refusal rather than becoming a non-exhaustive or generic failure.- The prior linear-generation lifetime cliff is absent; the counter wet population covers replay, per-enrolment standing, races, interleaving and operation beyond the former 4096 bound.
Run 36060763867 passes compiler, clippy, emit-build, floor and witnesses at this exact SHA. GitHub reports CLEAN and mergeable.
No source condition remains. Current main has advanced by two emitter commits since this head; the merge queue must adjudicate that composed revision normally. A queue red is a repair/requeue event, never grounds to bypass the queue.
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Sep 24, 2026
…by the d6f3ef1 merge (floor 36065148040, route_gap_unenrolled=2) The chunk_13 conflict was resolved to main's line on a misread diff; main never carried test.claim.approval_device_routes_wet_witness_test's TimestampAdd and Read expectations. Restored verbatim from #12000's head 50c93dc. Audit: every line #12000 adds is present in this tree except approval_device_routes' original issuance verb, which this PR replaces by design. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Sep 25, 2026
Conflicts resolved as a three-way merge against #12000's pre-landing head 50c93dc as base, so #12000's landed fixes and this PR's changes both apply. floor_route_gap chunk_13 takes the landed line: #12000 moved the read-skew claim to the hermetic file, so its wet expectation is gone by design. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Sep 25, 2026
Resolves the project.yml conflict with #12000 by deriving its MARKETING_VERSION and CURRENT_PROJECT_VERSION in gunbc.approve_ios_project (MARKETING_VERSION and PRODUCT_BUNDLE_IDENTIFIER read from gunbc.auth.approval_app_attest_config); both generated files regenerated. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This was referenced Sep 25, 2026
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.
Step 4 of the iOS approval-app plan: the six device routes on
gunbc.auth.approval_broker_serve, under decision B (msg_5b415336: every device operation is a POST whose body carries its authentication —gunbc servehands a route no header but the tailnet login, and the seed's purpose standing refuses growing it for the approval product). Stacked on #11989 → #11981 → #11975 → #11970.Wire (
gunbc.auth.approval_device_wire) — one shape, no headersX-Approval-*header rows are deleted.ReadAuthentication { enrollment_id, requested_at, assertion_b64 }is the POST body of the three reads;PushUpdateRequest { auth, push }is the push-update body. Strict decoders with located refusals, same idiom as the rest of the wire. The assertion's client data (device_read_client_data,device_push_update_client_data) is unchanged — moving the carrier off the headers changed no signed byte.gunbc.auth.approval_device_redemption_fixturesrendersread_authenticationandpush_update_requestenvelopes and amethod_every_device_operation = POSTsurface row;dag/test/fixture/approval_device_redemption/vectors.jsonregenerated from the renderer (generated_artifact_gate main_wet_one), never hand-edited.Wire.swiftdropsReadHeader, sendsreadRequest(auth)/pushUpdateRequest(auth, push)bodies over POST for pending/fetch/readback/updatePush;ProtocolVectorTestschecks the two new envelopes against the fixture bytes and the method row. (Still the first Swift this project has never executed — operator item.)Routes (
gunbc.auth.approval_device_routes, mounted byapproval_broker_serve)Six folds, each returning
{status, JSON body}; the broker owns routing, markers (six newApprovalBrokerMarkerarms) and the process; the redemption/wire/verifier modules own every decision:POST /approve/device/enrol— decode → writer gate → verifier config →enrolment_admissionoververify_attestation(client data =enrolment_transcript) →commit_enrolment→ push registration stored (owner-only, last-write-wins file beside the slot) →EnrolmentGrant.POST /approve/device/redeem— decode → writer gate →redeem_device_over_storewithverify_signatureunder the enrolment's decision key andverify_assertionover the signing input as verdicts.POST /approve/device/pending/…/requests/<segment>/…/enrollments/<segment>—admit_device_read(active enrolment,requested_atinside the ±60 s skew of the observed instant, assertion over the route's framed client data) then the pending listing / aFetchedRequest(stored request text, a MAC-keyedRedemptionChallengewith a 5-minute window, both verb capabilities viaissue_capability) / the readback.POST /approve/device/push— writer gate + read admission, then the registration.issue_device_enrolment_code(aCliWireResponse): 4 entropy octets → 8 hex chars, 10-minute window, written throughissue_enrolment_code; never a route.gunbc.roadmap_serveroadmap_mounts_during_cutoverclassifies all six as false — the total match the module built for exactly this reason.Supporting authorities:
gunbc.durable_cas_file_storecas_slot_keys(the store's own inverse of its layout — the one place a name becomes a key),gunbc.auth.approval_decision_storepending_escalations(keys from the listing, each standing throughobserve_escalation; an unreadable slot is carried, not dropped), andgunbc.auth.approval_app_attest_config—AppAttestVerifierAwaitingOperatortoday: bundle idai.gunb.approveand the production environment are known; the App ID prefix and the distributed bundle version are the operator's, and until that row changes every route that needs the verifier refuses 503 naming what is missing (no guessed prefix).Established by execution (
claim_batch, exact head ba3dda3)test.claim.approval_device_routes_witness_test(8): malformed bodies 400 on all six routes naming where; bad/non-canonical segment 400; readback for another enrolment 403 before any verification; the non-writer process 503 with the writer reason on a mutating route (which process is the non-holder is read from the gate); reads 503 "not configured" while the config row is awaiting the operator (the claim reads the row's arm, so it flips when the row does); the skew window brackets exactly [−60 s, +60 s) (on the wet route —Clock.TimestampAdd); the redemption status map; the roadmap mounts none of the six.test.claim.approval_broker_serve_witness_test: the table selects the ten routes, the six device routes only as POST (GET → 405), and carries exactly ten.test.claim.approval_device_wire_witness_test(28): the new bodies round-trip and refuse strictly atauth/assertion_b64/push.Mutation control: readback identity check inverted →
a_readback_for_another_enrolment_refuses_403FAIL,a_bad_segment_refuses_400PASS; restored.What this does not establish (honest)
The admitted paths — a real enrolment committing, a real redemption deciding, a real read answering — need (a) the operator's App ID prefix + bundle version in
approval_app_attest_config, (b) a live store, and (c) the ECDSA execution route (decision A). They are the declared frontierecdsa_verification_realization_frontierand are not faked with designed fixtures.Nothing fired at srv1.
🤖 Generated with Claude Code