Skip to content

feat: merge records a conditional fact instead of applying it - #54

Open
ShocOne wants to merge 1 commit into
mainfrom
feat/merge-conditional-facts
Open

feat: merge records a conditional fact instead of applying it#54
ShocOne wants to merge 1 commit into
mainfrom
feat/merge-conditional-facts

Conversation

@ShocOne

@ShocOne ShocOne commented Jul 30, 2026

Copy link
Copy Markdown
Member

Phase 7.4, step one — the guard that closes the class of bug this phase exists for.

The problem, precisely

Behaviour is unconditional by construction: one Writable, one ReturnedOnRead, one RejectedValues per attribute. Writing a fact that holds only under a precondition into any of those turns it into a claim about every case.

That is exactly how the pilot came to suppress matchType's read-back for every tag on the strength of an observation about static ones, and how endpoint-agent came to sit in objectType's rejected set.

The fix

A fact with a precondition goes into the description with its condition stated, and nothing generates from it.

Both halves are load-bearing. Applying it is the bug. Dropping it silently would lose evidence somebody paid a live run for.

The guard sits immediately after the suspected-facts guard, in the same shape and for the same reason — both say "this fact is not a strong enough claim to put in the schema", differing only in why.

Result gains Conditional alongside Ignored rather than reusing it. Ignored would be the wrong word: these facts are strong enough to act on and were withheld because the schema cannot express the condition, not because the evidence was weak. Somebody deciding whether to trust a schema needs to tell those apart.

The condition reads first in the prose, because the claim is wrong without it:

matchType is not returned on read
when objectType is endpoint-agent, matchType is not returned on read

Four tests, and the control matters as much as the subject

Driven through Apply rather than the internal helper.

  • The subject: a Corroborated conditional fact — deliberately strong, so the test cannot pass because it was too weak — does not reach Behaviour, and does reach the description with its condition.
  • The control: the same fact without a condition still reaches Behaviour. Without this, the suite would pass if merge had quietly stopped applying returnedOnRead altogether — a different bug wearing the same green tick.
  • Both branches: two facts from one gate are both recorded and neither wins. An attribute whose description carries only one branch reads as though that branch were the whole truth, which is where this started.
  • Idempotence: a second merge changes nothing, which merge -check depends on.

Mutation tested: disabling the guard fails two of them, the second with neither branch may win: false — one branch silently winning, the original bug reproduced exactly.

Step two is deliberately not here

Behaviour holding conditional variants, so emission can act on them, waits for the re-record. There is nothing conditional in the committed facts yet to shape the storage against — and designing the storage before seeing the data is how the unconditional model came to be written in the first place.

Verification

gate result
go test ./... pass
probe -mode verify 38 facts reproduced
merge -check idempotent
verify 24/24, no orphans
gofumpt -l . / golangci-lint empty / 0 issues

🤖 Generated with Claude Code

Phase 7.4, step one. The guard that closes the class of bug this phase exists
for.

Behaviour is unconditional by construction: one Writable, one ReturnedOnRead,
one RejectedValues per attribute. Writing a fact that holds only under a
precondition into any of those turns it into a claim about every case. That is
exactly how the pilot came to suppress matchType's read-back for every tag on
the strength of an observation about static ones, and how endpoint-agent came
to sit in objectType's rejected set.

So a fact with a precondition goes into the description with its condition
stated first, and nothing generates from it. Both halves are load-bearing:
applying it is the bug, and dropping it silently would lose evidence somebody
paid a live run for.

The guard sits immediately after the suspected-facts guard, for the same
reason and in the same shape -- both are "this fact is not strong enough a
claim to put in the schema", differing only in why.

Result gains Conditional alongside Ignored rather than reusing it. Ignored
would be the wrong word: these facts are strong enough to act on and were
withheld because the schema cannot express the condition, not because the
evidence was weak, and somebody deciding whether to trust a schema needs to
tell those apart.

The condition reads first in the prose, because the claim is wrong without it:
"matchType is not returned on read" is false, and "when objectType is
endpoint-agent, matchType is not returned on read" is true.

Four tests, driven through Apply rather than the internal helper. The control
matters as much as the subject: the same fact without a condition still reaches
Behaviour, so the suite cannot pass because merge quietly stopped applying
returnedOnRead altogether. Both branches of one gate are recorded, because an
attribute whose description carries only one reads as though that branch were
the whole truth -- which is where this started. And a second merge changes
nothing, which merge -check depends on.

Mutation tested: disabling the guard fails two tests, the second with "neither
branch may win: false" -- one branch silently winning, which is the original
bug reproduced exactly.

Step two, Behaviour holding conditional variants so emission can act on them,
waits for the re-record: there is nothing conditional in the committed facts
yet to shape it against, and designing the storage before seeing the data is
how the unconditional model got written in the first place.

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.

1 participant