Skip to content

E.1 first half: the projector emits ProjectedFromSource, and refuses the insertion the substrate cannot express - #9356

Merged
briansrls merged 3 commits into
mainfrom
e1/projector-shape
Aug 26, 2026
Merged

briansrls merged 3 commits into
mainfrom
e1/projector-shape

Conversation

@gunbai-bot

@gunbai-bot gunbai-bot Bot commented Aug 26, 2026 •

Copy link
Copy Markdown
Contributor

First half of E.1, the projector P. It emits the correspondence the delta consumes — and it refuses, loudly and countably, the one thing a qualifying projection actually does.

Why half

ProjectionProvenanceEntry has two arms. ProjectedFromSource needs nothing that does not exist. ProjectedAsInsertion { target, caused_by } is unemittable, and the reason is a property of the substrate rather than of this module:

  • std.occurrence_identity NodeOccurrenceIdentity has exactly two arms, OccurrenceSynthetic and OccurrenceMinted, and neither carries a cause.
  • caused_by appears zero times anywhere in dag/std or src/v2/std.
  • The arm an insertion would have to borrow, OccurrenceSynthetic, is already the fill value for every non-parser-constructed node — so borrowing it collapses parser-minted, incidentally-synthetic, and projection-synthesized-with-cause into one symbol.

The cost of improvising past that is a wrong answer, not a missing feature. The delta's source-coverage law reads carried sources; caused_by is exactly what stops an insertion's named base occurrence from discharging that occurrence's coverage obligation. A placeholder cause — or a local convention that Synthetic means projector-created here — makes the law report that a dropped occurrence survived. That is the defect review 56299 caught on #9337, arriving through the carrier instead of through the filter.

So insertions refuse, with a typed arm naming the occurrences, blocked on node://adhoc-e1692c72-f81 (model the third arm as a shared-authority algebra decision). No placeholder, no optional field, no convention: each is the fabricated cause wearing a different hat, and a fabrication is worse than an omission because the omission is countable.

The five laws, denominator and multiplicity named before the filter

law denominator multiplicity
every occurrence decided base observation's expected population —
decided once same exactly one per base occurrence
no foreign decision the decisions themselves, against the expected population —
insertion representable declared insertions —
drop named where known the decisions themselves —

A drop refuses here rather than downstream, and the two refusals differ. The delta would refuse an omitted occurrence as ProvenanceSourceMissing — true, but a statement about the provenance. The fact known here is that the projection dropped a reference. DroppedByProjection exists as a decision rather than being expressed by omitting the occurrence because "you dropped it" and "you did not tell me about it" are different facts with different owners.

Evidence

8/8 arms PASS by execution on a remote claim_batch run. Each fixture starts from the admitted shape and applies exactly one typed mutation, so it violates exactly one law — the arms are ordered, and a two-law fixture would test the ordering while decaying silently as earlier laws are corrected. This lane has been bitten by that once already.

a_declared_insertion_refuses_because_no_carrier_can_express_its_cause is the executing evidence that the module did not design around the absence. When the third arm lands, that arm flips to a regression control over the emitted insertion rather than retiring — deleting it would recreate specification-without-execution one rung up.

Not claimed

This is the provenance emission's carrier, not a graph transform over the v2 pipeline. It turns per-occurrence carry decisions into ProjectionProvenance; deciding what those decisions are is the rest of E.1 and needs the occurrence identities to reach the semantic graph.

@gunbai-bot gunbai-bot Bot changed the title import deletion continuation E.1 first half: the projector emits ProjectedFromSource, and refuses the insertion the substrate cannot express Aug 26, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 26, 2026 20:37
@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

CI state on this PR is green, and the one red row is a superseded run, not a defect. Recording it because the dashboard summary and GitHub disagree, and the disagreement will keep firing "CI FAILING" notices.

Flipping this PR from draft to ready started run 33011313374, which cancelled the in-flight 33010596082. The aggregator job in the cancelled run reported build=cancelled floor=cancelled and exited nonzero — which is correct behaviour: the witnesses job declares always() precisely so a cancelled run reports on its own terms rather than leaving the required context unanswered, and answering "red" for lanes that never finished is the honest reading, not a regression.

What each source says:

  • GitHub: mergeStateStatus: CLEAN. Both witnesses rows are attached to this sha — FAILURE 20:39:15 (cancelled run) and SUCCESS 21:28:43 (live run) — and the required context resolves to the later one.
  • Live run 33011313374: build pass 20m36s, floor pass 49m20s, witnesses pass.
  • Dashboard summary: checks failing, reading the stale row.

So the merge criterion that actually gates is satisfied, and the dashboard's ready: false is an artifact of reading a cancelled run's aggregator as current. I am not pushing anything to clear it — there is nothing to fix, and a no-op commit to make a stale row disappear would be churn that also invalidates the standing approval.

Sequencing note, unrelated to the above: this PR is deliberately waiting on #9359, which re-homes SubjectOccurrenceRef to std.occurrence_identity.ScopedOccurrenceRef { scope, occurrence }. This PR references the old name in four places. quiet-badger-569 and I agreed #9359 lands first and I carry the rename here, since theirs has the wider blast radius and this is two new files nobody imports yet.

— sent from crisp-crab-430

The superseded run's aggregator rendered as FAILURE rather than cancelled (its own
build and floor rendered as cancelled), and a failed check-run is terminal, so the
dashboard -- which aggregates every check-run on the head rather than the latest per
name -- reported this PR unmergeable with nothing left to converge. The content did
not change and does not need to: build, floor and witnesses all passed on the live
run. Re-running the failed check would ask a correctly-cancelled run to answer again,
so the head moves instead.
@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Correcting my previous comment on this PR: I was wrong that there was nothing to fix.

I read the disagreement between gh pr checks and the dashboard as a display artifact that would resolve itself. It would not have. The two instruments answer different questions and only one of them decides merge readiness:

  • gh pr checks shows the latest check-run per name — three passing.
  • The dashboard aggregates every check-run on the head. Measured on 97a2e8fdff, there are six, not three:
build     completed/cancelled     build     completed/success
floor     completed/cancelled     floor     completed/success
witnesses completed/FAILURE       witnesses completed/success

The cancelled run's build and floor rendered as cancelled; its witnesses aggregator alone converted supersession into failure. A failed check-run is terminal, so there was nothing to converge and re-reading later would never have cleared it. That is gunbc#9339, and it is a harder defect than the false-alert framing it has carried until now: a superseded run permanently blocks dashboard merge readiness.

So the head has moved. The commit is empty by design — the content did not change and does not need to, since build (20m36s), floor (49m20s) and witnesses all passed on the live run 33011313374. I did not re-run the failed check: the ghost belongs to a run that was correctly cancelled, and re-running is asking a dead subject to answer again.

The trigger was almost certainly my own gh pr ready on this PR — the dashboard opened it as a draft at 20:28, undrafting fired a second run at 20:37, and the second cancelled the first. That trigger is separately ruled for deletion (#9361), so it stops happening; it did not stop in time for this PR.

The standing approval was against content that is byte-identical here.

— sent from crisp-crab-430

@gunbai-bot

gunbai-bot Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Took the §4c remark from review 56387, and reached the opposite conclusion about it — which is why I pushed rather than replied.

The review reads the data legacy_binding_projection_insertion_blocked_note: String as admitted because it declares a bounded dissolution condition. That is backwards. §4c says the opposite in as many words: "Any invariant, receipt, event, ruling, citation, status, count, dissolution condition, or other machine-consumed fact belongs in a typed carrier. An ordinary String declaration whose sole purpose is commentary is misplaced or dead data." Naming a trigger is precisely what makes the row a fact rather than commentary, and therefore precisely what disqualifies the String carrier. It is also the exact shape #6262 introduced and #6424 swept 215 of.

std.dissolution already owns this, and the practical difference is not cosmetic: dissolution_status can read a DissolutionCondition and cannot read a sentence. A String note leaves the retirement condition invisible to the one mechanism that exists to see it — which that module's own annotation calls out by name as how the corpus lost its retirement conditions in the first place.

So the row is now:

data legacy_binding_projection_insertion_dissolution: DissolutionCondition = UnboundDissolution { ... }

with the irreducible rationale moved to a // annotation, where prose belongs.

Unbound rather than bound, and that is forced rather than lazy. The bound form is DeclarationAppears over std.occurrence_identity OccurrenceProjected — which does not exist on this base, so authoring that citation here would be refused by the citation wall as a name that does not resolve. That is the §3 fabricated-citation class std.dissolution's own annotation warns about. The condition binds in the same change that rebases onto #9359, at which point the ref is verifiable at the moment it is written rather than a promise about a name someone may choose later.

I also dropped the transcribed counts from the note and named the instrument instead — grep caused_by across dag/std and src/v2/std, read NodeOccurrenceIdentity's arms — since a measurement copied into prose is the positional-citation class and rots without anyone touching either end.

8/8 arms still PASS by execution on a remote claim_batch run after the change.

— sent from crisp-crab-430

@briansrls
briansrls merged commit 4704e1d into main Aug 26, 2026
3 checks passed
@briansrls
briansrls deleted the e1/projector-shape branch August 26, 2026 22:53
@briansrls
briansrls restored the e1/projector-shape branch August 26, 2026 22:56
gunbai-bot Bot pushed a commit that referenced this pull request Aug 26, 2026
…onsumers #9356 added after this branch was cut

#9356 merged ahead of this PR, adding src/v2/workflow/legacy_binding_projection.dag and
its witness. Both import SubjectOccurrenceRef from v2.workflow.legacy_binding_observation,
which this PR deletes in favour of ScopedOccurrenceRef in std.occurrence_identity.

The two changes touch disjoint files, so git merges them cleanly and merge-tree reports no
conflict -- while the resulting tree holds three live references and two imports of a type
it does not declare. Green plus green equals red with no conflict marker anywhere. This PR
owns the rename, so it carries every site the rename touches, including sites that appeared
after it was cut; there is no ordering of two PRs that avoids a broken window.

Sites:
  legacy_binding_projection.dag        import + 2 ScopedOccurrenceRef constructions
  legacy_binding_projection_witness    import + 2 .scope.digest field reads

Also corrects the LAW 4 annotation in that witness, which justified the projector's
insertion refusal by saying NodeOccurrenceIdentity has no arm carrying a cause. This PR
makes that false. The refusal STAYS and the witness stays green -- the projector has not
been wired to the new arm -- but the stated reason is now about what this module has been
taught to do, not about what can be expressed. A refusal defended by a reason that has
stopped being true is the stale-prose failure sitting inside the honesty argument itself.

Verified: working tree greps clean for SubjectOccurrenceRef outside one historical prose
reference in the hoist's own annotation; 4097 files parse-clean; projection 8/8,
occurrence_identity 12/12, legacy_binding_delta 17/17 witnesses PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Aug 27, 2026
…rtion has a producer (#9359)

* NodeOccurrenceIdentity gains a projector arm so ProjectedAsInsertion has a producer

E.1's ProjectedAsInsertion carries the base occurrence an inserted qualifier
acts on behalf of, and no producer could reach it: the only spelling a
projector had for a node it created was OccurrenceSynthetic, which says a node
has no authored occurrence and says nothing about why it exists. The cause was
gone at construction and std.occurrence_identity's own scope law closes every
route by which a finished tree could be re-keyed against the one it was
projected from.

The failure this prevents is a wrong answer, not a missing feature: with no
distinguishable insertion, the delta's source-coverage law reads an insertion
as evidence that a dropped base occurrence survived.

- std.occurrence_identity gains OccurrenceProjected { id, caused_by } -- a
  third constructor, not a field on OccurrenceSynthetic. A field would conflate
  "the compiler synthesised this and no cause exists" with "a projector created
  this and the cause was dropped", and would retype every existing synthetic
  node silently. A third arm makes every total match refuse to compile until
  its author decides what a projector-created occurrence means there.
- SubjectOccurrenceRef is hoisted out of v2.workflow.legacy_binding_observation
  into std.occurrence_identity as ScopedOccurrenceRef { scope, occurrence },
  beside the scope law it discharges.
- v2.std.node's facade conversion returns a narrowing rather than flattening
  the new arm into SyntheticOccurrence: the two-arm facade is declared
  temporary, so it states its boundary instead of growing.
- legacy_binding_delta derives the insertion row from the node the projector
  built (insertion_provenance_entry), with a typed refusal for a created node
  whose cause was lost.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Delete the orphaned pointer annotation the hoist left at the end of legacy_binding_observation

The re-home note sat after the module's last declaration, so section 4c refused
it: an annotation names the module item that follows it, and nothing followed.
The build lane caught it at emit (8 hard diagnostics, all this one block) --
claim_batch's preparation is lenient about annotation grain, so the local
witness runs went green over it.

Deleted rather than moved above some unrelated declaration. The fact it stated
-- that ScopedOccurrenceRef now lives in std.occurrence_identity beside the
scope law -- is already carried by that type's own annotation there. A pointer
note in the module that no longer declares the type is a second home for one
fact, which is what the hoist was closing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Carry the SubjectOccurrenceRef -> ScopedOccurrenceRef rename to the consumers #9356 added after this branch was cut

#9356 merged ahead of this PR, adding src/v2/workflow/legacy_binding_projection.dag and
its witness. Both import SubjectOccurrenceRef from v2.workflow.legacy_binding_observation,
which this PR deletes in favour of ScopedOccurrenceRef in std.occurrence_identity.

The two changes touch disjoint files, so git merges them cleanly and merge-tree reports no
conflict -- while the resulting tree holds three live references and two imports of a type
it does not declare. Green plus green equals red with no conflict marker anywhere. This PR
owns the rename, so it carries every site the rename touches, including sites that appeared
after it was cut; there is no ordering of two PRs that avoids a broken window.

Sites:
  legacy_binding_projection.dag        import + 2 ScopedOccurrenceRef constructions
  legacy_binding_projection_witness    import + 2 .scope.digest field reads

Also corrects the LAW 4 annotation in that witness, which justified the projector's
insertion refusal by saying NodeOccurrenceIdentity has no arm carrying a cause. This PR
makes that false. The refusal STAYS and the witness stays green -- the projector has not
been wired to the new arm -- but the stated reason is now about what this module has been
taught to do, not about what can be expressed. A refusal defended by a reason that has
stopped being true is the stale-prose failure sitting inside the honesty argument itself.

Verified: working tree greps clean for SubjectOccurrenceRef outside one historical prose
reference in the hoist's own annotation; 4097 files parse-clean; projection 8/8,
occurrence_identity 12/12, legacy_binding_delta 17/17 witnesses PASS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Brian Searls <briansearls1@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
gunbai-bot Bot pushed a commit that referenced this pull request Aug 27, 2026
…re the three claims it falsifies

#9356's projector module was authored against a base where NodeOccurrenceIdentity had no
arm carrying a projector-created node's cause. This PR adds that arm, which makes four
claims in that module and its witness false the moment it merges. One of them is not prose.

THE MACHINE-CONSUMED ONE. legacy_binding_projection_insertion_dissolution was an
UnboundDissolution whose description read "unemittable while ... NodeOccurrenceIdentity
carries no arm". std.dissolution dissolution_status matches over DissolutionCondition, so
merging as-is would put a false FACT on main: a debt reporting UNBOUND while its own
binding declaration sits in the same commit. It is now
bound_dissolution(decl_ref(std.occurrence_identity, OccurrenceProjected)).

This change is entitled to bind it, and is the first change that was. The row's own
annotation named the trigger: "The condition binds in the same change that rebases onto
the arm, at which point the ref is verifiable at the moment it is written." This is that
change. The citation wall would have refused that ref on the old base -- authoring it
there would have been the §3 fabricated-citation class -- and it resolves on this one.
Receipt: the parse sweep's citation count moves 1516 -> 1517 with debt unchanged at 42,
so the new reference resolves rather than joining the debt roster.

THE THREE PROSE CLAIMS, corrected rather than deleted:
  - the module preamble asserting NodeOccurrenceIdentity "has two arms" and that caused_by
    appears zero times in dag/std and src/v2/std, and that insertions are blocked on a
    named work item "until it lands"
  - the annotation saying the bound form's declaration "does not exist on this base"
  - the witness function name a_declared_insertion_refuses_because_no_carrier_can_express_
    its_cause -> ..._because_this_projector_is_not_wired_to_the_cause_arm

WHAT DID NOT CHANGE: the refusal. ProjectionInsertionUnrepresentable still fires and the
witness still passes. What the module cannot do is no longer EXPRESS an insertion's cause
but EMIT one -- nothing here has been taught to allocate a projected occurrence and name
what caused it. Wiring that, and flipping the arm to a regression control, belongs to the
change that does the wiring.

Found by crisp-crab-430, checking this change against the merged tree rather than the diff.

Verified: 4097 files parse-clean; projection 8/8 PASS. No consumer asserts this row's
unboundness, so nothing depended on the false state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot mentioned this pull request Aug 27, 2026
6 tasks
@gunbai-bot
gunbai-bot Bot deleted the e1/projector-shape branch August 27, 2026 01:18
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