Repair the seven plugin-security durability swallows — batch 2 of the #12981 worklist - #13414
Repair the seven plugin-security durability swallows — batch 2 of the #12981 worklist#13414os-elon wants to merge 3 commits into
plugin-security durability swallows — batch 2 of the #12981 worklist#13414Conversation
…h {}` sites swallowed
Batch 2 of #12981's ruled repair-first worklist. The census instrument landed
by batch 1 named seven tier-1 DARK sites in this package; all seven are
repaired through the in-package refusal accumulator, and a census re-run moves
tier 1 from 28 sites in 14 files to 21 in 11, with `channelled` 19 -> 26.
- bootstrap-system-capabilities.ts x2 -- refused insert on the derived half and
refused update on both halves were silent, under an `info` line claiming the
capabilities were seeded. Reported on the durability channel.
- cleanup-package-permissions.ts x1 -- a refused revocation left the grant live
while the package door answered success (ADR-0090 D5 "no ghost grants").
- suggested-audience-bindings.ts x4 -- create/confirm/prune/reap. The insert
site filed every failure under its "benign unique-index race" rationale; the
accumulator's shipped classifier keeps the real race benign and reports the
rest.
Two of the three files keep `warn`: their sinks ride on types exported from
index.ts declaring `warn` optional, so adding `error?` would need a
published-shape break (check:optional-error-sink-contract). The silence is
fixed here; the level is recorded on #12981.
No entry added to scripts/durability-degradation.baseline.json; the gate
vocabulary is untouched in either direction.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WkdHQwHr2KQmaX7P1BHzi
…allow-batch-2-plugin-security
…uled OUT of the programme Repairing #12981's seven plugin-security sites turned the census instrument's own `--self-test` red: its `dark` positive control named `bootstrap-system-capabilities.ts`, with a `why` reading "the card's shape, verbatim, still standing" -- a sentence batch 2 made false on purpose. Any tier-1 DARK member of the worklist is a control the repair programme is designed to destroy, so repointing at another worklist member only moves the breakage to the batch that repairs that one. The control now names plugin-sharing's `share-link-service.ts`, which batch 1 judged OUT on the merits (a use_count/last_used_at telemetry stamp -- escalating a functional degradation to `error` is the over-application AGENTS.md forbids), so it is a genuine dark member with a recorded reason to stay one. Control target only: no predicate, tier or vocabulary change. The instrument is wired into no workflow (0 references in .github/ and root package.json). Declared on #12981 before editing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012WkdHQwHr2KQmaX7P1BHzi
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 14 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin fb4ddc40d878b0ed22ca8077ec5941aa27edcba7 && git checkout fb4ddc40d878b0ed22ca8077ec5941aa27edcba7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 090f2302ec2135dd3c5c67d0ef2d4b81897af522 dfbecbb4da6b5e4ff5f9f37b54891fd78ccffc49 && git checkout -B drift-repro 090f2302ec2135dd3c5c67d0ef2d4b81897af522 && git merge --no-ff dfbecbb4da6b5e4ff5f9f37b54891fd78ccffc49
node scripts/docs-audit/affected-docs.mjs --json 090f2302ec2135dd3c5c67d0ef2d4b81897af522
|
Part of #12981
⛔ Deliberately NOT a closing keyword. #12981 is a WORKLIST card and the ruling's own shape is "a mechanical census … then batched repairs". This is batch 2: it repairs 7 of the tail's tier-1 sites and leaves 21 enumerated below. Closing the card on merge would orphan that tail.
Batch 1 (
35202f17f) landed the census and repaired two members. It fenced the sevenplugin-securitysites off because PR #13176 was in flight on this package's tsc program; that fence is discharged (#13395 merged asa68c61267), and batch 1 named these the highest-value members left.1. Assumption ①, measured FIRST — the seven are still exactly seven
⛔ Not taken from batch 1's table. The instrument batch 1 landed for this purpose was re-run, and it confirms the seven by name:
Tier 1 stood at 28 sites in 14 files before this PR — unchanged since batch 1, so
a68c61267moved nothing.Assumption ④ answered: four DISTINCT sites, not one helper reached four times.
suggested-audience-bindings.tssplits 3 + 1 across two functions —syncAudienceBindingSuggestionsat the confirm-observed UPDATE, the create INSERT and the prune DELETE, andreapOrganizationLessSuggestionsat its own DELETE. The count is not misleading, and the three inside one function share one accumulator while the reap gets its own.The measured result
Exactly +7 channelled, −7 dark, and
plugin-securityno longer appears in the DARK list at all (grep count 0). The members total is deliberately unchanged: thecatchblocks still exist and still absorb, which is the behaviour that must not change — what moved is that the error now reaches a channel.2. Assumption ②, confirmed rather than assumed — the accumulator IS reachable in-package
createSeedWriteRefusals/logSeedDurabilityFailurelive inplugin-security/src/per-organization-catalog.tsand are absent from this package'sindex.ts(measured: 0 occurrences), which is exactly why they are available to these seven and were not toplugin-auth. Verified there is no import cycle:per-organization-catalog.tsimports only@objectstack/spec/securityand@objectstack/types, nothing local.⭐ The ACCUMULATOR is reused; the shared REPORTER is not.
reportSeedWriteRefusals' prose is catalog-seed-specific and names anos migrateremedy for the legacy platform-wide index — untrue of all three files here. Each site gets its own report, the deviationpermission-set-drift.tsalready records.3. Assumption ③ — all seven judged genuinely DURABILITY, and the judgement is applied per-site
Applying batch 1's
share-link-service.tstest (ause_counttelemetry stamp is functional, not durability, and escalating it is the over-application AGENTS.md forbids), nothing is judged OUT — but one site is judged out in part, which is the sharper result:⭐
suggested-audience-bindings.ts's insert site filed EVERY failure under one documented cause. Its comment readcatch { /* unique-index race with a concurrent sync — benign */ }. That cause is real and genuinely benign — the other pass created the row this one wanted — but a store outage, a missing table and a rejected column all reached the samecatchand were all recorded as the benign race. The shared accumulator classifies with the shippedisUniqueViolationErrorpredicate, so the race stays benign and is excluded fromrefused, while everything else is counted and reported. That is the accumulator earning its keep instead of a blanket escalation.4.⚠️ The seven split into TWO shapes — reported rather than made to look uniform
The split is not aesthetic; it is forced by a gate, and it decides the LEVEL each report can use.
scripts/check-optional-error-sink-contract.mjsrules that a sink type declaring an optionalerrormust declarewarnNON-optional — "an optionalerrorwith no declared alternative is a contract that permits silence". So addingerror?to a sink whosewarnis optional is red on arrival unlesswarnis made required too.bootstrap-system-capabilities.ts×2SeedOptions.logger, andbootstrapSystemCapabilitiesis absent fromindex.ts— package-privateSeedLogger(requiredwarn, optionalerror), so the report goes toerrorwith the mandatorywarnfallback. No new sink type enters the gate's population.cleanup-package-permissions.ts×1,suggested-audience-bindings.ts×4SuggestionDeps— both exported fromindex.tswithwarnoptionalwarn. Requiringwarnon a published shape is a contract call above this repair.⇒ What is fixed in shape B is the SILENCE, which needed no contract at all; the LEVEL is #12981's — the same split, for the same reason, that batch 1 recorded for
plugin-sharing'sbackfillPrimaryBu.check:optional-error-sinkis green and still reports 1 baselined, shrink-only: had shape B taken theerror?, that number would have risen.5. What each repair does
Common shape, copied from the three landed exemplars — count refusals, report once with consequence AND remedy, widen the
> 0suppressor.bootstrap-system-capabilities.ts— a refused insert on the derived half fell throughelse if (!isDerived)into total silence, and a refused update was counted nowhere and logged nowhere on either half, while the boot went on logging[security] system capabilities seededatinfoover zero landed rows. That is the card's shape verbatim.sys_capabilityis the registry Setup lists, and grants resolve capabilities by name, not by row, so no principal gained or lost access — what a refused insert leaves is a capability absent installation-wide, and a refused update leaves a row whose label and description stay drifted. The curated half's existing per-nameblockedCuratedwarning is untouched and is not double-counted; this is the pass-level total and the only report the other two paths had.cleanup-package-permissions.ts— ADR-0090 D5 promises that uninstalling a package "revokes it everywhere at once. No ghost grants." That promise is an ABSENCE, and an absence cannot be read off a count of successes:{ sets: 0, positionBindings: 0, userGrants: 0, suggestions: 0 }was returned both by an uninstall of a package that granted nothing and by one whose every revocation was refused — while the package door answeredsuccess: trueand every survivingsys_position_permission_set/sys_user_permission_setrow kept granting the uninstalled package's permissions. The old comment read "count reflects reality"; it was true of the count and false of what a reader does with it.suggested-audience-bindings.ts×4 — refused create / confirm / prune / reap, each with its own consequence in the line (no prompt at all; a nag for a decision already made; a prompt for a declaration that is gone; organization-less rows that stay readable by every tenant and suppress each tenant's own row).CapabilitySeedResult,PackagePermissionCleanupOutcomeandSuggestionSyncOutcomegain arefusedcount. Additive — they are returned by functions, not constructed by callers (measured: 0 references to any of the three outside this package).6. ⭐ One file outside the surface, declared on #12981 BEFORE editing
scripts/measure-durability-swallow-family.mjs. Repairing the seven turned the instrument's own--self-testred:Its
darkcontrol named one of the seven, with awhyreading "the card's shape, verbatim, still standing" — a sentence this batch made false on purpose.The general fact: any tier-1 DARK member of the worklist is a control the repair programme is designed to destroy. Repointing at another worklist member only moves the breakage to whichever batch repairs that one. The control now names
plugin-sharing'sshare-link-service.ts, which batch 1 judged OUT on the merits (a telemetry stamp), so it is a genuine dark member with a recorded reason to stay one — verified by reading the code, not off the output. Control target only: no predicate, tier or vocabulary change, and the instrument is wired into no workflow (0 references in.github/and rootpackage.json).--self-testis green: 4 positive, 3 negative, 1 regression control.7. ⛔ Ruling fences — held, and measured against a positive control
Against a diff that touches 8 files:
No
catch { return null; }becamecatch {}and nothing swallows deeper — everycatchstill absorbs and still never throws, which is the behaviour that must not change. NoTEST_DEBT/DEBTentry raised.8. Reverse verification — direction predicted BEFORE the run
Prediction: removing the single
refusals?.record(object, e)indeleteRowsturns the cleanup pins RED —refusedreads 0, the refused pass and the nothing-to-do pass become equal again, and the report vanishes.Mutation proven on disk before anything was read, restore trapped on
EXIT INT TERMagainst an absolute path:Observed: exactly the predicted direction.
ABLATED_EXIT=1, 3 failed / 5 passed — the three #12981 pins red and every pre-existing test still green, so the ablation is targeted rather than a blanket break.Restore proven by state, not by exit code: blob back to
1ea95a9b…, marker count 0, guard count 1,git diff HEADempty,git statusclean. Nodist/participates: the mutated file and its test are in the same package and the test imports it by relative path, so vitest resolves it from source — there is no rebuild leg to prove for this one.9. Verification — final head
dfbecbb4dnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack. The first derivation warned STALE TREE;origin/mainwas merged and it was re-derived clean. Re-derived a third time after §6 added ascripts/**path, which pulled in 7 further families (agent-test-spelling,bash32-floor,cli-command-ids,entry-guard,parse-guard,pnpm-filter-targets,watch-hint-literal) — all run, all green.plugin-security:typecheck+vitest run— 89 test files / 1651 tests passed, re-run on the merged head. ⭐ Sincea68c61267thetypecheckscript runs three programs includingtsconfig.test.json, so this green does cover the test code this PR adds — no separate--listFilesmeasurement is owed, and there was nowhere to park an error if one existed.turbo run build --filter=./packages/* --filter=./packages/*/*— 70 successful, 70 total. (An earlier--filter '...@objectstack/plugin-security'run failed on@objectstack/restdeclarations; that is the dependents-only filter not selecting the dependents' own dependencies, not a red from this change.)pnpm lint(eslint . --no-inline-config), not a narrowed scan — exit 0, re-run on the final head becauseeslint .coversscripts/.check:type-check-debt --re-measure— 29 ledger entries re-measured, 1547 raw tsc errors, none above its recorded number; surplus: none.check:optional-error-sink,check:logger-receiver-detach,check:engine-double-contract,check:where-matcher,check:type-check-coverage,check:changeset-gate-self-tests.check:optional-error-sink-contractexited 254 — that is the wrong script name (the script ischeck:optional-error-sink), so nothing ran. Re-run under its real name: green.check:i18n/check:i18n-stale-fillare convention-triggered on packages owning ani18n-extract.config.ts;plugin-securityhas none, so this change cannot move them.cmd > log 2>&1; EXIT=$?), and every verdict quoted is the gate's own printed line, never a bare$?.check:durability-log-levelis green over all eight files, and per this card's standing premise that green is NOT MEASURED for every site named here — never "level approved".10. The remainder — 21 tier-1 sites, still enumerated
plugin-auth—auth-manager.ts×9,auth-plugin.ts×2plugin-auth—admin-user-endpoints.ts,admin-import-users.tsisMissingTableErrorlives in a packageplugin-authdoes not depend on.plugin-auth—ensure-default-organization.ts×1warn→error) on a different, quiet site, which correctly does not move a DARK count. Worth a batch of its own.runtime—action-execution.ts,domains/keys.ts77b91bdb4; left for batch 3.service-storage—storage-service-plugin.ts×2metadata-protocol—protocol.tsplugin-sharing—share-link-service.tsdarkcontrol, per §6.verify—harness.tsAuthoring session (durable copy — the footer below is rewritten to its bare form by every body edit): https://claude.ai/code/session_012WkdHQwHr2KQmaX7P1BHzi
Generated by Claude Code