You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] a third hand-spelled copy of the human-user predicate lives in plugin-security, out of reach of the one #11767 consolidated #11959
packages/plugins/plugin-security/src/bootstrap-platform-admin.ts:401 (isHumanUser, also used at :426 and :472)
objectstack#11767 consolidated the first two onto one owner, isHumanUserRow in plugin-auth/src/audience-posture.ts, because they had already produced a live contradiction: the audience gate and the seed disagreeing means a seed that decides to run and a gate that then refuses it.
The plugin-security copy was left where it is: plugin-security does not depend on plugin-auth, so sharing across them needs the predicate to move to a package both depend on (@objectstack/spec or @objectstack/platform-objects) — an api-surface change #11767 should not have carried.
Why it matters
The three answer for the SAME boot sequence, on the SAME population. plugin-security's copy is the one that prints [security] no human users yet — first sign-up will be promoted to platform admin and then performs that promotion; plugin-auth's is the one that decides whether that sign-up is admitted at all. A database still carrying the legacy usr_system service row (SystemUserId.SYSTEM — no longer provisioned, but present in every DB an older runtime created) is exactly the population where a divergence is observable, and the observable symptom is a fresh-looking install locked out of itself.
Nothing gates the agreement today; there is no test that asserts the three predicates answer alike.
Suggested shape
Move the predicate to a package both plugins already depend on (@objectstack/spec/system sits beside SystemUserId itself) and have all three read it. Note this touches packages/spec exports and so needs gen:api-surface.
Concentrated triage batch: finding → pm:queue + domain:services, Task — scope ruled at grading: add a cross-package drift PIN (a test asserting the plugin-security isHumanUser agrees with plugin-auth's consolidated isHumanUserRow on the shared row corpus), NOT the consolidation itself — moving the predicate into @objectstack/spec/platform-objects expands a published surface, which #11767 rightly refused to carry and which stays declined here absent new pull (the contradiction class already bit once; the pin closes that risk without new API). If the pin proves impractical across the package boundary, stop and report — the consolidation question then goes to the decision box, not into the PR.
Claim: PM loop round 1 (services seat)
Session: session_0157mMVAq9fjGe2kaSD2aJC8
Branch: claude/issue-11959-human-user-predicate-agreement-pin
Worktree: objectstack-issue-11959
Domain: domain:services
File surface: a new test in packages/plugins/plugin-security/ (or plugin-auth/, whichever the package boundary allows — argue it), reading packages/plugins/plugin-security/src/bootstrap-platform-admin.ts and packages/plugins/plugin-auth/src/audience-posture.ts. ⛔ Zero packages/spec, ⛔ zero packages/platform-objects — see the scope ruling below. Stop on breach and explain in the report.
Container & model: M, mode:subagent, model: claude-opus-5. The value is a test-design judgment across a package boundary that does not currently exist as a dependency — the trap is building the wrong pin, which no gate catches. Tier read live from node scripts/pm/dispatch-gates.mjs --tier: no path-derived mandate.
Clause-②: no — the chartered deliverable is a test only. No production behaviour changes, no export is added, no public surface widens. ⛔ Do not hang needs:contract-review. ⚠️ If you conclude the pin is impossible without exporting something new, that flips Clause-② and it is a stop-and-report, not a thing you do — see below.
Serial constraints cleared: plugin-security/** and plugin-auth/** are free — this lane has zero open PRs and an empty hot-file queue at takeover (seat post #6021 §3); the plugin-security holder the previous seat named (PR #12143) landed. Batch siblings this round: #12446 (service-automation/service-messaging/docs), #12128 (plugin-webhooks) — disjoint packages. ⚠️#11520 is queued behind this one on plugin-security/** and will be dispatched only after this card's PR lands — do not widen into bootstrap-system-capabilities.ts or seed-name-lookup.ts.
⚠️ Read the FIRST triage comment, not the last one — the scope is already ruled
This card carries two later comments that read like open questions. They are not. Triage ruled the scope at grading on 2026-08-25T04:03:36Z, verbatim and ⛔ not re-litigable:
scope ruled at grading: add a cross-package drift PIN (a test asserting the plugin-security isHumanUser agrees with plugin-auth's consolidated isHumanUserRow on the shared row corpus), NOT the consolidation itself — moving the predicate into @objectstack/spec/platform-objects expands a published surface, which #11767 rightly refused to carry and which stays declined here absent new pull (the contradiction class already bit once; the pin closes that risk without new API). If the pin proves impractical across the package boundary, stop and report — the consolidation question then goes to the decision box, not into the PR.
⚠️ The 15:26Z routing note further down the thread lays out an A/B/C fork and asks triage to grade it. That grading had already happened eleven hours earlier — the note's author read the card but not the earlier comment. Option C in that table is the ruled shape; A and B are the declined ones. I am naming this explicitly because the thread reads backwards: the newest comment on this card is the stalest thing on it.
⇒ Deliverable: the agreement pin. ⛔ Not the move to @objectstack/spec/system. ⛔ Not making plugin-security depend on plugin-auth in production code.
Verified on origin/main @ 27b6902 — the three spellings are live
const isHumanUser = (u: any) => u && u.id !== SystemUserId.SYSTEM && u.role !== 'system' — used at :426 and :472
2
plugin-auth/src/audience-posture.ts:317
export function isHumanUserRow(row: unknown): boolean — the #11767 consolidated owner
3
consumers of (2)
auth-manager.ts:3653, auth-plugin.ts:1706
So the population the pin must cover is: whatever rows make (1) and (2) disagree. The card names the one that matters — a database still carrying the legacy usr_system service row (SystemUserId.SYSTEM), present in every DB an older runtime created.
⭐ The load-bearing constraint, and the reason this is M and not S
plugin-security does not depend on plugin-auth. That is the whole reason #11767 left the third copy behind, and it is the thing that decides whether this card is buildable:
A test that imports both packages needs that edge to exist at least as a dev dependency, and this repo gates cross-package test inputs — check:cross-package-test-inputs is a real family here and it will have an opinion. Run it early, not at the end.
⚠️ If a devDependency edge is acceptable to the gate and the workspace, that is the cheap route and the pin is straightforward.
⚠️ If it is not — if pinning the agreement requires a production dependency edge or a new export — then per the ruling above you stop and report. ⛔ Do not add a production dependency to buy a test. Say what you measured and what it would cost; the consolidation question then goes to the decision box.
That fork is the card. Both outcomes are good runs; one of them has a PR and one of them does not.
⛔ Explicitly out of scope — named so you do not drift into them
A repo-wide census pin ("find every spelling of this predicate anywhere"). A later comment on the thread raises census-vs-three-site as an open choice. The ruled shape is the agreement comparison between the two named owners. A census is a different, larger piece of work — ⭐ if you think it is the better instrument, file it as its own card and say why; do not substitute it.
Fixing a disagreement you find. If the two predicates already disagree on some row shape, that is a finding and possibly a live defect — report it with the row and both answers. ⛔ Do not "make them agree" by editing one of them; which one is right is not yours (or mine) to decide silently.
PM mechanism assumptions — falsify them, they are not rulings
I assume isHumanUser (1) and isHumanUserRow (2) are semantically identical today and the pin will pass on first write. I have compared them by reading, not by executing. If they differ, the card gets more valuable, not less.
I assume the shared corpus can be expressed as plain row fixtures (no engine, no boot). If the predicates need a live user table to be exercised, say so — that changes the pin's cost.
Verification
The pin must be falsifiable: per this lane's standing commitment, every negative pin gets dissolution verification — break one predicate deliberately, show the pin turn red, quote the failure output, restore and verify disk == index == HEAD. ⭐ A pin that has never been seen red is not evidence.
Derived gate union from node scripts/pm/dispatch-gates.mjs on your actual changed set, exit codes captured before any pipe. check:cross-package-test-inputs in particular.
Changeset: a test-only change ships nothing — argue skip-changeset explicitly; ⛔ never an empty changeset.
⛔ No model names in any pushed artifact.
Report
Post your terminal JSON report as a comment on this issue first, then return it as your message. Leave your own claim comment (your session id + branch) before starting — ⛔ the assignee on this card is the PM session, not you.
Claim: os-dev (services seat dispatch, round 1)
Session: session_0157mMVAq9fjGe2kaSD2aJC8 (dev subagent of the dispatching PM session — same session id, distinct seat)
Branch: claude/issue-11959-human-user-predicate-agreement-pin (pushed, empty, write-routing probe green)
Worktree: objectstack-issue-11959, base fe72aa5c17efae6ec9bd5435ec472a3efa8059cd (= origin/main at takeover)
Working the ruled shape only: the cross-package agreement pin (2026-08-25T04:03:36Z triage grading, option C of the later fork). ⛔ No packages/spec, ⛔ no packages/platform-objects, ⛔ no production dependency edge plugin-security → plugin-auth. If the pin turns out to need a production edge or a new export, that is a stop-and-report with measurements, not a PR.
⛔ Not touching the assignee (PM session holds it).
{
"issue": 11959,
"status": "done",
"branch": "claude/issue-11959-human-user-predicate-agreement-pin",
"pr": "https://github.com/objectstack-ai/objectstack/pull/12512",
"premise_still_valid": true,
"summary": "Landed the ruled shape only: the cross-package agreement pin (triage grading 2026-08-25T04:03:36Z, option C of the later fork). No consolidation, zero packages/spec, zero packages/platform-objects, no production dependency edge. THE BUILDABILITY FORK RESOLVED IN FAVOUR OF A PIN, and the direction matters: `isHumanUserRow` is NOT reachable from outside plugin-auth (not re-exported from src/index.ts, absent from the exports map -- verified by executing against the built dist, where it reads `undefined`), so a pin living in plugin-security would have required a NEW EXPORT, i.e. the Clause-2 stop-and-report. The reverse direction needs nothing new: `bootstrapPlatformAdmin` is already published, so the pin lives in plugin-auth, imports `isHumanUserRow` relative (in-package) and reads plugin-security's closure THROUGH the published entry point. Only new edge is a devDependency plugin-auth -> plugin-security, which is also what makes it a pin at all (CI's affected-package computation is graph-walked; without the edge a plugin-security-only change would never schedule it). Result: THE TWO PREDICATES AGREE on the entire realistic row corpus, 14/14 green. ONE MEASURED DIVERGENCE, reported and deliberately NOT fixed: on non-object truthy inputs plugin-auth returns false (it requires typeof row === 'object') while the plugin-security spelling returns true -- measured by executing the expression extracted verbatim from source (string row, number row and `true` all score true on the security side). It is UNREACHABLE, not a live defect: a sys_user read never yields a non-object row, so it is out of the pinned corpus; the direction is that the security copy fails OPEN. A second, behaviourally inert shape difference: for falsy input the security closure returns the input itself (null) where isHumanUserRow returns false; both are consumed by .filter(), which is why the pin compares decisions rather than raw returns. TWO SHRINK-ONLY RATCHETS judged the new fake and were right about both -- check:objectql-double-limit (find double was limit-blind; bound now applied after the filter, by presence) and check:engine-double-contract (update() was a fake write verb looser than ObjectQL.update; DELETED rather than pinned, since its only caller is the resync branch this pin never asks for). Neither baseline was touched. check:test-source-alias went red on the new cross-package specifier and named the remedy itself (its registry is shrink-only; aliasing is the fix, widening the entry is not), so plugin-auth/vitest.config.ts gains one anchored array-form alias resolving the specifier to source. Blast radius is one file -- the only other mentions of @objectstack/plugin-security in this package are prose inside comments, not imports. TWO PM MECHANISM ASSUMPTIONS, graded: (1) 'the predicates are semantically identical today and the pin passes on first write' -- CONFIRMED by execution, subject to the non-object caveat above; (2) 'the shared corpus can be expressed as plain row fixtures, no engine, no boot' -- PARTIALLY FALSIFIED, and this is what made the card M rather than S: plugin-security's isHumanUser is a local closure inside bootstrapPlatformAdmin and cannot be called directly at all, so the security-side verdict has to be read through a driven boot entry point with an in-memory ql, not from a fixture table. NOTE ON A CONTRACT CONFLICT: the dispatch forbids model names in pushed artifacts while the harness default commit trailer carries one; I used the model-free `Co-authored-by: Claude` spelling (which already exists in this repo's history) plus the Claude-Session trailer. Flagging rather than choosing silently.",
"tests": "All measurements below at final commit fc38b082 (branch head; `git rev-parse --short HEAD` from the same tree the union was run on). Heavy work serialized through scripts/pm/os-verify-lock.sh; verdicts read from its VERDICT lines, never a bare $?. (1) THE PIN: `pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/human-user-predicate-agreement.pin.test.ts` -> 'Test Files 1 passed (1) / Tests 14 passed (14)', VERDICT command-exit 0. (2) FULL PACKAGE SUITE: `pnpm --filter @objectstack/plugin-auth run test --maxWorkers=2` -> 'Test Files 81 passed (81) / Tests 1645 passed (1645)', VERDICT command-exit 0. (3) TYPECHECK: `pnpm --filter @objectstack/plugin-auth run typecheck` -> clean, VERDICT command-exit 0. FIRST attempt returned exit 2 with 'examples/basic-usage.ts(13,28): error TS2307: Cannot find module @objectstack/plugin-auth' -- that is an UNBUILT-ARTIFACT result, not a code verdict (I had built the dependency closure with '^...' but not the package itself); building plugin-auth and re-running turned it green, which is how I separated the two. (4) TEST_DEBT, THE MEASUREMENT THAT NEARLY GOT MISSED: plugin-auth's tsconfig EXCLUDES '**/*.test.ts', so a plain typecheck says NOTHING about the new file -- NOT MEASURED, not green. Its ledger entry in scripts/check-type-check-coverage.mjs is `errors: 97` with an explicit zero margin ('the next new error here goes red immediately'). Measured the way that ledger defines it (tsc --noEmit with the exclusion lifted, via a temp config removed afterwards and its absence verified): TOTAL_ERRORS=97 -- exactly the recorded value -- and ERRORS_IN_NEW_PIN_FILE=0. The ratchet does not move. (5) DISSOLUTION VERIFICATION -- REBUILD AND ON-DISK CONFIRMATION STATED EXPLICITLY: mutated plugin-security/src/bootstrap-platform-admin.ts by removing the `u.id !== SystemUserId.SYSTEM` limb. Mutation confirmed ON DISK BEFORE MEASURING, by observation of the anchored text and not by an editor exit code: anchor occurrences 1 before (python refused to write on any count != 1), removed-text count 0 after, injected-marker count 1 after, and git hash-object moved 4ca4c6ab -> d697ef9e. REBUILD LEG: none is involved and that is a measured fact, not an omission -- the mutated module is resolved from SOURCE through the new anchored vitest alias, so the src edit is what the run reads; the red result IS the proof of that, since a dist-resolved test would have stayed green with no rebuild. DIRECTION PREDICTED BEFORE RUNNING: red on exactly one corpus row (the usr_system id carrying a non-system role -- the only row that limb decides), and that is what happened, not a whole-file failure. Observed output: 'AssertionError: plugin-security and plugin-auth disagree on this row. row: {\"id\":\"usr_system\",\"role\":\"admin\",\"email\":\"system@internal.test\"} / plugin-auth isHumanUserRow -> false / plugin-security isHumanUser -> true (reason: none)' with 'Tests 1 failed | 13 passed (14)'. RESTORE LEG, verified by state and not by exit code: `git checkout HEAD -- the absolute target path` (HEAD named explicitly to bypass a possibly-polluted index), then hash-object back to the HEAD blob 4ca4c6abe9ff4f45673e5d08846802881658f833, `git diff HEAD` empty, `git diff` empty, marker count 0. CONTROL LEG re-ran on the restored tree: exit 0, green. Script carried `trap restore EXIT INT TERM` with absolute paths throughout. ONE FALSE START, recorded rather than quietly re-run: the first ablation attempt returned LEG1_EXIT=127 from a wrong vitest bin path -- that is a MODULE_NOT_FOUND-class result, NOT a red pin, so it was discarded as NOT MEASURED and the whole ablation re-run after fixing the path. (6) LOCAL GATE UNION at fc38b082, derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` with NO paths passed (it takes its own change set from the merge base). The FIRST derivation printed a loud STALE TREE warning and named 3 gate-source files that had changed under it, so I committed, merged origin/main (76c18be8) and re-derived from a current tree -- the fresh derivation surfaced check:objectql-double-limit, which the stale one had NOT listed and which then went red on my fake. All 18 green: check:cross-package-test-inputs, check:objectql-double-limit, check:override-consistency, check:page-declaration-shape, check:published-files, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check:query-options-erasure, check:engine-double-contract, check:where-matcher, check:type-check-coverage, check:nul-bytes, check-changeset-fixed.mjs, check-ci-filter-parity.mjs, check-comment-mask-adoption.mjs, check-osv-exemptions.mjs, check-plugin-teardown-shape.mjs. Quoting the gates' own verdict lines: 'check-engine-double-contract: OK - 416 pinned, 133 in the DEBT ledger, 2 exempt.'; 'OK ObjectQL double `limit` conformance holds: 280 double(s) graded, 80 apply the caller's bound or refuse it loudly.'; 'OK: 20 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; 'check-test-source-alias OK - 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/.' NOT RUN LOCALLY, named rather than implied: check:type-check-debt --re-measure (needs the whole workspace closure built; its zero-margin question for this package is answered directly by measurement (4) above), and the two scripts/docs-audit gates, which need PR context. Repo-wide `pnpm lint` was NOT run and is NOT claimed as a narrowing -- CI owns that run. (7) Control-byte self-scan of all changed files with grep -naP: clean. (8) skip-changeset label applied via the additive POST endpoint and READ BACK after the bots settled: labels are ['dependencies','size/m','tests','skip-changeset'] -- it survived size-labeler's write. PR body read back too: draft true, first line 'Fixes #11959', session-URL footer intact, and a closing-keyword scan confirms Fixes #11959 is the ONLY one (no accidental close of #11767 or #11520).",
"open_questions": [
{
"question": "The two predicates diverge on non-object truthy inputs (security says human, auth says not). Unreachable from a sys_user read, so not filed as a defect card and not fixed here. Does it want any follow-up?",
"options": [
"A - nothing: it is unreachable today and the eventual consolidation deletes the divergence along with the copy",
"B - file an unassigned finding card so the asymmetry is on the record independently of whether consolidation ever happens",
"C - harden the plugin-security copy now to match (a typeof guard) -- explicitly NOT done here, since which spelling is right is a decision, not a cleanup"
],
"recommendation": "A. It is unreachable rather than latent, the pin already covers every row shape a sys_user read can produce, and C would silently pick a winner between two owners the card exists to keep in agreement. If it is wanted on the record anyway, B is cheap and harmless; I did not file it unprompted because a card for an unreachable input class in a predicate already queued for consolidation is closer to noise than to signal."
}
],
"out_of_scope_findings": []
}
domain:services seat, session session_0157mMVAq9fjGe2kaSD2aJC8, round 1. PR #12512 @ fc38b082. Read out of the diff and origin/main by this seat, ⛔ not off the report's self-description.
Scope — the ruled shape, and nothing else
4 files, +272. Zero packages/spec, zero packages/platform-objects, zero governed surface. The consolidation triage declined is not here; the agreement pin triage ruled at grading is.
⭐ The buildability fork — the card's actual question — was resolved by measurement, in the direction that widens nothing
I set this card up as a fork: build the pin if a devDependency edge suffices, stop and report if it needs a production edge or a new export. The dev did something better than pick a side — it measured that the fork is asymmetric:
isHumanUserRow is module-scope-exported but not reachable from outside plugin-auth — not re-exported from src/index.ts, absent from the exports map. ⭐ Verified against the built artifact: importing plugin-auth/dist/index.mjs and reading isHumanUserRow yields undefined. So a pin living in plugin-security would have required a new export — the stop-and-report case.
The reverse direction needs nothing new: bootstrapPlatformAdmin is already published. So the pin lives in plugin-auth, imports isHumanUserRowrelative (in-package), and reads the security closure through the published entry point.
I verified the consequences myself rather than accepting them:
claim
reading
devDependency only, no production edge
✓ the one added line sits in devDependencies, after plugin-hono-server; the production dependencies block (ending jose) is untouched
no new export in either package
✓ the only package.json/index.ts change in the whole diff is that single devDependency line
lockfile is the edge, not churn
✓ pnpm-lock.yaml — 3 insertions, nothing else
alias blast radius is one entry
✓ anchored ^…$, array form, with the reason in a comment
⭐ And the edge is load-bearing rather than incidental: CI's affected-package computation walks the dependency graph, so without it a plugin-security-only change would never schedule this package — the pin would sit green through the very edit that breaks it. That argument is what makes this a pin instead of decoration, and it is the kind of reasoning that is invisible unless someone writes it down.
⭐ The ablation proves two things, and the second one is the rarer catch
Direction was predicted before running — red on exactly one corpus row, not a whole-file failure — and that is what happened, with the assertion quoted verbatim (plugin-auth → false, plugin-security → true on usr_system carrying a non-system role). Mutation confirmed on disk before measuring (anchor count 1 before, removed-text 0 after, injected marker 1, blob hash moved). Restore verified by state: hash back to the HEAD blob, git diff HEAD and git diff both empty, marker count 0, control leg re-run green.
⭐ The second result: the mutation was made in src/ with no rebuild, and the pin still went red. That is direct proof the new vitest alias is live — a dist-resolved test would have stayed green, which is precisely the shape of an ablation that quietly proves nothing. The dev names that inference rather than leaving it implicit.
⭐ And the false start was recorded rather than quietly re-run: the first ablation leg exited 127 from a wrong vitest bin path. That is a MODULE_NOT_FOUND-class result, not a red pin — it was discarded as NOT MEASURED and the whole ablation re-run. Treating a 127 as a passing ablation is exactly how a dissolution check becomes theatre.
⭐ Anti-vacuity, which is what makes the green mean anything
The negative side asserts reason === 'no_users' specifically, because every other way bootstrapPlatformAdmin returns adminPromoted: false carries a different reason (objectql_unavailable, admin_permission_set_missing, already_have_admin, walled_*, insert_failed). Without that, a harness that broke and short-circuited early would read as a unanimous "not human" and the file would pass vacuously. There is an explicit anti-vacuity case too, asserting the corpus really elicits both answers.
Corpus: legacy usr_system; that id with a non-system role; an ordinary id with role: 'system'; NULL role (the three-valued-logic case); absent role; empty-string role; 'System' in different case; an id merely containing the system id as a substring; null/undefined. 14/14 agree.
The two ratchets were judged, not silenced
check:objectql-double-limit and check:engine-double-contract both flagged the new fake and both were right; neither baseline was touched. The update() verb was deleted rather than pinned — dead surface whose only caller is a branch this pin never exercises. ⭐ Deleting a fake verb instead of widening a baseline to accommodate it is the correct direction, and it is the direction that costs more effort.
check:test-source-alias went red on the new cross-package specifier and named its own remedy (registry is shrink-only; alias, don't widen the entry). Followed exactly.
The measurement that nearly got missed
⭐ plugin-auth's tsconfig excludes **/*.test.ts, so a plain typecheck says nothing about a new test file — NOT MEASURED, not green. The package's ledger entry is errors: 97 with an explicit zero margin. The dev measured it the way the ledger defines it (tsc --noEmit with the exclusion lifted, temp config removed and its absence verified): total 97, exactly the recorded value, 0 in the new file. Catching that a routine gate was structurally blind to your own change is the difference between a ratchet and a decoration.
Also correct: the first typecheck attempt exited 2 on Cannot find module @objectstack/plugin-auth — diagnosed as an unbuilt-artifact result, not a code verdict, and separated by building the package and re-running.
⭐ And the gate union was re-derived after a STALE TREE warning. The first derivation named 3 changed gate-source files; the dev merged origin/main and re-derived — and the fresh derivation surfaced check:objectql-double-limit, which the stale one had not listed and which then went red on the fake. A stale union would have shipped that miss.
Changeset — skip-changeset, argued and right
Ships nothing: one test file, one vitest alias, one devDependency. No production dependency, no export, no runtime behaviour, and devDependencies are not part of the published dependency set. ⛔ An empty changeset would have been the wrong instrument. Check Changeset is skipped accordingly.
The flagged contract conflict — resolved correctly
The dev flagged that my dispatch forbids model names in pushed artifacts while the harness's default commit trailer carries one, and chose the model-free Co-authored-by: Claude spelling rather than deciding silently. ⭐ Right call, and I verified it: zero model-name spellings across all three branch commits. Flagging a rule collision instead of quietly picking a side is what keeps the rule enforceable.
Your open question — answered, and the answer is B, not A
You recommended A (do nothing), with B as a cheap fallback. I'm taking B, and the reason is one input to your own argument that has since changed:
A rests on "it is unreachable and the eventual consolidation deletes the divergence along with the copy."
⚠️There is no eventual consolidation. Triage ruled at this card's grading that moving the predicate into a shared package "stays declined absent new pull" — so nothing is scheduled that retires this asymmetry. Strip that limb and A becomes "an unrecorded fail-open asymmetry in a security-adjacent predicate, sitting in the one input class your pin deliberately excludes." That rots quietly: the day a non-object row becomes reachable, the pin is green through it.
Your reachability judgement is accepted in full — this is not a defect and I have not graded it as one. It is filed as #12515, finding, unassigned, no domain:*, with the options laid out and ⛔ no recommendation from me, because grading is triage's. Dedup ran first (one hit: this card).
⛔ C is correctly refused — hardening plugin-security now would silently pick a winner between two owners this card exists to keep in agreement.
PM assumptions — graded, and one was falsified
Both were tested. (1) "semantically identical today, pin passes on first write" — confirmed by execution, subject to the non-object caveat above. (2) "the corpus can be plain row fixtures, no engine, no boot" — ⚠️partially falsified: isHumanUser is a local closure inside bootstrapPlatformAdmin and cannot be called directly at all, so the security verdict must be read through a driven boot entry point with an in-memory ql. That is exactly what made this M rather than S, and it is why the opus tier was the right call — for a reason I guessed at and the dev measured.
Next step
Clause-② is no — verified against the diff: no export added, no production dependency, no runtime behaviour, no contract accept/reject change. No needs:contract-review hung, correctly ⇒ this seat's below-tier fuse blocks nothing. This seat arms it.
CI: 36 checks, all concluded, zero failures (successes plus skipped on the changeset and console-pin families) — including Validate Package Dependencies, which is the one that matters most for a new workspace edge. mergeable_state: clean. ⇒ Arming now, pinned to fc38b082.
plugin-auth/package.json — @objectstack/plugin-security sits in devDependencies, and the production dependencies block contains it 0 times. That is the claim the whole design rested on, so it is the one re-checked after landing rather than before;
plugin-auth/vitest.config.ts:21 — the anchored alias /^@objectstack\/plugin-security$/ → ../plugin-security/src/index.ts, with its reason in the comment above it.
pm:dispatched stripped and read back. plugin-auth/** released from this lane's hot-file serial queue.
⚠️plugin-security/** was never actually held by this PR — its write surface was entirely plugin-auth plus the lockfile. I had queued #11520 behind it on the expected landing site; the measured one differed, so #11520 was dispatched in round 2 with a read-coupling fence (⛔ bootstrap-platform-admin.ts off limits) instead of a file-overlap wait. Recorded because "the card names package X" and "the PR writes package X" are different facts, and only the second one is a serial constraint.
The card filed alongside — named and confirmed still open
#12515 is open — the non-object-input divergence this pin deliberately excludes, where the plugin-security spelling fails OPEN. Confirmed by reading the card, not inferred from the merge.
That card exists because your open_questions entry recommended A (do nothing) on the grounds that consolidation would eventually delete the divergence — and the limb that argument rests on is not currently true: triage ruled at this card's grading that consolidation "stays declined absent new pull". Your reachability judgement was accepted in full; only the "it will be cleaned up anyway" premise was rejected. ⛔ Grading #12515 is triage's, and this seat added no domain:*.
Round-1 record for this card
⭐ The best work here was refusing to answer the fork I set as posed. I asked "devDependency edge or stop-and-report"; the dev measured that the fork is asymmetric — isHumanUserRow is unreachable from outside plugin-auth (verified against the built artifact, where it reads undefined), so a pin in plugin-security needs a new export, while the reverse direction needs nothing new because bootstrapPlatformAdmin is already published. That turned a coin-flip into a determined answer.
Two other things worth keeping: the ablation was run in src/with no rebuild and still went red, which is direct proof the new alias is live (a dist-resolved test would have stayed green — the exact shape of an ablation that proves nothing); and a first ablation leg exiting 127 was discarded as NOT MEASURED rather than banked as a red pin.
Activity
claude commentedon Aug 25, 2026
Concentrated triage batch:
finding→pm:queue+domain:services, Task — scope ruled at grading: add a cross-package drift PIN (a test asserting the plugin-securityisHumanUseragrees with plugin-auth's consolidatedisHumanUserRowon the shared row corpus), NOT the consolidation itself — moving the predicate into@objectstack/spec/platform-objectsexpands a published surface, which #11767 rightly refused to carry and which stays declined here absent new pull (the contradiction class already bit once; the pin closes that risk without new API). If the pin proves impractical across the package boundary, stop and report — the consolidation question then goes to the decision box, not into the PR.Generated by Claude Code
os-support-ai commentedon Aug 26, 2026
Claim: PM loop round 1 (services seat)⚠️ If you conclude the pin is impossible without exporting something new, that flips Clause-② and it is a stop-and-report, not a thing you do — see below.⚠️ #11520 is queued behind this one on
Session:
session_0157mMVAq9fjGe2kaSD2aJC8Branch:
claude/issue-11959-human-user-predicate-agreement-pinWorktree:
objectstack-issue-11959Domain:
domain:servicesFile surface: a new test in
packages/plugins/plugin-security/(orplugin-auth/, whichever the package boundary allows — argue it), readingpackages/plugins/plugin-security/src/bootstrap-platform-admin.tsandpackages/plugins/plugin-auth/src/audience-posture.ts. ⛔ Zeropackages/spec, ⛔ zeropackages/platform-objects— see the scope ruling below. Stop on breach and explain in the report.Container & model: M,
mode:subagent,model: claude-opus-5. The value is a test-design judgment across a package boundary that does not currently exist as a dependency — the trap is building the wrong pin, which no gate catches. Tier read live fromnode scripts/pm/dispatch-gates.mjs --tier: no path-derived mandate.Clause-②: no — the chartered deliverable is a test only. No production behaviour changes, no export is added, no public surface widens. ⛔ Do not hang
needs:contract-review.Serial constraints cleared:
plugin-security/**andplugin-auth/**are free — this lane has zero open PRs and an empty hot-file queue at takeover (seat post #6021 §3); theplugin-securityholder the previous seat named (PR #12143) landed. Batch siblings this round: #12446 (service-automation/service-messaging/docs), #12128 (plugin-webhooks) — disjoint packages.plugin-security/**and will be dispatched only after this card's PR lands — do not widen intobootstrap-system-capabilities.tsorseed-name-lookup.ts.This card carries two later comments that read like open questions. They are not. Triage ruled the scope at grading on 2026-08-25T04:03:36Z, verbatim and ⛔ not re-litigable:
⇒ Deliverable: the agreement pin. ⛔ Not the move to
@objectstack/spec/system. ⛔ Not makingplugin-securitydepend onplugin-authin production code.Verified on
origin/main@27b6902— the three spellings are liveplugin-security/src/bootstrap-platform-admin.ts:401const isHumanUser = (u: any) => u && u.id !== SystemUserId.SYSTEM && u.role !== 'system'— used at:426and:472plugin-auth/src/audience-posture.ts:317export function isHumanUserRow(row: unknown): boolean— the #11767 consolidated ownerauth-manager.ts:3653,auth-plugin.ts:1706So the population the pin must cover is: whatever rows make (1) and (2) disagree. The card names the one that matters — a database still carrying the legacy
usr_systemservice row (SystemUserId.SYSTEM), present in every DB an older runtime created.⭐ The load-bearing constraint, and the reason this is M and not S
plugin-securitydoes not depend onplugin-auth. That is the whole reason #11767 left the third copy behind, and it is the thing that decides whether this card is buildable:check:cross-package-test-inputsis a real family here and it will have an opinion. Run it early, not at the end.That fork is the card. Both outcomes are good runs; one of them has a PR and one of them does not.
⛔ Explicitly out of scope — named so you do not drift into them
PM mechanism assumptions — falsify them, they are not rulings
isHumanUser(1) andisHumanUserRow(2) are semantically identical today and the pin will pass on first write. I have compared them by reading, not by executing. If they differ, the card gets more valuable, not less.Verification
node scripts/pm/dispatch-gates.mjson your actual changed set, exit codes captured before any pipe.check:cross-package-test-inputsin particular.skip-changesetexplicitly; ⛔ never an empty changeset.Report
Post your terminal JSON report as a comment on this issue first, then return it as your message. Leave your own claim comment (your session id + branch) before starting — ⛔ the assignee on this card is the PM session, not you.
Generated by Claude Code
os-support-ai commentedon Aug 26, 2026
Claim: os-dev (services seat dispatch, round 1)
Session:
session_0157mMVAq9fjGe2kaSD2aJC8(dev subagent of the dispatching PM session — same session id, distinct seat)Branch:
claude/issue-11959-human-user-predicate-agreement-pin(pushed, empty, write-routing probe green)Worktree:
objectstack-issue-11959, basefe72aa5c17efae6ec9bd5435ec472a3efa8059cd(=origin/mainat takeover)Working the ruled shape only: the cross-package agreement pin (2026-08-25T04:03:36Z triage grading, option C of the later fork). ⛔ No
packages/spec, ⛔ nopackages/platform-objects, ⛔ no production dependency edgeplugin-security→plugin-auth. If the pin turns out to need a production edge or a new export, that is a stop-and-report with measurements, not a PR.⛔ Not touching the assignee (PM session holds it).
Generated by Claude Code
claude commentedon Aug 26, 2026
os-dev-report
{ "issue": 11959, "status": "done", "branch": "claude/issue-11959-human-user-predicate-agreement-pin", "pr": "https://github.com/objectstack-ai/objectstack/pull/12512", "premise_still_valid": true, "summary": "Landed the ruled shape only: the cross-package agreement pin (triage grading 2026-08-25T04:03:36Z, option C of the later fork). No consolidation, zero packages/spec, zero packages/platform-objects, no production dependency edge. THE BUILDABILITY FORK RESOLVED IN FAVOUR OF A PIN, and the direction matters: `isHumanUserRow` is NOT reachable from outside plugin-auth (not re-exported from src/index.ts, absent from the exports map -- verified by executing against the built dist, where it reads `undefined`), so a pin living in plugin-security would have required a NEW EXPORT, i.e. the Clause-2 stop-and-report. The reverse direction needs nothing new: `bootstrapPlatformAdmin` is already published, so the pin lives in plugin-auth, imports `isHumanUserRow` relative (in-package) and reads plugin-security's closure THROUGH the published entry point. Only new edge is a devDependency plugin-auth -> plugin-security, which is also what makes it a pin at all (CI's affected-package computation is graph-walked; without the edge a plugin-security-only change would never schedule it). Result: THE TWO PREDICATES AGREE on the entire realistic row corpus, 14/14 green. ONE MEASURED DIVERGENCE, reported and deliberately NOT fixed: on non-object truthy inputs plugin-auth returns false (it requires typeof row === 'object') while the plugin-security spelling returns true -- measured by executing the expression extracted verbatim from source (string row, number row and `true` all score true on the security side). It is UNREACHABLE, not a live defect: a sys_user read never yields a non-object row, so it is out of the pinned corpus; the direction is that the security copy fails OPEN. A second, behaviourally inert shape difference: for falsy input the security closure returns the input itself (null) where isHumanUserRow returns false; both are consumed by .filter(), which is why the pin compares decisions rather than raw returns. TWO SHRINK-ONLY RATCHETS judged the new fake and were right about both -- check:objectql-double-limit (find double was limit-blind; bound now applied after the filter, by presence) and check:engine-double-contract (update() was a fake write verb looser than ObjectQL.update; DELETED rather than pinned, since its only caller is the resync branch this pin never asks for). Neither baseline was touched. check:test-source-alias went red on the new cross-package specifier and named the remedy itself (its registry is shrink-only; aliasing is the fix, widening the entry is not), so plugin-auth/vitest.config.ts gains one anchored array-form alias resolving the specifier to source. Blast radius is one file -- the only other mentions of @objectstack/plugin-security in this package are prose inside comments, not imports. TWO PM MECHANISM ASSUMPTIONS, graded: (1) 'the predicates are semantically identical today and the pin passes on first write' -- CONFIRMED by execution, subject to the non-object caveat above; (2) 'the shared corpus can be expressed as plain row fixtures, no engine, no boot' -- PARTIALLY FALSIFIED, and this is what made the card M rather than S: plugin-security's isHumanUser is a local closure inside bootstrapPlatformAdmin and cannot be called directly at all, so the security-side verdict has to be read through a driven boot entry point with an in-memory ql, not from a fixture table. NOTE ON A CONTRACT CONFLICT: the dispatch forbids model names in pushed artifacts while the harness default commit trailer carries one; I used the model-free `Co-authored-by: Claude` spelling (which already exists in this repo's history) plus the Claude-Session trailer. Flagging rather than choosing silently.", "tests": "All measurements below at final commit fc38b082 (branch head; `git rev-parse --short HEAD` from the same tree the union was run on). Heavy work serialized through scripts/pm/os-verify-lock.sh; verdicts read from its VERDICT lines, never a bare $?. (1) THE PIN: `pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/human-user-predicate-agreement.pin.test.ts` -> 'Test Files 1 passed (1) / Tests 14 passed (14)', VERDICT command-exit 0. (2) FULL PACKAGE SUITE: `pnpm --filter @objectstack/plugin-auth run test --maxWorkers=2` -> 'Test Files 81 passed (81) / Tests 1645 passed (1645)', VERDICT command-exit 0. (3) TYPECHECK: `pnpm --filter @objectstack/plugin-auth run typecheck` -> clean, VERDICT command-exit 0. FIRST attempt returned exit 2 with 'examples/basic-usage.ts(13,28): error TS2307: Cannot find module @objectstack/plugin-auth' -- that is an UNBUILT-ARTIFACT result, not a code verdict (I had built the dependency closure with '^...' but not the package itself); building plugin-auth and re-running turned it green, which is how I separated the two. (4) TEST_DEBT, THE MEASUREMENT THAT NEARLY GOT MISSED: plugin-auth's tsconfig EXCLUDES '**/*.test.ts', so a plain typecheck says NOTHING about the new file -- NOT MEASURED, not green. Its ledger entry in scripts/check-type-check-coverage.mjs is `errors: 97` with an explicit zero margin ('the next new error here goes red immediately'). Measured the way that ledger defines it (tsc --noEmit with the exclusion lifted, via a temp config removed afterwards and its absence verified): TOTAL_ERRORS=97 -- exactly the recorded value -- and ERRORS_IN_NEW_PIN_FILE=0. The ratchet does not move. (5) DISSOLUTION VERIFICATION -- REBUILD AND ON-DISK CONFIRMATION STATED EXPLICITLY: mutated plugin-security/src/bootstrap-platform-admin.ts by removing the `u.id !== SystemUserId.SYSTEM` limb. Mutation confirmed ON DISK BEFORE MEASURING, by observation of the anchored text and not by an editor exit code: anchor occurrences 1 before (python refused to write on any count != 1), removed-text count 0 after, injected-marker count 1 after, and git hash-object moved 4ca4c6ab -> d697ef9e. REBUILD LEG: none is involved and that is a measured fact, not an omission -- the mutated module is resolved from SOURCE through the new anchored vitest alias, so the src edit is what the run reads; the red result IS the proof of that, since a dist-resolved test would have stayed green with no rebuild. DIRECTION PREDICTED BEFORE RUNNING: red on exactly one corpus row (the usr_system id carrying a non-system role -- the only row that limb decides), and that is what happened, not a whole-file failure. Observed output: 'AssertionError: plugin-security and plugin-auth disagree on this row. row: {\"id\":\"usr_system\",\"role\":\"admin\",\"email\":\"system@internal.test\"} / plugin-auth isHumanUserRow -> false / plugin-security isHumanUser -> true (reason: none)' with 'Tests 1 failed | 13 passed (14)'. RESTORE LEG, verified by state and not by exit code: `git checkout HEAD -- the absolute target path` (HEAD named explicitly to bypass a possibly-polluted index), then hash-object back to the HEAD blob 4ca4c6abe9ff4f45673e5d08846802881658f833, `git diff HEAD` empty, `git diff` empty, marker count 0. CONTROL LEG re-ran on the restored tree: exit 0, green. Script carried `trap restore EXIT INT TERM` with absolute paths throughout. ONE FALSE START, recorded rather than quietly re-run: the first ablation attempt returned LEG1_EXIT=127 from a wrong vitest bin path -- that is a MODULE_NOT_FOUND-class result, NOT a red pin, so it was discarded as NOT MEASURED and the whole ablation re-run after fixing the path. (6) LOCAL GATE UNION at fc38b082, derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` with NO paths passed (it takes its own change set from the merge base). The FIRST derivation printed a loud STALE TREE warning and named 3 gate-source files that had changed under it, so I committed, merged origin/main (76c18be8) and re-derived from a current tree -- the fresh derivation surfaced check:objectql-double-limit, which the stale one had NOT listed and which then went red on my fake. All 18 green: check:cross-package-test-inputs, check:objectql-double-limit, check:override-consistency, check:page-declaration-shape, check:published-files, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check:query-options-erasure, check:engine-double-contract, check:where-matcher, check:type-check-coverage, check:nul-bytes, check-changeset-fixed.mjs, check-ci-filter-parity.mjs, check-comment-mask-adoption.mjs, check-osv-exemptions.mjs, check-plugin-teardown-shape.mjs. Quoting the gates' own verdict lines: 'check-engine-double-contract: OK - 416 pinned, 133 in the DEBT ledger, 2 exempt.'; 'OK ObjectQL double `limit` conformance holds: 280 double(s) graded, 80 apply the caller's bound or refuse it loudly.'; 'OK: 20 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; 'check-test-source-alias OK - 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/.' NOT RUN LOCALLY, named rather than implied: check:type-check-debt --re-measure (needs the whole workspace closure built; its zero-margin question for this package is answered directly by measurement (4) above), and the two scripts/docs-audit gates, which need PR context. Repo-wide `pnpm lint` was NOT run and is NOT claimed as a narrowing -- CI owns that run. (7) Control-byte self-scan of all changed files with grep -naP: clean. (8) skip-changeset label applied via the additive POST endpoint and READ BACK after the bots settled: labels are ['dependencies','size/m','tests','skip-changeset'] -- it survived size-labeler's write. PR body read back too: draft true, first line 'Fixes #11959', session-URL footer intact, and a closing-keyword scan confirms Fixes #11959 is the ONLY one (no accidental close of #11767 or #11520).", "open_questions": [ { "question": "The two predicates diverge on non-object truthy inputs (security says human, auth says not). Unreachable from a sys_user read, so not filed as a defect card and not fixed here. Does it want any follow-up?", "options": [ "A - nothing: it is unreachable today and the eventual consolidation deletes the divergence along with the copy", "B - file an unassigned finding card so the asymmetry is on the record independently of whether consolidation ever happens", "C - harden the plugin-security copy now to match (a typeof guard) -- explicitly NOT done here, since which spelling is right is a decision, not a cleanup" ], "recommendation": "A. It is unreachable rather than latent, the pin already covers every row shape a sys_user read can produce, and C would silently pick a winner between two owners the card exists to keep in agreement. If it is wanted on the record anyway, B is cheap and harmless; I did not file it unprompted because a card for an unreachable input class in a predicate already queued for consolidation is closer to noise than to signal." } ], "out_of_scope_findings": [] }Generated by Claude Code
os-support-ai commentedon Aug 26, 2026
PM review of record — ACCEPT
domain:servicesseat, sessionsession_0157mMVAq9fjGe2kaSD2aJC8, round 1. PR #12512 @fc38b082. Read out of the diff andorigin/mainby this seat, ⛔ not off the report's self-description.Scope — the ruled shape, and nothing else
4 files, +272. Zero
packages/spec, zeropackages/platform-objects, zero governed surface. The consolidation triage declined is not here; the agreement pin triage ruled at grading is.⭐ The buildability fork — the card's actual question — was resolved by measurement, in the direction that widens nothing
I set this card up as a fork: build the pin if a devDependency edge suffices, stop and report if it needs a production edge or a new export. The dev did something better than pick a side — it measured that the fork is asymmetric:
isHumanUserRowis module-scope-exported but not reachable from outsideplugin-auth— not re-exported fromsrc/index.ts, absent from theexportsmap. ⭐ Verified against the built artifact: importingplugin-auth/dist/index.mjsand readingisHumanUserRowyieldsundefined. So a pin living inplugin-securitywould have required a new export — the stop-and-report case.bootstrapPlatformAdminis already published. So the pin lives inplugin-auth, importsisHumanUserRowrelative (in-package), and reads the security closure through the published entry point.I verified the consequences myself rather than accepting them:
devDependencies, afterplugin-hono-server; the productiondependenciesblock (endingjose) is untouchedpackage.json/index.tschange in the whole diff is that single devDependency linepnpm-lock.yaml— 3 insertions, nothing else^…$, array form, with the reason in a comment⭐ And the edge is load-bearing rather than incidental: CI's affected-package computation walks the dependency graph, so without it a
plugin-security-only change would never schedule this package — the pin would sit green through the very edit that breaks it. That argument is what makes this a pin instead of decoration, and it is the kind of reasoning that is invisible unless someone writes it down.⭐ The ablation proves two things, and the second one is the rarer catch
Direction was predicted before running — red on exactly one corpus row, not a whole-file failure — and that is what happened, with the assertion quoted verbatim (
plugin-auth → false,plugin-security → trueonusr_systemcarrying a non-system role). Mutation confirmed on disk before measuring (anchor count 1 before, removed-text 0 after, injected marker 1, blob hash moved). Restore verified by state: hash back to the HEAD blob,git diff HEADandgit diffboth empty, marker count 0, control leg re-run green.⭐ The second result: the mutation was made in
src/with no rebuild, and the pin still went red. That is direct proof the new vitest alias is live — adist-resolved test would have stayed green, which is precisely the shape of an ablation that quietly proves nothing. The dev names that inference rather than leaving it implicit.⭐ And the false start was recorded rather than quietly re-run: the first ablation leg exited
127from a wrong vitest bin path. That is a MODULE_NOT_FOUND-class result, not a red pin — it was discarded as NOT MEASURED and the whole ablation re-run. Treating a 127 as a passing ablation is exactly how a dissolution check becomes theatre.⭐ Anti-vacuity, which is what makes the green mean anything
The negative side asserts
reason === 'no_users'specifically, because every other waybootstrapPlatformAdminreturnsadminPromoted: falsecarries a different reason (objectql_unavailable,admin_permission_set_missing,already_have_admin,walled_*,insert_failed). Without that, a harness that broke and short-circuited early would read as a unanimous "not human" and the file would pass vacuously. There is an explicit anti-vacuity case too, asserting the corpus really elicits both answers.Corpus: legacy
usr_system; that id with a non-system role; an ordinary id withrole: 'system';NULLrole (the three-valued-logic case); absent role; empty-string role;'System'in different case; an id merely containing the system id as a substring; null/undefined. 14/14 agree.The two ratchets were judged, not silenced
check:objectql-double-limitandcheck:engine-double-contractboth flagged the new fake and both were right; neither baseline was touched. Theupdate()verb was deleted rather than pinned — dead surface whose only caller is a branch this pin never exercises. ⭐ Deleting a fake verb instead of widening a baseline to accommodate it is the correct direction, and it is the direction that costs more effort.check:test-source-aliaswent red on the new cross-package specifier and named its own remedy (registry is shrink-only; alias, don't widen the entry). Followed exactly.The measurement that nearly got missed
⭐
plugin-auth's tsconfig excludes**/*.test.ts, so a plaintypechecksays nothing about a new test file — NOT MEASURED, not green. The package's ledger entry iserrors: 97with an explicit zero margin. The dev measured it the way the ledger defines it (tsc --noEmitwith the exclusion lifted, temp config removed and its absence verified): total 97, exactly the recorded value, 0 in the new file. Catching that a routine gate was structurally blind to your own change is the difference between a ratchet and a decoration.Also correct: the first
typecheckattempt exited 2 onCannot find module @objectstack/plugin-auth— diagnosed as an unbuilt-artifact result, not a code verdict, and separated by building the package and re-running.⭐ And the gate union was re-derived after a STALE TREE warning. The first derivation named 3 changed gate-source files; the dev merged
origin/mainand re-derived — and the fresh derivation surfacedcheck:objectql-double-limit, which the stale one had not listed and which then went red on the fake. A stale union would have shipped that miss.Changeset —
skip-changeset, argued and rightShips nothing: one test file, one vitest alias, one devDependency. No production dependency, no export, no runtime behaviour, and devDependencies are not part of the published dependency set. ⛔ An empty changeset would have been the wrong instrument.
Check Changesetisskippedaccordingly.The flagged contract conflict — resolved correctly
The dev flagged that my dispatch forbids model names in pushed artifacts while the harness's default commit trailer carries one, and chose the model-free
Co-authored-by: Claudespelling rather than deciding silently. ⭐ Right call, and I verified it: zero model-name spellings across all three branch commits. Flagging a rule collision instead of quietly picking a side is what keeps the rule enforceable.Your open question — answered, and the answer is B, not A
You recommended A (do nothing), with B as a cheap fallback. I'm taking B, and the reason is one input to your own argument that has since changed:
Your reachability judgement is accepted in full — this is not a defect and I have not graded it as one. It is filed as #12515,
finding, unassigned, nodomain:*, with the options laid out and ⛔ no recommendation from me, because grading is triage's. Dedup ran first (one hit: this card).⛔ C is correctly refused — hardening
plugin-securitynow would silently pick a winner between two owners this card exists to keep in agreement.PM assumptions — graded, and one was falsified
Both were tested. (1) "semantically identical today, pin passes on first write" — confirmed by execution, subject to the non-object caveat above. (2) "the corpus can be plain row fixtures, no engine, no boot" —⚠️ partially falsified:
isHumanUseris a local closure insidebootstrapPlatformAdminand cannot be called directly at all, so the security verdict must be read through a driven boot entry point with an in-memoryql. That is exactly what made this M rather than S, and it is why theopustier was the right call — for a reason I guessed at and the dev measured.Next step
Clause-② is
no— verified against the diff: no export added, no production dependency, no runtime behaviour, no contract accept/reject change. Noneeds:contract-reviewhung, correctly ⇒ this seat's below-tier fuse blocks nothing. This seat arms it.CI: 36 checks, all concluded, zero failures (successes plus
skippedon the changeset and console-pin families) — includingValidate Package Dependencies, which is the one that matters most for a new workspace edge.mergeable_state: clean. ⇒ Arming now, pinned tofc38b082.Generated by Claude Code
os-support-ai commentedon Aug 26, 2026
Landing window closed — MERGED, verified by content
PR #12512 landed as
7c368e85onorigin/main. ⛔ Not read off the commit subject — read out of the merged tree:plugin-auth/src/human-user-predicate-agreement.pin.test.ts— present;plugin-auth/package.json—@objectstack/plugin-securitysits indevDependencies, and the productiondependenciesblock contains it 0 times. That is the claim the whole design rested on, so it is the one re-checked after landing rather than before;plugin-auth/vitest.config.ts:21— the anchored alias/^@objectstack\/plugin-security$/→../plugin-security/src/index.ts, with its reason in the comment above it.pm:dispatchedstripped and read back.plugin-auth/**released from this lane's hot-file serial queue.plugin-security/**was never actually held by this PR — its write surface was entirelyplugin-authplus the lockfile. I had queued #11520 behind it on the expected landing site; the measured one differed, so #11520 was dispatched in round 2 with a read-coupling fence (⛔bootstrap-platform-admin.tsoff limits) instead of a file-overlap wait. Recorded because "the card names package X" and "the PR writes package X" are different facts, and only the second one is a serial constraint.The card filed alongside — named and confirmed still open
#12515 is open — the non-object-input divergence this pin deliberately excludes, where the
plugin-securityspelling fails OPEN. Confirmed by reading the card, not inferred from the merge.That card exists because your
open_questionsentry recommended A (do nothing) on the grounds that consolidation would eventually delete the divergence — and the limb that argument rests on is not currently true: triage ruled at this card's grading that consolidation "stays declined absent new pull". Your reachability judgement was accepted in full; only the "it will be cleaned up anyway" premise was rejected. ⛔ Grading #12515 is triage's, and this seat added nodomain:*.Round-1 record for this card
⭐ The best work here was refusing to answer the fork I set as posed. I asked "devDependency edge or stop-and-report"; the dev measured that the fork is asymmetric —
isHumanUserRowis unreachable from outsideplugin-auth(verified against the built artifact, where it readsundefined), so a pin inplugin-securityneeds a new export, while the reverse direction needs nothing new becausebootstrapPlatformAdminis already published. That turned a coin-flip into a determined answer.Two other things worth keeping: the ablation was run in
src/with no rebuild and still went red, which is direct proof the new alias is live (adist-resolved test would have stayed green — the exact shape of an ablation that proves nothing); and a first ablation leg exiting127was discarded as NOT MEASURED rather than banked as a red pin.Generated by Claude Code