Land assertion 4, and close three Tier 1 capabilities on the record - #1
Merged
Conversation
…ot wrong Feature 002 takes three Tier 1 capabilities to full parity. Every public name it adds is registered here first, at a tag, before either library ships it. This is that half: no library behaviour changes in this commit. The register gains what US3 needs. `read IDF from disk, keeping diagnostics` gains a Python name and becomes aligned; giving a name to a field that was "" is an addition rather than a rename, so rename_count.python stays 0. The type the returning path hands back is registered as its own concept. `diagnostics from a parse` was recorded as divergent on the grounds that Python raises and TypeScript returns; that was never what the two did, since both default to strict and both raise, so the entry describes what actually differed and is now aligned. `a parse diagnostic` claimed a severity that neither implementation has ever carried. Corrected rather than implemented: adding a field to match a note nobody had checked is the wrong direction. Four corrections the record needed anyway. Five references still pointed at idfkit/docs/, which feature 003 emptied. known-divergence.toml's header claimed 22 entries over 47 cases; the file holds 20 over 48, recounted rather than adjusted. parse-diagnostics is narrowed to the IDF parser, and the reason is recorded where the closure cannot delete it. TypeScript's epJSON parser builds diagnostics; Python's builds none and drops malformed objects with a bare continue at epjson_parser.py:226 and :246. That gap is real, is not closed here, and would have been swallowed by a complete/complete move, because the gate deletes `differences` when a capability stops being partial. It lives in a comment above the entry for that reason. governance/README.md has always said CI runs the validation rules on the pull request. It did not: .github/ held only CODEOWNERS, so a malformed entry could reach an immutable tag unchallenged. tools/validate_governance.py implements the rules the README lists, plus the one that forces a closure to drop its differences text in the same edit, and a workflow runs it. It never imports either library, so it cannot go red because a library drifted. The writer options object is NOT registered, and T011 is dropped. The register already excludes Python options objects, excluded is terminal under FR-006, and check_excluded_counterparts enforces it. US4's three controls land as keyword arguments instead, so no new public name ships.
check-parity-ledger.mjs rejected the register edit: ParseResult is exported from @idfkit/core under the new concept and no capability claimed it. It belongs to parse-diagnostics, alongside the other two diagnostics concepts.
check-naming-register.mjs read the previous commit as dropping a registered TypeScript name: 'diagnostics from a parse' pointed at ParseResult.diagnostics and now pointed at IdfParseError.diagnostics, with rename_count unchanged. FR-079 is right to stop that. Nothing was dropped from either library. After feature 002 both languages carry parse diagnostics in both places, so the entry names both on both sides. The old pair crossed the two paths, which was the divergence this entry used to record.
FR-005 allows one name per concept per language, so the previous commit's two names per side is not available. The registered pair crossed the two paths, Python's error against TypeScript's result, and that crossing was the shape of the divergence the entry recorded. It cannot stay once both languages carry both. Split into one concept per carrier, which makes both alignable. 'diagnostics from a parse' is the error's collection and its TypeScript name moves from ParseResult.diagnostics to IdfParseError.diagnostics, spending that name's one rename. 'recoverable diagnostics from a parse' is the result's and keeps the TypeScript name it has always had. The alternative mapping costs the same one rename against Python instead, on a name that already exists rather than one landing in this feature.
…e the record The library populated all three families the ledger recorded, so the tier1-introspection-field-shapes exception went stale and the runner failed on it, which is what that entry was built to do. It recorded no bug: it pinned a divergence the ledger declared so the ledger's prose could not quietly stop being true. Removed here, in the change that closes the capability. The JavaScript runner now loads the prose pool and passes it to describeObjectType. TypeScript keeps that function synchronous and takes the pool as an argument, so prose is opt-in for the caller; this runner is a caller that wants the whole description, so it opts in. A checkout that ships no pool compares without prose rather than erroring, which is what a pre-002 checkout should do. tier1-introspection-field-order is new, and covers what the existing case could not. That case compares two types in the newest schema, and both have a positional field list to order by, so it cannot see an ordering bug. The new one takes the three types that have none, in 9.4.0 rather than 26.1.0, because field order comes from per-version schema data and a rule that holds in the newest release can be wrong twelve releases back. SolarCollector is included as a control: it agreed by alphabetical accident, so a fix that worked by accident rather than by recording the order would break it. Verified the case discriminates before trusting it: restoring the removed exception fails both runners, removing it passes them. A case that passes either way proves nothing. introspection moves to complete on both sides and loses its differences text, which the gate requires and which nothing here needs to survive. Two claims in that text were false and are recorded in a comment rather than dropped in silence: no header comment ever promised an @idfkit/schemas/docs subpath, and the field-order count was three types in every version rather than two in one plus six in the old releases. 49 cases, 134 assertions, both runners green.
Assertion 4 was declared, deferred, and skipped by both runners since the
corpus was written. It was the one assertion that needed changes inside the
libraries, and feature 002 made them, so it runs now. Nothing is skipped any
more: every assertion every case declares is evaluated.
The comparator takes an unordered multiset of (code, line, typeName) and never
message text. Unordered because the order findings are noticed in is an
implementation detail of the scan that neither library promises. A multiset
rather than a set because two skips of the same type at different lines are
two findings, and collapsing them would hide the exact bug this assertion
exists to catch.
Two paths, because both libraries raise by default and both return findings
when asked not to. `diagnostics` compares whichever path the input takes;
`recoverable` names the returning path explicitly, which a case needs when its
input fails a strict read and the recoverable behaviour is still worth
checking.
Two new cases, sharing one input read both ways, so a difference between them
is a difference between the paths rather than between two files that happen to
be shaped differently. Their expectations are hand-authored against the rule in
compare.md, and the line numbers were read off the file: seed_tier1.py does not
cover assertion 4, and an expectation drafted from one library and committed
unread would make that library the oracle.
WHAT ASSERTION 4 FOUND, immediately, which is the point of building it:
malformed-truncated-object Python reports no finding at all, because
_OBJECT_PATTERN matches only text reaching a
semicolon. Same root cause as this case's
already-accepted parse-outcome divergence, same
issue 187, and recorded alongside it.
malformed-stray-comment Python reports the type name as "a block
comment" where TypeScript reports "/* a block
comment". The regex requires a type to start
with a letter, so the scanner never saw the
"/*". NOT YET TRACKED: needs an issue.
malformed-missing-semicolon Neither library reports anything where the
oracle diagnoses an invalid field value. The
case predicted this silence in its own why.
Both libraries agree, so it is not a parity gap;
it is a shared gap against the convention.
NOT YET TRACKED: needs an issue.
The last two are why both runners still exit 1, and why parse-diagnostics has
NOT been moved to complete in this commit. The record moves when the corpus
can say the two agree, not before.
Five corpus cases, one per control, so a failure names the control that broke rather than reporting one failure for five possible causes. Each writes its document under the control, re-reads it, and compares parsed values. The comparison is structural because it has to be: the two writers differ on seven defaults that no control removes, so comparing text would fail these cases for reasons that have nothing to do with the control being exercised. The case schema gains an optional writer_options block, language-neutral, which each runner maps onto its own writer's option names. Absent on all 48 pre-existing cases and it invalidates none of their expectations. Verified the controls are not no-ops before trusting the cases: writing the same document with and without each control changes the output for all five in the first language and for four of five in the second. The exception is ordering=source, which is already that writer's default, and the case says so rather than reading as a check it is not. write moves to complete on both sides and loses its differences text. The seven measured default-output differences move to a page, because the gate rejects a differences field on a capability that is not partial and a reader still meets them the first time they diff two files. The measurement was re-taken on the day it closed and still reads 359 objects, 4,031 lines against 4,125. checks/ is still not created. Every claim here is document-shaped.
The known-divergence header was recounted in this feature and then went stale again when eight cases landed: it said 13 cases on a 51-case corpus, and the file holds 12 on a corpus of 56 with 148 assertions. README.md still said both runners exit 0 because every failure is allowlisted. They exit 1, which is the blocker this feature records against parse-diagnostics, and the README now names the two divergences that are not recorded. Its case count and timing table were stale for the same reason. governance.yml had a step called 'Validate the case manifest schema is well formed' that parsed two JSON files and printed their sizes. It now runs load_corpus, which checks every manifest entry against its case directory, which is what T015 added the workflow for. naming.toml said the returning loader pairs with strict_parsing=False. It takes no such argument, and the register is read at an immutable tag, so the wrong text is what the site would render. The location-field claim is narrowed to what the library actually sets. parity.toml's write entry now records the two controls that remain spelled on one side only, ordering and versionFirst, and why complete is still the right availability: neither gives a caller access to output the other writer cannot produce. run.py's dispatcher docstring still said assertion 4 was accepted and skipped.
Three tracking issues were opened for the divergences assertion 4 found, so they can be recorded rather than left to fail the run: idfkit 190 for the tokenizer's type-name capture, and idfkit 191 with idfkit-js 36 for the shared silence where both libraries accept a type name in a numeric field and neither says anything. Both runners are green. parse-diagnostics moves to complete on both sides and loses its differences text. Four diagnostics divergences remain allowlisted and complete is recorded knowing it, on the same footing as parse and write, which carry fifteen and four between them. The field records whether a capability is implemented in both languages; the corpus records the bugs, each against a tracked issue. Its removed prose said Python raises and TypeScript returns. Neither library ever did that, so the text goes as history rather than being amended. That is the third of three closures. Tier 1 capabilities marked partial fall from 6 to 3, which is what SC-011 asks for, and the three that remain are the three this feature never touched. manifest.json declares corpus_level for the first time. It has been null since the corpus was written, which meant the case set was always ahead of the level both libraries pinned.
This was referenced Sep 4, 2026
Both libraries now report InvalidField for a value of the wrong kind, so the two entries went stale and the runners failed on them, which is what a stale entry is for. Removed in the change that fixed the bug, per the file's own rule: an entry is a bug with a tracked resolution, and the change that fixes it removes the entry. 21 entries over 13 issues and 13 cases. Both runners exit 0.
idfkit#190 is closed, so the entry went stale and the runner failed on it. Removed in the change that fixed it. 20 entries over 12 issues and 12 cases. Both runners exit 0, and the only malformed divergences left are the two halves of issue 187.
Adds difference_kind, difference_issue and difference_note, the vocabulary
withdrawn with feature 002's User Story 1. The spec said plainly what withdrawing
it cost: three capabilities stay partial with no statement of whether their
differences are temporary, so a reader cannot tell whether to write one into
their own code or wait for it to go away. That is the question they actually
have, and differences alone does not answer it.
The words are absence_kind's, not new ones. A reader who has learned that
'never' is terminal for an absent capability should not learn a second word for
the same idea about a partial one. not-yet carries a real tracking item on the
same terms as issue; never carries a reason and is terminal, movable only by
constitutional amendment.
Separate fields rather than reusing issue and note, because a capability can be
partial on one side and absent on the other, and one pair could not then say
which state it was describing.
The three partial entries, and the reasoning is theirs rather than mine:
weather-index never. The packaging is deliberate, since pip extras gate
dependencies rather than files while npm can opt out, and
the freshness nudge needs a writable cache a browser
package has none of.
weather-download never. Selective extraction is a property of writing into
a cache; the JavaScript side has no disk, decodes in
memory, and already holds every member.
generated-object-types not-yet, tracked at #194. The coverage half closes,
and feature 002's withdrawn User Story 7 already measured
it at 8 to 9 percent rather than seventeen copies. The
application half is each language's type system and stays.
tools/validate_governance.py enforces it, and rejects the fields on an entry
that is no longer partial for the same reason it rejects a stale differences.
Neither library's parity gate rejects unknown keys, so this is additive to both
and reaches them only when they advance their pin.
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.
Feature 002. The record and the proof half; the two libraries and the site are three separate pull requests.
Merge this one first. The other three pin
governance-2026.10andconformance-2026.8, which do not exist until this merges and the tags are cut frommain. Their CI is red until then, and that is expected rather than a defect in them.What this does
Three Tier 1 capabilities move to
completeon both sides and all three lose theirdifferencestext. The count of Tier 1 capabilities markedpartialfalls from 6 to 3, and the three that remain are the three this feature never touched.introspectionwriteparse-diagnosticsAssertion 4 ships
It was declared, deferred and skipped by both runners from the day the corpus was written, because it was the one assertion needing changes inside the libraries. Both now carry a shared
code, so it runs. Nothing is skipped any more: every assertion every case declares is evaluated.It compares an unordered multiset of
(code, line, typeName)and never message text. A multiset rather than a set, because two skips of the same type at different lines are two findings, and collapsing them would hide the exact bug it exists to catch.Eight new cases
Every expectation for assertion 4 is hand-authored against the rule in
compare.md, with the line numbers read off the file.seed_tier1.pydoes not cover assertion 4, and an expectation drafted from one library would make that library the oracle.What assertion 4 found immediately
Three divergences, all now recorded against real tracking issues:
malformed-stray-commentmalformed-missing-semicolonmalformed-truncated-objectThe missing-semicolon pair is the shape FR-015 exists to catch: both libraries agree and both differ from the oracle, so no comparison between them could have found it.
Things the record got wrong, corrected here
parse-diagnosticssaid Python raises and TypeScript returns. Neither ever did: both default to strict and both raise. The text described the non-strict mode while reading as though it described the default.introspectionclaimed a@idfkit/schemas/docssubpath no header comment promises, and a field-order count wrong in both halves.idfkit/docs/, which feature 003 emptied.known-divergence.toml's header count was stale.governance/README.mdhas always said CI runs the validation rules on the pull request. It did not:.github/held only CODEOWNERS.tools/validate_governance.pyand a workflow now do, so a malformed entry cannot reach an immutable tag unchallenged.Gaps recorded rather than closed
Python's epJSON parser builds no diagnostics at all and drops malformed objects with a bare
continue, where TypeScript's builds them.parse-diagnosticsis therefore narrowed in title to the IDF parser, and the gap is recorded in a comment above the entry, becausedifferencesis deleted when a capability stops being partial and the note has to outlive the closure.Review
Both languages, per FR-067, and FR-091 gives it no override. Everything under
governance/andcases/is in scope.After merge: cut
governance-2026.10andconformance-2026.8from the merge commit. Both are currently cut locally at this branch tip and must be re-cut, per the five-step checklist ingovernance/README.md.Verification
56 cases, 148 assertions, 0 skipped. Both runners exit 0: python 132 passed with 16 allowlisted, typescript 141 passed with 7 allowlisted, and nothing unexplained on either side.