From 9857b2c27480942e44f22fd9488ea25426923c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Z=20Sink=C3=B3?= Date: Sun, 9 Aug 2026 20:06:27 +0200 Subject: [PATCH] docs: the twelve questions the specification does not answer, and code does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Everything closed so far has been a defect: a place where the document says something wrong, or where an implementation does not do what it says. What is left is a different kind, and lumping the two together would hide it. These are places where the specification says NOTHING, something had to happen, and whatever the first implementation did became the answer. Nobody chose it. It is not written down. It is load-bearing. In at least three of the twelve, the two implementations answered differently and no vector could see it. The list, with what was measured for each: D-1 scalar_type is never read during validation, in either implementation D-2 an absent optional position: Go removes it, Rust materializes null D-3 an instance-authored primitive replaces the declaration, in one vector, with no rule stated D-4 an injected `inherit` takes the ENCLOSING origin, so a value the YAML did not contain is recorded as yaml-authored D-5 `access` operations are checked; nothing inside them is D-6 a child named `a.b` has no address that resolves, so INV-040 is false for it D-7 YAML dialect, tags and !!binary are whatever each library does D-8 stage names and error codes exist only in the fixtures; §8 lists none D-9 INV-032 is stated as a type-system property Go cannot satisfy D-10 the mapping gate matches INV-nnn tokens and never reads MUST, so an unnumbered normative clause cannot enter coverage D-11 no budget of any kind at any entry point D-12 `make release` does not compute or verify the INV-045 subject Each entry states the question, what happens today, whether that is a decision or an accident, the options with what each costs, and what it blocks. None is fixed by writing more code — they are settled by choosing, and then by a vector that holds both implementations to the choice. Two of them belong together and the document says so: D-5 (`access` has described value domains and no enforced grammar) is the same shape of question as the first audit's F-08 (`contract` is required to be enforced and nothing evaluates it). Deciding them separately will produce two different answers to one question. Also corrected here: README called check_spec_vectors.py "negative-tested". Nothing under tests/ references it. It now says what the tool checks. --- [signing-metadata] key = cic-my-sign-key signature = vault:v1:MEQCIFFTSjhQDodXUF5N5Fwssk2oJvPTim8QWhZh/MVikZUmAiA9L3shLNiez2rtcEKUp65MO3R0JgjPEOAd3q+JwOpPwg== hash-algorithm = sha256 digest = HEE1Hq+VkiUU/KPY6u9/j+Mbrv1SR8FnxfN7a5f4C2Y= [certificate] -----BEGIN CERTIFICATE----- MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0 lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW -----END CERTIFICATE----- --- [signing-metadata] key = cic-my-sign-key signature = vault:v1:MEYCIQCu9hVs2pzfBy+js6yZCYFxZ1d4LsvXu1cySI1FZ/JT3gIhAP+WuweO+ZUVpfImhS9D3VbBRltT9AzrGEaNHyUioLnc hash-algorithm = sha256 digest = hgWDRf8INVip96fp/NWhgCw2MNTZB0Ox1WVPor4Suhw= [certificate] -----BEGIN CERTIFICATE----- MIICBjCCAaygAwIBAgIUSnRMR6RPnEbg296XWPOqq/u5PCwwCgYIKoZIzj0EAwIw QzELMAkGA1UEBhMCSFUxGTAXBgNVBAoMEENlbnRyYWxJbmZyYUNvcmUxGTAXBgNV BAMMEENJQyBEZXZlbG9wZXIgQ0EwHhcNMjYwMzIwMTMyMjU5WhcNMjYxMjMxMTMy MjU5WjBFMQswCQYDVQQGEwJIVTEZMBcGA1UECgwQQ2VudHJhbEluZnJhQ29yZTEb MBkGA1UEAwwSR2Fib3IgWm9sdGFuIFNpbmtvMFkwEwYHKoZIzj0CAQYIKoZIzj0D AQcDQgAEIG2CVmTfmLB9pLLclj7YmP2eedAjklpy4LGrU2ijoiy6Xqpuybv7OgJe i+ez31s65NEV8+X/ByeX1cstR988z6N8MHowCQYDVR0TBAIwADAdBgNVHQ4EFgQU yZN6AIX/TNnIJ9GwAa/NRN3ujHAwHwYDVR0jBBgwFoAUXn6CHYzPUqU4JVP8g+OS WeDYjhcwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEF BQcDBDAKBggqhkjOPQQDAgNIADBFAiEA+bFzXRoJ4PCQbhAAtpkcMjt0vNj5rEW0 lOMBGDNyaWkCIB1vmM7PcZzv/c9bIrxF5kqv6QXomouhByUfeNUTbpKW -----END CERTIFICATE----- --- MANIFEST.sha256 | 7 +- README.md | 3 +- SPEC.md | 4 + docs/pending-decisions.md | 279 ++++++++++++++++++++++++++++++++++++++ project.yaml | 2 +- 5 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 docs/pending-decisions.md diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 index 6f8ed6b..9ebbc88 100644 --- a/MANIFEST.sha256 +++ b/MANIFEST.sha256 @@ -45,6 +45,7 @@ 1fb7dd2afe2b230d7c335db5c5da4e72e0ad271ce2dcd1683f446945e0abb917 conformance/materialization/011_discriminator_envelope/input.yaml 2020cd3da7008987b81055f0ef1bc7481567202da7f8bf49d616b1018f52bc21 mk/infra.mk 2063bf8ac333858a9b5c96de552fc4879ca3c35e5d0e34b0dd4067cec94bb5da Dockerfile +214885986c75f2a6928c9e2a8326804353310ade3834815c5d64f3e74634ddbf project.yaml 21a2f539315cb8500c9d812776d0e777b2eca73d668b3079b694b651fe8539d8 conformance/materialization/012_discriminator_payload_keywords/input.yaml 21a9994fa283ffca9aa0d063526ba4bc8b7d53d781bfbbee0d0041dcc04d143a conformance/invalid/005_origin_in_authoring_input/schema.yaml 21a9994fa283ffca9aa0d063526ba4bc8b7d53d781bfbbee0d0041dcc04d143a conformance/invalid/006_unknown_primitive/schema.yaml @@ -52,7 +53,6 @@ 225e266950c6f85bfd2be20cacb3de0ac011414f92010b831136640ffe9a46be conformance/invalid/007_sealed_missing_path/expected-error.yaml 229376bac93d6b5d81c8c3015b4981999cf3a3c82bf8a8e1d63eecaf61d1e35d conformance/validation/007_origin_duplicate_term/expected-error.yaml 24219339b219898ecb69d09495bc078745cef2c1b5065eedfff886dd95032881 conformance/invalid/010_required_value_missing/expected-error.yaml -25330918bb1de80bf3db79532e39dd87e2752df46e402434c0044de476046f86 README.md 2758d20cf5aa0688060bce28a52786eee272986714a8376d7f190eb2dc90a43e reviews/README.md 275acc4de6f4d5fcd87330039777ceb8f8d8a7933c4e4924eccabe21a9f28da2 conformance/materialization/002_origin_schema/meta.yaml 27fc384a0d7987e4cb311648408b9b5a3995c0b58871ed55389ae154061209c9 conformance/validation/011_more_than_one_document/object.yaml @@ -155,6 +155,7 @@ 72c48fa014dea46236640cf52037631aa5db63a6f58807b3bf24577a247aa238 tools/check_doc_links.yaml 72e9b7c69632c1e1877e9f9d69812f3259003989b69f6b0fa7f23612d4514c94 tools/releaselib/git_service.yaml 7426752feb1140b2023192728df35248e4f802617c150bb0523ab97dc1638c0b tools/init-hooks.sh +753f2eb2a6b70c2e35e76b61baa67b9f9533f8f86713da7b9ff62ab44def6ccb docs/pending-decisions.md 75b59a6cf6a2ce8af4fd04c8a0dcf7d33e5e5f132a48a981252ce61c05cf97c9 docs/hu/makefile-cheatsheet.md 75d086d878abe5943b2dd0c080c0ffb4fd88b517289c216ffa6bc5c1f12056f7 conformance/invalid/003_closure_undeclared/input.yaml 764d101d6bb896af8c4f082768f30f9f7cf77a6f0a0fed940b7b39805330d842 requirements.txt @@ -211,6 +212,7 @@ 94dabea4d0bdb7a81c750c4b2830f096c9ee2e59952fd1b9f7d4e235cddeb2f9 mk/golang.mk 96a0612d7f3d7d3faf96bbc408da0b32979eaee2599962e8d6d9e97c15b38d40 conformance/materialization/015_nested_sealed_template/schema.yaml 992423396d17250586acefe1ca589c21d650fb75c7f578bdff9cbb42fa1351fa conformance/invalid/012_scalar_position_sequence_payload/expected-error.yaml +998c8f3ef914d9447a6f565be986c0c8d3459b55b6a131290609d912b709e542 SPEC.md 9c11fd643268e578437b015dc5a4ea6e5a3f290f0303ac04ea4d429768208617 tools/schemalib/__init__.py 9c2c853fb20be48e4ebbfdf70a98e6890df116fd77fd85a6be2b010674afcaab go/conformance/conformance_test.go 9d0d594b86072a88bbda400992cb62d0b4faf71176a76c8f30c2e632c8eb5fb1 conformance/validation/011_more_than_one_document/meta.yaml @@ -238,6 +240,7 @@ aad20e1630b166e5ca74baad558d0f47cc9be1d6b6aea22c23deebeaa75447fc conformance/ma ac2343c803409938f2414e5833bab1ae33061506fe2678307121837036e3ada2 conformance/invalid/003_closure_undeclared/schema.yaml af1074d4241e3ac01c7b4197ffcef9e270dbde4c4ddccd7b1d6c0aab4a75cbb0 docs/hu/architecture.md af6cd3c7db5089246c1c7533fb2763351d03dab7c26424a8827e347fdaee618e tools/schemalib/artifact.yaml +b1f4c81b89b74764e424b6f963615944c64b5c11d3e61ef8027334836a51b18d README.md b24ec80c06a16f685f69c69a2b2d266b823e45d0153868b10967df484d9eafee conformance/invalid/010_required_value_missing/meta.yaml b35767f17012df3e8334e7414d1d535c60a3a6e83c7a3038e6c8ea4915c48bdb conformance/validation/002_origin_empty/object.yaml b3d0d0dd71c294b5fa8bad4fcdd9784feae35141aeb71e1755abd8bbd0cfca58 pyproject.toml @@ -273,7 +276,6 @@ c874c3372e425b2255c6eb1771e13f77fe6025923d06ced7b5a91259e28e1d22 go/objectmodel c8daecc8e4b81241d10c642c6c051712022bb8950a7855fd31d7805534de9586 conformance/materialization/006_closure_opaque/expected.yaml cadf923c73fdea6b9e1478f822f30d3f4d6c7463b15a12df709f0ae5d56743a2 conformance/validation/007_origin_duplicate_term/meta.yaml cc2109e262ef7d2c0ae924af62ae6b794934a37c9be09e35f1a175b22faa2483 go/objectmodel/construct.go -cc22d2735173aaf85a9944852f4522df726185bd7f8b023085fd7d0c5d7fe6d4 project.yaml cc94b0652d20afaf3b85ee72e50913f0277b6441642309670818355847f1e721 spec/node.schema.yaml ccc12224c9262ebed8b7bedb3046e9a359fa817e9c08659765d62d63355d0e3a conformance/materialization/014_hostile_keys/input.yaml cdc268dd275143f3cbad6b84078f397367c40951812143d227b5490cd007ce82 tools/infra.py @@ -334,7 +336,6 @@ fa3d02b5fc0677eb715d676e88b98b94016a76eb13e47abfa1040d0e6f12719f docs/rust-gate fa8afcf490e0b9b1e3ce94789f3233ed73289553ec94f16d2c347afd5e08b367 conformance/invalid/002_sealed_yaml_schema_conflict/input.yaml fab14c32130c2345945fa55f41e6614bc443815733249fce81ed7d97e1d1f4cf rust/src/value.rs fd4ba258680da20f4084db2696fa942c09e47de8ec4650104f2b2e18c53980bc go/module/adversarial_test.go -fdd8b8d94d94d9c08aafdc2b0e6f85db5deba627541786a8d72ef99c3eb37049 SPEC.md ff005c27b6c185b065c2121b01fa6f71c9668af920483714c9b137e411cec32c go/objectmodel/emit_test.go ff5d544dc1e253e76844a7c325f7f73d952ac29b284092f0ed2e80b8de3ae0a2 go/objectmodel/materialize.go ffa488df0e6ea07872d7c2b8a7ed0b692e4a998711991e4471cfc134fcbaf0b6 go/objectmodel/api_test.go diff --git a/README.md b/README.md index 81b3f7e..2232f97 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ written into SPEC.md and propagated nowhere else. | `SPEC.md` — 46 numbered invariants | written, normative | | `conformance/` — 38 vectors | executed against Go on every CI run | | `docs/spec-vector-map.md` | 37 invariants vector-covered, 5 declared unvectorizable with a reason each | -| `tools/check_spec_vectors.py` | runs and passes; negative-tested | +| `tools/check_spec_vectors.py` | runs and passes; checks the invariant index, not RFC-2119 clauses | | `go/` | implemented — corpus, fuzz, mutation, adversarial and CLI golden tests | | `rust/` | implemented — corpus, reader, rejection and CLI golden tests | | `mk/rust.mk` | present; digest-pinned toolchain, `make rust.quality` | @@ -74,6 +74,7 @@ conformance/ the falsifiable part — YAML in, YAML out validation/ canonical object -> accept / reject docs/ spec-vector-map.md every invariant -> its vectors, or why it has none + pending-decisions.md questions the spec does not answer and code does decision-delta.md what this model changes in D-003 / D-011 migration-surface.md the measured file list this model would change branch-decision.md why base-repo wasm/main diff --git a/SPEC.md b/SPEC.md index 5f94bc3..0bf835d 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1205,6 +1205,10 @@ The procedure, and the three commissioning prompts a request carries, are in ## 13. Related documents +- [`docs/pending-decisions.md`](docs/pending-decisions.md) — questions this + document does not answer, which an implementation has answered anyway. Twelve + of them, three answered differently in the two implementations. Read this + before adding a rule: several of these are load-bearing and unwritten. - [`docs/spec-defects.md`](docs/spec-defects.md) — where this document is not executable, measured by implementing it. **Read SD-017 before relying on INV-033.** diff --git a/docs/pending-decisions.md b/docs/pending-decisions.md new file mode 100644 index 0000000..453b7c6 --- /dev/null +++ b/docs/pending-decisions.md @@ -0,0 +1,279 @@ +# Pending decisions + +Questions this specification does not answer, where an implementation has +answered them anyway. + +That is the distinction worth holding onto. A defect is a place where the +document says something wrong; these are places where it says **nothing**, and +something had to happen, so whatever the first implementation did became the +answer. Nobody chose it. It is not written down. It is load-bearing. + +`docs/spec-defects.md` records the first kind. This file records the second. + +Each entry states the question, what happens today and whether that is a +decision or an accident, the options with what each costs, and what it blocks. +None of them is fixed by writing more code: they are settled by choosing, and +then by a vector that holds both implementations to the choice. + +Sources: the three external review threads against `cbaf928`, recorded in +[`../reviews/`](../reviews/). Findings closed since are in the commit history; +what remains is here. + +--- + +## D-1 — Does `scalar_type` constrain a value, or describe it? + +**Measured:** neither implementation reads `scalar_type` during validation. It +is parsed, carried, and emitted into the `shape` primitive, and nothing compares +it to the payload. `scalar_type: integer` with a string value materializes and +validates. + +**Accident.** The arity check added after the audit refuses a *collection* at a +scalar position; it deliberately did not touch subtype, because answering that +question in an emitter is how a specification acquires rules nobody wrote. + +| option | cost | +|---|---| +| **Constraining** — the payload must match | needs a type lattice (what is `integer` for a YAML `1.0`? for `0x10`?), coercion rules or their explicit absence, and a vector per type | +| **Descriptive** — an annotation the model carries and does not check | cheap and honest, but then `shape.scalar_type` in a canonical object is a claim no one verified, and a module reading it is trusting the author | + +**Blocks:** any consumer that treats a canonical object as typed data. +**Related:** audit semantic F-04, claim F-11. + +--- + +## D-2 — What happens to a declared position that is absent, optional, and has no default? + +**Measured:** Go removes the node. Rust materializes a scalar as `null`, a list +as empty, and walks object children. `docs/spec-defects.md` SD-007 already +records three readings — omit, null, reject — and calls none of them decided. + +**Accident, and a divergence the corpus cannot see:** no vector has an absent +optional position. + +| option | cost | +|---|---| +| **Omit** — the node does not exist | matches Go; a reader cannot distinguish "not configured" from "not declared" without the schema | +| **Materialize as null** | matches Rust; every optional position becomes a node with a value nobody wrote, and `origin` has no term for that | +| **Reject** — every declared position needs a value or a default | strictest and simplest to state; makes optionality a schema error rather than a runtime state | + +**Blocks:** cross-implementation agreement outside the corpus. +**Related:** audit semantic F-05, claim F-03. + +--- + +## D-3 — May instance input author a primitive the schema declares, and if so, does it replace or merge? + +**Measured:** vector `011_discriminator_envelope` supplies an `access` primitive +from the instance and it REPLACES the declaration. Nothing states whether that +is allowed in general, and nothing states what happens when both the schema and +the instance declare parts of the same primitive. + +**Accident.** One vector fixes one case; the rule is unwritten. + +| option | cost | +|---|---| +| **Forbidden** — primitives come from the schema only | INV-036 nearly says this already; would invalidate vector 011 as written | +| **Replacement** — the instance value wins whole | what happens today for the one case that exists; simple, and loses schema-declared members silently | +| **Merge** — leaf-wise override | most useful, most to specify: merge order, list handling, and what `origin` says about a merged node | + +**Blocks:** D-4, because a merged primitive's origin is undefined. +**Related:** audit semantic F-01. + +--- + +## D-4 — What origin does an injected primitive-internal default carry? + +**Measured:** `inherit` is injected into every access operation that does not +declare it, and it takes the origin of the *enclosing* primitive — `[yaml]` in +vector 011, `[schema]` in 013. So a value the YAML did not contain is recorded +as yaml-authored. + +**Accident, and it makes an origin false.** `origin` is a claim about who +authored a value; the author did not write this one. + +| option | cost | +|---|---| +| **Enclosing origin** — today's behaviour | keeps the object simple; the provenance of an injected default is wrong, which is the one thing origin exists to be right about | +| **Always `[schema]`** | truthful — the model supplied it — and makes a node's origin differ from its parent's inside one primitive, which nothing else does | +| **Do not inject** — absence means the default | nothing to attribute; a policy decision point must then know the default, which §6.4 says it should not have to | + +**Related:** audit semantic F-02. + +--- + +## D-5 — Is `access` a grammar or a shape? + +**Measured:** the operations are checked (`read`, `modify`, and `write` is +refused). Nothing else is: `inherit` may be any value, an operation block may +carry any member, and `rules` may be anything at all. + +**Accident.** INV-024 through INV-026 describe value domains in prose that no +code reads. + +| option | cost | +|---|---| +| **Enforce** — closed member set, typed values | the primitive becomes a real contract; needs a grammar in §6.4 and a vector family | +| **Leave open** — `access` is a shape the model carries | honest, cheap, and means a policy decision point cannot rely on anything about its content | + +**Related:** audit semantic F-03. +**Note:** this is the same shape of question as **F-08 of the first audit** — +the `contract` primitive, which §8.7 requires to be enforced and which nothing +evaluates. Both are "a primitive the specification describes and no code +checks". They should be decided together or the answer will not be consistent. + +--- + +## D-6 — What characters may a node name contain? + +**Measured:** a child named `a.b` materializes. `Path()` returns +`$.values.a.b`, and `Get` splits it into two segments, so the address does not +round-trip. Key quoting was fixed for serialization; addressing was not, and the +round-trip test in `addressing_test.go` passes only because no vector uses such +a name. + +**Accident, and INV-040 is false for it.** Every node has exactly one address — +except these, which have none that resolves. + +| option | cost | +|---|---| +| **Restrict names** — a declared character set | simplest; a schema with `a.b` becomes a schema-load error; opaque payload keys stay unrestricted and unaddressable, which needs saying | +| **Escape in the address grammar** | keeps names free; every address becomes harder to read and both resolvers need the escape | +| **Length-prefixed or quoted segments** | unambiguous, and no longer looks like a path | + +**Blocks:** INV-040 being true as stated. +**Related:** audit claim F-04, semantic F-09. + +--- + +## D-7 — Which YAML is the input dialect? + +**Measured:** Go uses `gopkg.in/yaml.v3`, Rust uses `saphyr`. Anchors, aliases, +duplicate keys and non-string keys are now refused explicitly, in both, because +each was found to diverge. Tags, `!!binary`, and version directives are not +mentioned anywhere and are handled by whatever each library does. + +**Accident.** Three divergences in this area have been found by review; the +remainder is untested rather than agreed. + +| option | cost | +|---|---| +| **Name a version and a tag policy** — e.g. YAML 1.2 core schema, no tags | states the contract; both implementations need checking against it | +| **Define an input profile** — an explicit subset the model reads | strongest, and the most work: every construct outside it must be refused by name | + +**Related:** audit semantic F-11. + +--- + +## D-8 — Are the pipeline stage names and error codes normative? + +**Measured:** every rejection carries a stage and a code, the vectors assert +them, and **§8 does not list them**. `schema-load` is not a stage the +specification names at all (`docs/spec-defects.md` SD-002). The corpus is the +only place the vocabulary exists. + +**Accident.** A second implementation must match strings it can only learn by +reading fixtures. + +| option | cost | +|---|---| +| **Make them normative** — §8 lists stages, an appendix lists codes | a table, and then the vectors check the specification rather than defining it | +| **Declare them non-normative** — diagnostics, not contract | then the vectors must stop asserting them, and a caller cannot branch on a code | + +**Related:** audit semantic F-13. + +--- + +## D-9 — What does INV-032 require of a language that cannot express it? + +**Measured:** Go cannot satisfy the construction guarantee — an external struct +embedding the interface satisfies it, which the repository documents in SD-019 +and defends against at runtime. Rust satisfies it structurally. The invariant is +stated as a property of the type system, so one shipped implementation does not +meet the normative text. + +**A defect the repository already admits, and a decision it has not made.** + +| option | cost | +|---|---| +| **Restate as boundary behaviour** — validate, bind representations, snapshot once | achievable in both; loses the "unforgeable by construction" claim that made INV-032 interesting | +| **Keep the strong form and mark Go non-conformant on it** | honest; means the reference implementation fails a normative requirement, in writing | +| **Two levels** — a required boundary contract, plus a stronger construction property where the language allows | more text, and describes what is actually true | + +**Related:** audit claim F-05, semantic F-14; SD-019. + +--- + +## D-10 — What does the corpus mapping gate actually promise? + +**Measured:** `check_spec_vectors.py` builds its universe by matching `INV-\d+` +tokens after the §12 heading. It does not read RFC 2119 keywords — zero +occurrences of `MUST` in the tool — so an unnumbered `MUST` cannot enter +coverage at all, and §8's stage requirements are full of them. It also accepts +any non-empty justification cell without checking that the alternate check +exists. No test exercises it. + +**Accident.** The README calls it "negative-tested"; nothing under `tests/` +references it. + +| option | cost | +|---|---| +| **Require every RFC-2119 clause to belong to an invariant** | makes the gate's promise true; a large editing pass over §8 | +| **Parse clauses directly and track them** | no editing pass, a harder tool, and a coverage number that finally means what it says | +| **Narrow the claim** — the gate checks the invariant index and says so | free, and leaves unnumbered MUSTs untested by design rather than by accident | + +**Related:** audit claim F-06. + +--- + +## D-11 — What are the resource budgets? + +**Measured:** none, at any public entry point. No limit on input bytes, node +count, nesting depth, output bytes, time or memory. The only bound is template +expansion depth (64, now in both). Two amplification findings have been closed — +alias expansion and a quadratic scan — and both were found by review rather than +by a budget refusing them. + +**Accident.** The adversarial review marked recursion depth, memory and output +"inconclusive, not defended" because it could not execute; the absence of any +budget is not inconclusive. + +| option | cost | +|---|---| +| **Per-entry-point budgets** — bytes in, nodes, depth, bytes out | bounded work, and every limit becomes a number someone has to defend | +| **Depth only** — the recursive walks are the crash risk | cheapest real improvement; says nothing about a wide-and-shallow input | +| **Document the absence** | free, and moves the risk to every caller | + +**Related:** audit adversarial, resource section. + +--- + +## D-12 — Does `make release` implement INV-045? + +**Measured:** it does not. The release path hashes one canonical source file, +clears `buildHash`, expects a build artifact this repository does not produce, +and signs a five-field metadata subset. `release_subject.py` — which computes +the INV-045 subject — is not called by it. The descriptor schema was corrected +to accept `repo_type: spec`, but nothing runs that validation either. + +**A gap, not an accident:** the release machinery is inherited from a template +for a different kind of repository. + +| option | cost | +|---|---| +| **Replace the path** — one command that freezes, computes, verifies, review-binds and signs the subject | the honest fix; discards inherited machinery that does not fit | +| **Wrap it** — keep the Vault chain, feed it the subject digest | less to write, keeps a state machine nobody here needs | + +**Blocks:** producing a release that demonstrably satisfies INV-045. +**Related:** audit claim F-02. + +--- + +## How these get decided + +Not by me and not by an implementation. Each one is settled by an entry in +`SPEC.md`, a vector that holds both implementations to it, and a line here +saying which way it went and why the alternatives were refused. + +Until then, every one of them is answered — by accident, in code, differently in +two places in at least three of the cases above. diff --git a/project.yaml b/project.yaml index f8811db..b11451e 100644 --- a/project.yaml +++ b/project.yaml @@ -49,7 +49,7 @@ metadata: # # Recompute with `make release.subject`; check with `make release.verify`, # which a third party can run with a clone and a Python and nothing else. - buildHash: '830f62fda35e03d00836602fe9ab7d6237209f1c83277c5b79c10cf691605d4f' + buildHash: '90f232d2fede1efd83bbdd4b7e6e5928030edaa9b67adf6d67fe300a405ad4c8' cicSign: 'TBD' cicSignedCA: certificate: "TBD — filled by the release process with the CIC Root CA certificate"