Uh oh!
There was an error while loading. Please reload this page.
test(plugin-auth): declare IDataEngine on the member-role migration double so its update pin ratchets - #12259
Merged
yinlianghui merged 1 commit intoAug 25, 2026
Conversation
…ouble so its update pin ratchets `makeMemoryEngine` in `member-role-canonical.test.ts` already bound its `update` to ObjectQL's own dispatch predicate by hand, but spelled only one engine sibling (`find`) — below `check:engine-double-contract`'s inference threshold. So the pin was real protection that no ledger row named: drop the `assertEngineUpdateDispatch` call, or the `update` member itself, and nothing reddens. Declaring the contract admits it through #11626's declaration route. The intersection (`IDataEngine & MemoryEngineHandles`) is what keeps this honest rather than padded — the contract is asserted and `rows`/`calls` stay declared as what they are, test handles read by the assertions. No member was added to satisfy discovery, and the double's behaviour is unchanged: 35/35 tests pass identically before and after. The ledger row is written by the gate's own `--write` (391 rows, 1 added, 0 lost). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
yinlianghui
marked this pull request as ready for review
August 25, 2026 15:38
Uh oh!
There was an error while loading. Please reload this page.
yinlianghui
deleted the
claude/issue-11850-engine-double-idataengine-annotations
branch
August 25, 2026 16:23
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 freeto 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.
Fixes#11850
Verified at
238cc7c73c.The card's table is 4/5 measurement artifact — re-measured, not assumed
The card asked for one
IDataEngineannotation on each of five update-slice doubles. Re-deriving the population with the card's own recipe (a patched copy ofscripts/check-engine-double-contract.mjswith the twosiblings.length < 2thresholds made configurable, run overtestFiles()for both slices, selectingpinned === true && siblings.length < 2 && !declared) reproduces the card's five rows exactly — same files, same lines, same sibling and member sets.But the predicate that produced them cannot tell an engine double outside the ratchet from a test-harness return tuple that re-exports an already-pinned function, and four of the five are the latter:
protocol.bulk-record-not-found.test.ts:114return { engine, rows, update, del, findOne }update,pinned: 1protocol.many-data-atomic.test.ts:93return { engine, update, del, rows, commits, rollbacks, handle }update,pinned: 1protocol.record-not-found.test.ts:53return { p: new ObjectStackProtocolImplementation(engine), findOne, update, del, store }update,pinned: 1protocol.update-path-id-wins.test.ts:128return { p: …, findOne, update, del, store }update,pinned: 1member-role-canonical.test.ts:393return { rows, calls, find, update }— a real minimal engine doubleIn each of the four
metadata-protocolfiles the real engine double is a separate literal a few lines above (const engine: any = { registry, update, delete: del, findOne, … }), it carries ≥2 engine siblings, it is discovered by inference, and its file already holds{"verb": "update", "pinned": 1}inscripts/engine-double-contract.pinned.json. Theupdatethe probe sees on the tuple is the samevi.fnobject, seen a second time through the harness's return value. There is no unprotected pin there.Annotating those four would have been a false declaration — the tuples carry
p,store,commits,rollbacks,handle, and are not engines — and would have pushed each file's ledger row frompinned: 1topinned: 2, double-counting one function. That is the padding failure mode #11626 was filed about, inverted. Per the card's own rule that the annotation must state what the object already is, those four are not candidates and are reported rather than padded.What this PR does
The fifth row is genuine.
makeMemoryEngineinpackages/plugins/plugin-auth/src/member-role-canonical.test.tsreturns a real minimal engine double: it already calledassertEngineUpdateDispatchby hand, spelled one engine sibling (find) — below the inference threshold — and appeared nowhere in the pinned ledger. Real protection that no ledger row named.It now declares the contract, entering discovery through #11626's declaration route:
import type { IDataEngine } from '@objectstack/spec/contracts'(plugin-auth already depends on@objectstack/spec).IDataEngine & MemoryEngineHandles, with the literal castas unknown as IDataEngine & MemoryEngineHandles.--write.On the spelling — the card predicted
as unknown as IDataEngine, matching both #11626 doubles. That is not right here: the tests readengine.rowsandengine.calls, so a bareIDataEnginereturn type would break those reads. The gate accepts an intersection, and its own docblock calls that "the commonest spelling in this repo — a double that adds test-only handles to the contract it implements". The intersection is what keeps this honest rather than padded: the contract is asserted, androws/callsstay declared as the test handles they are. No member was added or changed to satisfy discovery.The
deleteslice — measured, and emptyThe card left this unmeasured. The probe ran both slices:
The
deleteslice has no equivalent population on this tree — 294 doubles discovered at threshold zero, zero of them pinned-with-fewer-than-two-siblings. Nothing to fold in and nothing to file. On the update side, 7 pinned-low-sibling constructs, 2 of which already declareIDataEngine(the #11626 pair), leaving the card's 5.Verification
The gate before the ledger write, quoting its own verdict line:
After
--write(391 (file, verb) row(s), 1 added or grown, 0 lost— a single-row diff):That count moved 2 → 3: the double entered by the declaration route, as intended.
Reverse verification. With the annotation reverted from the committed state (mutation confirmed on disk by anchored greps — import absent, intersection absent, bare signature restored) the gate goes red, exit 1:
Restored via a
trap … EXIT INT TERM; tree confirmed byte-identical to the commit afterwards.Runtime inertness.
member-role-canonical.test.ts— 35/35 passing before and after, identical. The fourmetadata-protocolfiles are untouched; their 60/60 baseline is recorded for completeness.The type check that mattered.
pnpm --filter @objectstack/plugin-auth typecheckis green — but plugin-auth'stsconfig.jsonexcludes**/*.test.ts, so that run never read the edited file. That is exactly why the package carries aTEST_DEBTentry, recorded aterrors: 97with the note "no margin, so the next new error here goes red immediately". Measured through the gate's ownremeasureProjectshape (its tsconfig with the test exclusion dropped, written outside the repo, 175 test files in the program): 97 errors, zero of them in the edited file — the recorded count exactly, so the ratchet does not move.Gate union re-derived at the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack; the family list is identical to the pre-rebase derivation. All 14 matched families plus the convention-triggered ones green, each exit code captured before any pipe:check:agent-test-spelling·check:cross-package-test-inputs·check:entry-guard·check:parse-guard·check:pnpm-filter-targets·check:published-files·check:slot-lookup·check:test-source-alias·check:type-source-resolution·check-ci-filter-parity·check-cross-package-test-inputs·check-plugin-teardown-shape·check-affected-docs·check-drift-comment·check:query-options-erasure·check:type-check-coverage·check:where-matcher·check:engine-double-contract·check:nul-bytes·check-type-check-coverage --self-testDeclared narrowing:
check:type-check-debt's--re-measurehalf re-runs tsc for all ~40 ledger entries and requires the whole workspace built. The full-closure build could not get the shared verify lock (three agents queued;exit 99after a 9-minute wait). Narrowed with warrant: the ratchet compares per-entry counts, and this diff touches exactly one package, so onlyTEST_DEBT['@objectstack/plugin-auth']can move — and that entry was measured directly at 97, unchanged. The--self-testhalf ran in full and passes. CI runs the whole farm regardless.No changeset: test-only plus a repo ledger, nothing published —
skip-changeset.Generated by Claude Code
Generated by Claude Code