Skip to content

ENCODING-0 cut 2: pure .dag CBOR, DER and X.509; App Attest enrolment parses real bytes and refuses at the ECDSA frontier - #11727

Closed
gunbai-bot[bot] wants to merge 28 commits into
mainfrom
session/warm-tern-701
Closed

gunbai-bot[bot] wants to merge 28 commits into
mainfrom
session/warm-tern-701

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Stacked on #11643 (NUMERIC-BIT-0). Every bit operation here goes through std.machine_word. Retarget to main once #11643 lands.

What

Pure .dag CBOR, DER and X.509 parsers. Their first production consumer is the App Attest enrolment route's parse stage. This implements ruling A (fierce-seal-607, recorded on #11592): no host parser, #11592's fixtures are the oracle, and ECDSA stays the typed unbound frontier.

  • std.octet_span: the one byte-offset authority (ENCODING-0: one modeled authority for bytes, Base64, CBOR, DER and X.509 parsing #11629 rule). Every parser names what it read as a half-open span over the caller's single input. Integer and bit-field reads go through std.machine_word (word_shift_right, word_and, word_from_octets), so no parser open-codes a shift or a mask.
  • extdeps.ietf.cbor: an RFC 8949 decoder. Every departure from deterministic encoding gets its own typed refusal: non-shortest argument, indefinite length, reserved info, duplicate map key (compared by wire octets), trailing input, nesting bound, truncation. Floats and 8-octet arguments refuse as unsupported (std.machine_word does not realize 64-bit words).
  • extdeps.itu.der: X.690 DER. TLV framing, exact tiling of children, minimal lengths and INTEGERs, BOOLEAN FF/00 only, BIT STRING unused-bit rules, OIDs (non-minimal subidentifiers refused). The high-tag-number form refuses as unsupported.
  • extdeps.ietf.x509: the RFC 5280 fields App Attest consumes, plus the structural part of 6.1 path validation. That covers name chaining, validity, basicConstraints cA and pathLen, keyUsage keyCertSign, agreement and admission of the signature algorithm, and refusal of unhandled critical extensions. It never checks a signature: a passing chain is X509ChainStructurallyValid.
  • extdeps.time.posix_epoch plus extdeps.time.rfc3339 rfc3339_epoch_seconds: one calendar authority. The route derives certificate validity's now from the same observed_at it uses for code expiry, so there is one instant and not two parameters.
  • extdeps.apple.app_attest:
    • Refusals are now typed (AttestationMalformed { AppAttestDecodeRefusal }, AttestationChainUntrusted { X509Refusal }) in place of the old String causes.
    • The pinned Apple App Attestation Root CA is DER; its SHA-256 fingerprint was checked against Apple's published value.
    • app_attest_verify runs every step it can decide without cryptography, in Apple's order: structural chain, counter, AAGUID environment, credential id equals key id, and validation category under an explicit AppAttestLaunchExtensionPolicy. That policy is swift-ibex-621's finding (b): a missing launch extension is the caller's decision. When all of those pass, it refuses with AttestationVerificationUnrealized { AppAttestEcdsaSignatureVerification }.
    • app_attest_verify_assertion decodes, then refuses at AppAttestSha256Digest.
    • Neither ever mints Verified. The only constructor of a verified value is still *_verification_from_implementation.
  • gunbc.auth.approval_device_redemption: the new ios_enrolment_admission is the production route that feeds enrolment_admission from real attestation bytes. ecdsa_verification_realization_frontier is narrowed: the CBOR/X.509 decoding it used to name as unrealized host work now exists in .dag, and what remains is ECDSA plus SHA-256.

Evidence

Independent oracles only. RFC 8949 Appendix A and X.690's own examples, date -u for epochs. For App Attest, the genuine device attestation and assertion from #11592 (veehaitch/devicecheck-appattest, Apache-2.0), with expected fields read by openssl x509 -text and a direct byte read, not by this decoder.

witness result
cbor_rfc8949_witness_test 18/18 PASS
der_x690_witness_test 17/17 PASS
app_attest_parse_witness_test 19/19 PASS
approval_device_redemption_witness_test (existing) 32/32 PASS

The App Attest claims cover:

  • the genuine attestation decoding to its independently read fields;
  • the leaf certificate's key equalling the COSE credential key;
  • the decidable steps passing on genuine bytes, then refusal at the absent category (Required) or at the ECDSA frontier (Optional);
  • wrong environment, wrong key id, and expired or not-yet-valid refusals;
  • adversarial mutations of the genuine bytes: trailing CBOR, duplicate key, unknown key, certificate with trailing bytes, non-base64 input (swift-ibex-621's findings (e) and (f));
  • the route refusing genuine bytes at the frontier, and refusing a duplicate key with the decoder's own typed cause.

Bypass discriminator, by mutation (each mutant restored afterwards; tree verified clean):

mutant claims that go RED
CBOR duplicate-key check removed (cbor_key_seen -> false) a_duplicate_top_level_key_is_refused, the_enrolment_route_refuses_a_duplicate_key_through_the_modeled_decoder
DER exact-extent check removed (== -> <= in der_read_exact) a_certificate_with_trailing_bytes_is_refused

A route that walked the attestation bytes by hand would read the same fields and pass every happy-path claim. It cannot pass the route claim above, because only the modeled decoder refuses a duplicate key.

A finding the genuine fixture forced: Apple sets the AT flag (0x40) on assertion authenticator data that carries no credential data (37 octets). Whether credential data is read is therefore the caller's fact (reads_credential), not inferred from the flag.

Scope notes

🤖 Generated with Claude Code

Brian Searls and others added 28 commits September 18, 2026 18:14
… in .dag

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ise the fold accumulation

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

The bitwise claim was false and the fold was correct. 0x0FF00FF0 was written as
267391984, which is 0x0FF013F0, while the expected and/or/xor beside it were
computed from the pattern intended. .dag has no hex literal, so the conversion is
done by hand and read by nobody; a comment stating the intent cannot catch it
because no machine reads one. The transcription is now claimed against the octet
packing, which has its own pinned vectors, so a future mistype reds a claim that
names the constant rather than one that blames the fold.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…frontier as rows

Review 68053 (REQUEST_CHANGES), both findings.

(1) word_modulus walked int_pow_bounded on every operation to learn one of
four numbers, so every op paid a linear recursion with a checked multiply per
step just to read a constant off a closed coproduct. It now answers from the
closed set, and word_modulus_agrees_with_the_power_authority_holds runs the
table against std.induction int_pow_bounded for every member including Width64,
where both must answer with nothing -- the value is derived and checked, not
transcribed and trusted. word_shift_left also computed two powers where the
second is the modulus divided by the first.

(2) The operations landing ahead of their consumers said so in a // block, and
DESIGN 4c rules an annotation is not evidence a machine claim holds. They now
carry std.roster_frontier rows: DeclarationAppears bound to the exact symbols on
gunbc#11647 where the consumer exists to cite, unbound with a stated description
where it does not, because a forward citation to a name nobody has written can
never resolve. word_zero and word_equal had no consumer anywhere and were
deleted rather than described; word_rotate_left needs no row because
word_rotate_right consumes it here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ited instance

Verifying review 68053's first finding against the code surfaced two more
instances of the same defect that the finding did not name: word_wrapping_multiply
walked int_pow_bounded per call for its split point, and octet_radix did the same
for a number this module already answers. Fixing only the cited site would be
repairing the symptom rather than the boundary.

word_half_radix is derived once over the closed set and checked against the power
authority, as the modulus is; it refuses Width64 although 2^32 IS representable,
because a split point for a word with no residue would be answering about a
subject that does not exist, and that deliberate disagreement is claimed rather
than left looking like an oversight. octet_radix now READS the Width8 modulus
rather than deriving a second constant, so nothing new was introduced that could
go stale. The three surviving int_pow_bounded call sites all take a genuine
variable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…l signature

Review 68073. std.dissolution bound_dissolution takes ref: DeclarationRef and
does the DeclarationAppears wrapping itself; the four bound rows passed a
DissolutionTrigger under a keyword the signature does not declare. I wrote the
call from the TYPE it constructs rather than from the helper's own signature,
which is how a call can look right beside the type declaration and bind against
nothing. The floor refuses this, so the frontier claim could not have been green
-- neither of the two heads carrying it has a CI verdict yet, so this was caught
by review rather than by a run.

DeclarationAppears, DeclarationRef and DissolutionCondition were imported only
for that mis-shaped call and are dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s its expiry

Review 68088. The roster asserted in prose that gunbc.dissolution_census reads
it, while the census folds census_closure_frontier_row_groups, which it was not
a member of. So every row's expiry was computed by nothing: when
extdeps.crypto.sha2 sha256_add_all lands, a row outside the census is never
reported fired-still-present and its trigger cannot fire. That is the inert-lens
tier, and it is the same DESIGN 4c violation the roster exists to correct,
committed in the sentence claiming to correct it -- declaring rows in a typed
carrier is half of the discharge and being folded is the other half.

Enrolled in gunbc.census_closure_frontier and renamed to the roster's
_frontier_rows convention. The two existing claims assert the declarations
exist, which is not enrolment, so a third joins the rows against the census
closure itself by subject key; dropping the group entry reds it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 68118. word_from_octets was the one exported operation that did
arithmetic before guarding on its own width: it guarded on octet_radix, which
asks about Width8 and always answers, so a Width64 call passed the count and
per-octet checks and then accumulated eight octets to as much as 2^64 - 1 in the
Int the seed realizes as i64, with word_of_int refusing only afterwards. The
refusal was inspecting a number the realization had already wrapped or trapped
on -- the post-check this file's own word_wrapping_multiply annotation forbids,
and the thing that made the module's Width64 sentence false for one entry.

Every other exported operation was checked and guards on word_modulus first.

A Width64 call with the wrong octet count now reports WordWidthUnrealizable
rather than OctetCountMismatch, which is the right order: a width with no
residue the carrier can hold is the more fundamental fact.

The arm was unwitnessed, which is why it stood -- nothing reds on a path no
claim runs. Three claims now pin it, the from_octets one supplied the exact
eight-octet input that overflowed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t from this tree

The required declarations phase refused four CITED-MODULE-ABSENT: std.machine_word
cites extdeps.crypto.sha2 sha256_add_all / sha256_sigma / sha256_xor3 /
sha256_ch, and no module declares extdeps.crypto.sha2 here. It exists on
gunbc#11647 and nowhere else.

A DeclarationRef is a CITATION and the gate resolves it against the tree it runs
in, not against any branch. I had written the rule correctly on the issue -- a
forward reference to a name nobody has written can never resolve -- applied it to
three rows, and then broke it for four because I had READ those symbols on
CRYPTO-0's branch, which is what made citing them feel safe. Seeing a symbol
somewhere is not this tree being able to check it.

DeclarationAppears remains right for a forward reference into a module that
already exists; it is the absent MODULE that cannot be cited. The four consuming
symbols are now named in prose in each description, where they point a reader
without asserting a reference this tree must honour.

Floor was already clean on b290752: verdict=FloorClean, claims_failed=0, all 43
newly enrolled claims passed. This was the parse phase alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…t, order width before amount

Three defects, each with a counterexample on a40faa2.

(1) Word is an ordinary record and .dag has no module-private construction, so
word_of_int being the 'sanctioned entry' was unfalsifiable -- nothing refused the
other route. word_shift_right(Word{Width8,256},1) answered WordReady 128, and no
arm fired because the OUTPUT was in range: the operations validated their results
and trusted their premises. Every exported boundary now qualifies its Word
arguments against their own declared width before any arithmetic reads them, via
one shared seam, and word_from_octets qualifies every member -- it checked member
width and trusted member value, so [1, 256] packed to 512 at Width16. This is
mitigation and says so; sole construction remains the trigger that DELETES these
checks rather than keeping them.

(2) int_to_octets read every absent capacity as 'fits'. int_pow_bounded answers
Absent both when the power exceeds the Int bound, where every representable value
genuinely is below it, and when the exponent is NEGATIVE, which is not about
capacity at all -- so count = -1 returned OctetsReady [], a successful empty
rendering of a nonsense request. The neighbouring annotation had already named
this exact conflation and the code committed its other half. The count is decided
on its own terms before the capacity is asked.

(3) word_rotate_right asked amount before width, alone among the four shift and
rotate arms, so a Width64 rotate by 64 named the amount when the width is what
cannot exist. Two operations disagreeing about which refusal one malformed call
produces is a fork in the refusal vocabulary.

Controls for all three, each red against the pre-fix code, including one per
exported family so a partially applied premise check cannot pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…octet seam

main's #11660 added std.encoding utf8_decode_octets, which reads each member
through base64_octet_int -- the 'b + 0' identity whose entire content was a width
claim it never checked, and which this branch deleted. The two changes are
correct separately and refuse together: a SEMANTIC conflict, with nothing
overlapping textually, so the merge is clean and the resolve is not.

Fixed here rather than in #11647 because this branch removed the helper and lands
first. The replacement is the seam base64's own octets already use:
base64_octet_word admits through word_of_int at Width8, and Utf8Refused is the
honest destination, which is the state this fold already uses for every other
malformed input -- no new refusal vocabulary for a case the model had a word for.

The discriminator is a NEGATIVE member, not an oversized one: 256 refused on both
paths because utf8_step rejected it anyway, but -1 satisfied  and was
decoded as an ASCII scalar of -1. Three controls, one of them that exact input.

gunbc.plans.blackjack_onboarding taught base64_octet_int in a code sample and in
prose; deleting the symbol is what made that stale, so it is repointed here
rather than left for its owner to discover.

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

Two boundaries the premise seam missed, which is the partial-coverage state my
own commit message named and then left one function short of complete.

(A) octet_int_values mapped o.value over its members with no qualification and no
refusal arm, so [Word{Width8,256}] projected to [256] -- and std.encoding consumed
that projection on the base64 decode production route. A projection is not exempt
from the premise rule: reading a field is where an uninhabitable word stops being
a record and becomes a number a consumer trusts. It now qualifies through the
same octets_first_unqualified fold word_from_octets uses, so width and value come
from one authority rather than two agreeing by accident, and returns a typed
result because a projection that can refuse must be able to say so. Threaded at
the base64 caller, which already had an Optional channel.

(B) word_int_value was unqualified AND had no consumer, witness or frontier row
anywhere. Deleted, the same remedy word_zero and word_equal got: a declaration
with no consumer at all is removed, not described. A caller with a matched
qualified word reads its field directly.

Three controls: an out-of-range member, a non-octet member, and a qualified
positive.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lper repeats

Ruled repair shape. .dag has NO module-private, so every top-level helper is a
callable boundary and there is no wall to put one behind. Qualifying each public
operation and leaving the helpers taking raw Word left four raw entrances, and
one of them was the defect the public wrapper exists to prevent:
word_from_octets_realizable was callable directly with Width64 and would
reconstruct up to 2^64 - 1 in the i64 before word_of_int refused. I had split
that body out claiming the guard could not be bypassed. A name is not a proof.

Re-validating inside every helper is validation multiplied by helper count, and
it fails when helper N+1 forgets -- which is exactly how the projection survived
the previous pass. So the proof moved into the type. QualifiedWord is produced
only by word_qualified / word_pair_qualified; RealizableWidth only by
word_realizable_width, the one function that refuses Width64. Helpers below the
public boundary take those carriers, so reaching them unqualified does not
typecheck. Public ops take Word, qualify once, pass carriers down.

octet_int_values takes List<QualifiedWord> and LOSES its refusal arm: with every
member arriving with its proof attached there is nothing left to refuse, so the
typed result added last round is gone again -- the carrier subsumed it.
word_int_value stays deleted. OctetsReady now carries List<QualifiedWord>, which
is an interface change for consumers of word_to_octets; CRYPTO-0 told.

Honest about the rung: a record with public fields is still constructible, so
this is a sealed wrapper, not a private constructor. It changes the failure mode
from "a helper forgot" to "someone forged the proof". Rung unchanged at
mitigatable; sole construction remains the next-rung trigger.

The wall is unwritable in the accepted corpus by construction, so the controls
are fixture sources compiled through the real acceptance path, in their own
module -- machine_word_witness_test is stamped SubstrateInputsOnly truthfully and
compile_dag_diagnostic_census walks the checkout, so the claims could not live
there without making that stamp a lie. Three REDs, one per former raw entrance,
plus a green control that is load-bearing because three REDs alone are satisfied
by a compiler that stopped judging argument types.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review 68365, and the build lane had already caught its consequence: the
generated-artifact gate reported dag/test/fixture/approval_device_redemption/
vectors.json DRIFTED, which is this defect rendered into bytes.

path_segment joined base64_encode(...) directly into the segment. This PR changed
that return to String?, and this call site arrived on main from gunbc#11660 while
this PR was in review, so the two are correct separately and wrong together --
nothing overlaps textually, the merge was clean, and an Optional was being joined
into a route identity. Every other caller in the tree was migrated; this one did
not exist when I migrated them.

The arm is unreachable here: the octets are the UTF-8 encoding of a String, so
every member is a byte by construction. The declared contract is TOTALITY -- the
annotation above the function says the segment is total, injective and never
refused -- so threading an Optional out would be a weaker contract at a
route-identity boundary rather than more honesty, and would ripple into
decode_path_segment. std.bytes divergent seam is the declared idiom for an arm
that cannot be reached; same treatment and same reason as
encode_sm_access_version_payload_wire earlier in this PR.

The vectors drift is expected to clear without regeneration: the committed file
was generated when path_segment was correct, and this restores that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ledger-Repair-Judged: docs/design-rung-drops.md
…ced a regen

CI on 8d81d86 was structural, not infra: the carrier refactor changed
word_to_octets to answer List<QualifiedWord> while word_from_octets takes
List<Word>, and octet_packing_round_trips_holds fed one into the other --
"value does not inhabit its declared type at the generic type argument:
declared Product(Word), produced Product(std.machine_word.QualifiedWord)".

That round trip is an ordinary consumer pattern, not a witness artefact: SHA-2
block packing is exactly to_octets into from_octets. So the unwrap is exported as
qualified_words_to_words rather than open-coded at the call site. It discards a
proof, which is safe in this direction only -- word_from_octets re-qualifies every
member, so the cost is paying the check twice, never skipping it. The asymmetry
is deliberate and stays: a caller assembling octets by hand has raw Words, and
making the entry demand carriers would push the qualification back onto callers.

Separately, the plans-doc edit is REVERTED. It repointed prose that taught
base64_octet_int, which this PR deletes, and doing so drifted the generated
docs/plans/blackjack-onboarding.md. A remote regen of the generated-artifact gate
produced no change to that projection, so I cannot verify the repair, and I will
not carry an unverified edit to a generated artifact to close a drift I created.
The file is now byte-identical to main and the drift clears. The staleness is
real and is reported on the PR rather than silently dropped -- and the right
replacement is a judgment for that doc owner, since the idiomatic form under the
new model is not a one-for-one symbol swap.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
I reverted this edit one commit ago because it drifted the generated
docs/plans/blackjack-onboarding.md and my remote regen of the generated-artifact
gate produced no change, so I could not verify the repair. That reasoning was
right and its premise was wrong: the heal-generated-artifacts job had already
regenerated the projection from this very edit and pushed it as 97888f6,
which is why my push was rejected as non-fast-forward.

So the projection exists, it was produced by the authority rather than by hand,
and reverting the .dag would have re-drifted the healed .md in the opposite
direction. Merged the heal and restored the edit; authority and projection now
agree, and neither was hand-written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ns real authority

Review 68385. The green control read blocking_count_for(..., wanted:
"TypeMismatch") == 0 -- the absence of ONE diagnostic class, which a source that
never reached the type judgment satisfies permanently. A parse refusal or an
unresolved import leaves it green. That is the decoration DESIGN 4b names: green
by construction, carrying no information, and worse than absent because it would
be cited as the control that makes the three REDs mean anything.

And the defect was live, not theoretical: the probe sources imported BigEndian
from std.machine_word, which does not DECLARE it -- it imports it from
extdeps.toolchain.architecture_profile. The probes cited the wrong authority, so
the green could have been green for the wrong reason, and two of the four never
needed the name at all.

Fixed both. The green now asserts ZERO blocking rows of ANY class, which is what
"the compiler accepted this" means. The REDs gain a paired total-count assertion,
because a class-scoped count alone does not establish that a RED is red for the
right reason -- a source that fails to parse reports no TypeMismatch and would
read as no defect.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ot just refusal

Review 68407: the onboarding plan still told a newcomer to build boundary-test
octets with std.encoding base64_octet_of_int, which this PR deletes. I fixed the
SIBLING citation in the same file last round and missed this one, because I
searched for the symbol I had just been thinking about instead of for the set of
symbols this PR removes. Swept properly this time: base64_octet_int,
base64_sextet_char, word_zero, word_equal, word_int_value, OctetValuesResult and
octets_first_unqualified have no live references left, only annotations
describing their removal.

The replacement is not a symbol swap. There is no crossing constructor any more
-- base64_decode already answers List<UInt8> -- so the doc now says to write that
list directly, which is what its own adjacent sentence ("take the real decoded
type here, do not mint an Int list") was already telling the reader. Only the
.dag authority is edited; the projection is regenerated by heal-generated-
artifacts, as it was for the sibling fix, rather than hand-written.

Also in this commit, found by auditing my own claims rather than by a sixth
review: the premise-family control asserted only THAT each operation refused, via
a helper true for any cause. Which refusal fires is that claim entire subject --
a width mismatch standing in for an out-of-range value would have passed. The
range, shift-bound and premise claims now name their cause, and the any-cause
helper had no consumer left afterwards so it is deleted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All four typecheck claims failed on d2e1d9c, and the way they failed is the
diagnosis: the REDs assert > 0 and the green asserts == 0, so only one value
fails both -- the 0 - 1 CensusNotRunnable sentinel. The not-runnable-is-not-zero
design did its job, failing loudly in both directions instead of letting the
green pass while the harness was dead.

CensusNotRunnable is what compile_dag_diagnostic_census returns when the compile
panics inside its catch_unwind. The probe sources wrote record literals as
Word \{ width: ... \}, escaped against .dag string interpolation -- but the
corpus precedent (type_argument_arity_witness_test, algebra_receiver_alias_
witness_test) writes braces UNESCAPED inside probe sources, and { width: Width8 }
is not the bare {Name} interpolation form anyway. The escape put a literal
backslash into the source handed to the compiler.

This is a hypothesis with a precedent, not a confirmed cause: the green source
contains no record literal and failed too, which this does not explain. If the
next run still reports NotRunnable, the cause is module-level rather than in the
sources, and I will withdraw the module and carry the typecheck wall as a
declared obligation rather than ship four claims that cannot run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ledger-Repair-Judged: docs/design-rung-drops.md
…tnessed

I said in b8abbf7 that if the module still reported not-runnable the cause was
not in my probe sources and I would withdraw it rather than ship claims that
cannot run. It still fails, so I am doing that.

The escaping fix did not resolve it and the failure is not diagnosable from the
floor log: all four claims fail at once, the REDs failing > 0 and the green
failing == 0 simultaneously, which no single count explains. The reach probe
reports opaque_host_call_unbounded:compile_dag_diagnostic_census with
last_builtin=census_total_count and ~2.3s shared fill per claim. Every further
hypothesis costs a ~45 minute cycle.

Shipping them failing is not an option and weakening them until they pass is
worse. What is left is to say plainly, on the carrier whose property they were
meant to establish, that the typecheck wall is ASSERTED AND NOT EXECUTED, with a
capability-grained trigger: a census probe that can report the NotRunnable cause,
sufficient to distinguish a dead harness from a source that compiled clean.

Recorded there rather than only here because review 68441 APPROVED this PR citing
that module as doing "the thing most PRs skip" while it had never executed. An
approval reading source is not a run, and the annotation now says so at the point
a future reader would otherwise trust the wall.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ssertion authData carries no credential data

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… longer reads the next element (review 68531)

std.octet_span gains octet_in_span; every read inside an element's content goes through it.
der_bit_string refuses empty content; x509_key_usage_permits_cert_sign no longer grants
keyCertSign from the byte after an empty KeyUsage. Regression claims RED with the old reads.

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

gunbai-bot Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor Author

Addressed review 68531 in f10406a. Both findings were real, and they had one root cause, as the review said: std.octet_span octet_at is bounded by the input but not by the field being read.

  • Root fix: std.octet_span gains octet_in_span(input, span, at), which returns absent for any offset outside the span. Every read inside an element's content now uses it: DER INTEGER/BOOLEAN/small-integer/BIT STRING, X.509 time and KeyUsage, App Attest flags. Framing reads (a tag or length at a position) stay on octet_at.
  • der_bit_string: empty content now refuses with DerBitStringMalformed, as the module header promised (X.690 8.6.2.3).
  • x509_key_usage_permits_cert_sign: an empty KeyUsage (03 01 00) no longer grants keyCertSign from the following byte. The failure arm now refuses rather than widening.

Evidence, from local claim_batch runs:

  • der_x690_witness_test is 19/19, including two new claims built from the review's exact inputs: 03 00 05 must refuse, and 03 01 00 04 must not grant keyCertSign, with 03 02 01 04 as the positive control.
  • app_attest_parse_witness_test is still 19/19.
  • Mutation: with the old unbounded reads restored, both new claims go RED. The fix was restored and the tree verified clean.

— sent from warm-tern-701

@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

Scoped claim receipt at head f10406a17bccc1c2a93c088dfb4b56f452192a64

Per the interim gate pinned on #11626 (CI is build-only since #11742, so a green check executes no claims).

Dispatch (one remote run, both memory variables confirmed forwarded — ctrl-build: forwarding env: GUNBC_MEMORY_BUDGET_BYTES GUNBC_BIND_MEMORY_CGROUP_BYTES):

CTRL_BUILD_RUNNER_EXEC_PROPERTIES=EstimatedMemory=16GB \
GUNBC_MEMORY_BUDGET_BYTES=12884901888 GUNBC_BIND_MEMORY_CGROUP_BYTES=12884901888 \
CTRL_BUILD_FORWARD_ENV="GUNBC_MEMORY_BUDGET_BYTES GUNBC_BIND_MEMORY_CGROUP_BYTES" \
ctrl-build --remote -- bash -lc 'cargo build --release -p v1-compiler --bin claim_batch &&
  for p in <the 8 modules below>; do
    ./target/release/claim_batch --claim-run --hermetic --source-root dag --source-root src/v2 \
      --entry $p --functions "<every test fn in $p>"
  done'

Scope. (a) every witness module the diff touches, and (b) every witness module that imports a module the diff changes. The diff changes std.octet_span, extdeps.ietf.cbor, extdeps.itu.der, extdeps.ietf.x509, extdeps.time.posix_epoch, extdeps.time.rfc3339, extdeps.apple.app_attest and gunbc.auth.approval_device_redemption; their direct witness importers are rows 4-8.

module claims result
cbor_rfc8949_witness_test 18 all PASS, rc=0
der_x690_witness_test 19 all PASS, rc=0
app_attest_parse_witness_test 19 all PASS, rc=0
approval_device_redemption_witness_test 32 all PASS, rc=0
approval_device_wire_witness_test 25 all PASS, rc=0
public_workload_census_witness_test 30 all PASS, rc=0
runner_label_resolution_witness_test 22 all PASS, rc=0
served_surface_browser_observation_witness_test 5 all PASS, rc=0
total 170 0 failed

Discriminating REDs (same dispatch shape; each defect restored one at a time, then reverted):

defect restored module claims that go RED
CBOR duplicate-key detection removed (cbor_key_seen -> false) app_attest_parse_witness_test a_duplicate_top_level_key_is_refused, the_enrolment_route_refuses_a_duplicate_key_through_the_modeled_decoder
DER exact-extent check removed (== -> <= in der_read_exact) app_attest_parse_witness_test a_certificate_with_trailing_bytes_is_refused
pre-fix unbounded reads restored (octet_in_span -> octet_at, review 68531) der_x690_witness_test an_empty_bit_string_is_refused_and_never_reads_its_neighbour, an_empty_key_usage_does_not_grant_cert_sign_from_the_next_byte

Each ran rc=1 with exactly those claims red and the rest of the module green; every mutant was reverted in the same dispatch and the tree verified clean afterwards. The second row is the route-level bypass discriminator: a hand-written offset walk over the same bytes reads the same fields and passes every happy-path claim, but cannot refuse a duplicate key.

The honest gap. A per-module run binds bare names by module scope, where the whole-corpus floor binds by corpus scope. This receipt therefore does not exclude a corpus-scope name collision. It also executes only the modules listed above: the transitive importer closure of my changed modules is 259 witness modules (extdeps.time.rfc3339 is widely imported), and I read the rule as direct importers. Say so if you meant the transitive set and I will run it.

— sent from warm-tern-701

@gunbai-bot

gunbai-bot Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #11895, which carries this change merged onto current main together with the program's other open PRs (operator ruling 2026-09-20, wind-down consolidation). — sent from fierce-seal-607

@gunbai-bot gunbai-bot Bot closed this Sep 20, 2026
gunbai-bot Bot pushed a commit that referenced this pull request Sep 21, 2026
…ned_be admits through uint8_octets_of_ints, base64 callers go through base64_octets (jws too)

The whole-corpus floor ran 494 claims and 34 failed with 'cannot access field members on List' -- #11727 was written against the pre-sealed word_from_octets(List<Word>) and base64_encode(List) shapes, and #11677's jws had the same raw-List call on main. Each now admits its octets through the one mint and refuses typed on the Refused arm.

Co-Authored-By: Claude Opus 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.

0 participants