Slot tolerance: a record with unread slots is held read-only, not shed #124

Merged
libby merged 20 commits from hands/slot-tolerance into main 2026-11-12 02:45:08 +00:00
Member

cultlib-gaps:follow_up:slot-tolerance-r4-land, spec cultlib-gaps:cut_spec:cut-slot-tolerance.r5: a record whose payload carries slots past its reader's declared slots, at any depth, is held read-only (every write or removal refused, nothing changed on disk) instead of shedding the slots on the next write; consent follows cultlib-gaps:ruling:slot-mark-consent; types the guard cannot measure are not guarded (cultlib-gaps:ruling:slot-unmeasurable-type). Vectors in tests/vectors/union-parity/ (extra-slot-store, extra-slot-consent, slot-outcomes) for the TS, Python and Rust leaf cuts (cut-slot-readonly-{ts,py,rs}).

Semver: Breaking in unity/org.gamecult.cultlib/CHANGELOG.md (writes that used to succeed are refused). ApiCompat (measureApiBreaks against cultlib-unity-v3.0.0): 0 breaks; public additions only.

Soul: verdicts cut-slot-tolerance.s1 to .s4 (s1 to s3 blocked on false read-only marks, Flatten through a marked base, the formatter-identity decision and a stale GeneratorConsumer check; s4 held). Caching 1347, Mesh 357, Networking 830 passed, GeneratorConsumer exit 0, vectors regenerate byte-identical.

🤖 Generated with Claude Code

`cultlib-gaps:follow_up:slot-tolerance-r4-land`, spec `cultlib-gaps:cut_spec:cut-slot-tolerance.r5`: a record whose payload carries slots past its reader's declared slots, at any depth, is held read-only (every write or removal refused, nothing changed on disk) instead of shedding the slots on the next write; consent follows `cultlib-gaps:ruling:slot-mark-consent`; types the guard cannot measure are not guarded (`cultlib-gaps:ruling:slot-unmeasurable-type`). Vectors in `tests/vectors/union-parity/` (extra-slot-store, extra-slot-consent, slot-outcomes) for the TS, Python and Rust leaf cuts (`cut-slot-readonly-{ts,py,rs}`). Semver: Breaking in `unity/org.gamecult.cultlib/CHANGELOG.md` (writes that used to succeed are refused). ApiCompat (`measureApiBreaks` against cultlib-unity-v3.0.0): 0 breaks; public additions only. Soul: verdicts `cut-slot-tolerance.s1` to `.s4` (s1 to s3 blocked on false read-only marks, Flatten through a marked base, the formatter-identity decision and a stale GeneratorConsumer check; s4 held). Caching 1347, Mesh 357, Networking 830 passed, GeneratorConsumer exit 0, vectors regenerate byte-identical. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
The guard formatter counts each int-keyed object's slots into a thread-bound scope opened around the one payload decode;
ToStoredDocument turns the finding into CultStoredDocument.UnreadSlots (explained only for depth-0 slots the catalog comparison
reports ignored); HeldOwned refuses an unexplained mark whatever is declared; RefuseUndeclared throws the one refusal with
UnreadSlots; CultCache.IsWritable asks the same predicate. Vectors: extra-slot-unit-arm, extra-slot-store.cc.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
SlotReadOnlyTests reads the union-parity vectors through VectorFiles.Committed, the shared reader, so
UnionParityVectorTests.Committed stays private. The variant test that only pins the read no longer says
the extra slot is ignored: under the slot rule it is read past and the record is read-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Per vector: the object carrying the extra slot, its depth and the mark (type, slots carried, slots
declared); the counting rule (highest key + 1, short arrays and retired gaps do not mark, a union adds no
depth); the store-record assertions, the consent rule and the extra-slot-store.cc carry check that
cut-slot-readonly-ts/-py/-rs assert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Behavioural break per docs/semver-policy.md (tightens the soft-drift classification; writes that
succeeded now refuse). ApiCompat against cultlib-unity-v3.0.0 measures no signature break.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Vectors: extra-slot-store.cc gains the unit-arm record (tests.slot_unit); extra-slot-consent.cc
(records under tests.union_surface.wide) and slot-outcomes.msgpack are new. The consent reader
(SurfaceDeclaring, listing the wide id compatible) and UnitDoc move into UnionParityVectorTests;
the in-test consent store construction, Relabel and AppendRootSlot go. Red against 115bc0a2:
9 tests (spec cultlib-gaps:cut_spec:cut-slot-tolerance.r5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DeclaredSlots (the reflected public [Key] count) is deleted: it falsely marked an AllowPrivate
type's own write. SlotFormatter measures one instance (uninitialized, else a parameterless
constructor) on its first read inside a scope and keeps the count; a type neither instance can be
written for is not guarded (question slot-unmeasurable-type, recommended answer unguarded).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Explained required only that some slot was reported, so a depth-0 slot the comparison never
reported was consented to and dropped on write (finding unreported-slot-over-consent).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WriteRefusal holds Claim's whole decision (no home store, read-only home, the home store's
Refusal) and the flatten-chain walk: a flatten is refused with the refusal of the first record on
its base chain its home store does not own. Claim throws its answer; IsWritable asks it of the
held record's removal form, and for an unheld key whether no store lists it foreign. IsWritable's
per-store Refusal sweep is deleted (findings iswritable-readonly-store, flatten-marked-base-loss).
MintElementIds' doc names the mark.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CHANGELOG Unreleased (org.gamecult.cultlib): Breaking gains the flatten-through-an-unowned-base
refusal, MintElementIds refusing a store holding a marked memory-only record, and the
written-count rule with the [MessagePackFormatter] and unmeasurable-type exclusions; Added
corrects IsWritable. The vector README says what a runtime walks for extra-slot-consent.cc,
the unit-arm record and slot-outcomes.msgpack.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ResolvedBag (an assembly CultCacheFormatterResolver writes [count, items...]) and TagList
(List<string> with [MessagePackObject]) are falsely marked by the attribute-based guard (Soul s2,
guard-asks-attribute-not-formatter). extra-slot-store.cc gains marked-variant, a variant of
marked; slot-outcomes.msgpack rows gain a ninth column, the key whose refusal refuses a Flatten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Guarded decided from attributes, then measured whatever formatter the composite handed out, so a
consumer resolver's [count, items...] type and a [MessagePackObject] collection were falsely
marked (Soul s2 guard-asks-attribute-not-formatter). KeyLayout decides from the formatter itself,
by identity with StandardResolver's (or StandardResolverAllowPrivate's), excluding the attribute
and generic-collection resolvers' instances, as ResolverClaim does. The [MessagePackFormatter]
attribute check is deleted: that door is now the AttributeFormatterResolver identity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul s2 iswritable-unheld-copies-foreign. The copy was induced by r5's negative grep forbidding
Store.Refusal(key.Value, null) anywhere in IsWritable; the held branch still asks WriteRefusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exempt: any type not written by MessagePack's [Key]-layout formatter (own [MessagePackFormatter],
a consumer resolver, a [MessagePackObject] collection) and a type no instance of which can be
written (cultlib-gaps:ruling:slot-unmeasurable-type). The measure runs [Key] getters and a
parameterless constructor once per type per process; a failure leaves the type unchecked. The
README states the leaf exemptions and the flattenRefusedBy column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The assertion went out with 0dfc437b. The new test kills mutant M13 (the unheld branch answering
true), which survived without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check that each document type runs MessagePack's source-generated formatter saw the guard's
SlotFormatter and exited 1 (Soul s3 generator-consumer-check-red). The guard owns the one unwrap,
internal, visible to the consumer through InternalsVisibleTo; that also exposes the library's
internal GeneratedMessagePackResolver, so the consumer suppresses CS0436 (its own is the type used).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Soul s3 exemption-bullet-overstates-resolver. AnExtraSlotPastTheWrittenCountStillMarks pins it:
RelayResolver hands out StandardResolver's formatter for Relayed, and its extra slot marks 2/1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CHANGELOG: an unwritable type is not checked either (reads as a separate clause)
Some checks failed
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 15m8s
e41e99e0dc
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
libby force-pushed hands/slot-tolerance from e41e99e0dc
Some checks failed
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 15m8s
to 44c798d583
All checks were successful
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Successful in 21m18s
2026-11-12 02:23:24 +00:00
Compare
libby merged commit a8c2825745 into main 2026-11-12 02:45:08 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/CultLib!124
No description provided.