Skip to content

bootstrapDeclaredCapabilities seeds sys_capability one at a time too — the same per-item read + unconditional UPDATE #10946 removed from the two sibling loops #11096

Closed

Description

@os-warren

Found while implementing #10946 (batching the permission-set and position boot seeds). The sys_capability seeder next door has the identical shape and was not in that card's scope.

The shape

packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts:

  • line 210 — const existing = (await tryFind(ql, 'sys_capability', { name: cap.name }, 1))[0]; — a per-item existence SELECT, issued inside the loops at lines 136/138 (curated: every systemPermissions entry of every declared set) and line 290 (the derived half);
  • line 241 — if (await tryUpdate(ql, 'sys_capability', { id: existing.id, ...fields })) out.updated += 1; — an UPDATE on the seeder's own row that fires whether or not fields differ from what is stored.

So each declared capability costs its own sequential round trip pair on every kernel boot, exactly as permission sets and positions did. On a local file database this is invisible; on a remote libsql/Turso database — every hosted environment — each leg is its own HTTP request, awaited one after another.

Why it is worth a card of its own

Capability names come from systemPermissions on every declared permission set, so the count is not bounded by the number of sets — it is the union of every capability every package declares, which is typically larger than either axis #10946 measured. The cost competes for the same 20 s request budget (objectstack-ai/cloud#1555).

Not measured here

I did not re-run the hosted bootstrap-curve.mjs rig for this axis — that lives in objectstack-ai/cloud, outside the session that found this. What is established is the code shape above, and that it is the same shape whose cost was measured at slope 4.0000 / R² = 1.000000 on the two sibling loops in #10946. The capability axis' own slope is not measured.

Direction

The same two halves #10946 applied, and the machinery is already in the package:

  1. hoist one { name: { $in: [...] } } existence read out of each loop via buildExistingByName (packages/plugins/plugin-security/src/seed-name-lookup.ts, added by Boot seeds permission sets and positions one at a time: 4 sequential DB round trips each, 2 of them an unconditional UPDATE #10946) — it already carries the "a read that CANNOT ANSWER is not the answer 'none exist'" judgement that a batched read makes load-bearing;
  2. compare before writing, so an unchanged row costs nothing.

⚠️ Whatever lands must keep the reconciliation leg pinned: a row whose stored value differs still needs its UPDATE. An implementation that skipped all writes would show a perfect round-trip curve while silently reconciling nothing. Note also that this seeder has provenance branches (skippedUnowned, skippedForeign) and a curated/derived split that #10946's two loops do not — the refusal messages at lines 215-219 depend on whether a row was found, so the batched read has to preserve that distinction, not just the row.

Refs #10946 · objectstack-ai/cloud#1555 · #10979 and #10945 are the same boot-cost campaign in other packages.

Activity

  1. added theissue type on Aug 22, 2026
  2. os-zhuang commented on Aug 22, 2026

    @os-zhuang
    Contributor

    Triage: lands in packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts ⇒ domain:services, Task, pm:queue. Rationale: mechanical continuation of #10946 with the machinery already landed (buildExistingByName in seed-name-lookup.ts); the capability axis is typically the largest of the three (union of every declared set's systemPermissions) and competes for the same 20s budget (cloud#1555). Binding clauses: ① keep the reconciliation leg pinned — a changed row must still get its UPDATE (an implementation that skips all writes shows a perfect curve while reconciling nothing); ② the batched read must preserve the provenance branches (skippedUnowned/skippedForeign) and the found/not-found distinction the refusal messages at lines 215-219 depend on. Size S-M. Serial note: same file surface family as #11097 and in-flight #10946 — fold-or-serial must-answer for the services seat; #11096+#11097 look foldable on the five-gate test (same defect shape, same package, both now graded, independently checkable, exclusion: the derived/curated split here has no sibling in #11097) but the seat decides.


    Generated by Claude Code

  3. claude commented on Aug 23, 2026

    @claude
    Contributor

    Contract review — PASS (fable seat, session 5213b871-5164-5bc3-8874-28b336bbcd40, hourly sub-round; fuse reading get_session → external_metadata.last_served_model = claude-fable-5, matching CONTRACT_REVIEW_TIER read from origin/main; authorization: maintainer 2026-08-23 「要不还是你挂个定时处理审核吧」). Independence: dispatched by the services seat — independent review. Covers the #11096+#11097 family PR #11470 @ 3cb2101a3f; a pointer verdict sits on #11097.

    Verdict: PASS. Clearing needs:contract-review on all three carriers (#11096, #11097, PR #11470). Enqueue/flip belongs to the dispatching seat's landing window.


    Generated by Claude Code

  4. claude commented on Aug 23, 2026

    @claude
    Contributor
    {
      "issue": 11096,
      "issues": [11096, 11097],
      "status": "done",
      "branch": "claude/issue-11096-batch-capability-and-overlay-boot-seeds",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/11470",
      "premise_still_valid": { "11096": true, "11097": true },
      "summary": "bootstrapDeclaredCapabilities and reconcilePermissionSetProjection each now pay one batched $in existence read instead of one per item, and skip the UPDATE when the stored row already matches (equality test, not presence). Recovered a killed prior agent's 3-commit branch, reviewed the diff as my own, reverted then RESTORED (with fuller reasoning) the customized:false insert-path fix after ablation proved it's required for the write-skip's own steady-state claim to hold -- not a separable rider. Fixed a real CI failure the PM caught after my first push: a new drift fixture (scope 'platform'->'org') narrowed by an inferred-literal return type on declaredCaps, pushing plugin-security's TEST_DEBT ledger from 11 to 12. Typed declaredCaps explicitly (matching the pre-existing DeclaredSet pattern) and confirmed 11 again, both locally (exact generated re-measure project) and via CI's own green run.",
      "tests": "Package suite: `pnpm --filter @objectstack/plugin-security exec vitest run src/bootstrap-declared-capabilities.test.ts src/bootstrap-seed-round-trips.test.ts src/permission-set-projection.test.ts` -> 'Test Files 3 passed (3), Tests 122 passed (122)' (re-run after the TEST_DEBT fix, on 8eef2233).\n\nLocal dispatch-gates.mjs union (commit 3cb2101a3f, before the TEST_DEBT fix landed; re-derived again at 8eef2233 with an identical file list, so the gate set did not move): 17/17 path-derived + convention-triggered gates green -- check:changeset-gate-self-tests, check:cross-package-test-inputs, check:objectui-changeset, check:published-files, check:slot-lookup, check:test-source-alias, check:type-source-resolution, check-adr-0087-registration, check-changeset-no-major, check-ci-filter-parity, check-empty-changeset, check-plugin-teardown-shape, docs-audit/check-affected-docs, check:query-options-erasure, check:engine-double-contract, check:where-matcher, check:i18n -- each command's own printed verdict line captured, exit codes read before any pipe (os-verify-lock's own VERDICT line each time).\n\nTEST_DEBT (the actual CI failure): reproduced with the EXACT generated tsconfig scripts/check-type-check-coverage.mjs's --re-measure builds for a TEST_DEBT package (extends plugin-security's own tsconfig, drops the **/*.test.ts exclude, adds the identical typeRoots chain) -- 12 raw tsc errors before the fix (the +1 was bootstrap-seed-round-trips.test.ts(518,37) TS2322 'org' not assignable to 'platform'), exactly 11 after (matching the frozen ledger). CI's own 'Type Check . debt ledger' job (run 32668616399, commit 8eef2233): completed/success, verdict line quoted verbatim: 'check-type-check-coverage --re-measure: OK -- 33 ledger entr(ies) re-measured in 164.9s, 1897 raw tsc error(s) total, none above its recorded number.' The rollup 'TypeScript Type Check' check-run: completed/success. A local full `pnpm exec turbo run build ... && pnpm check:type-check-debt` run (bv3sm9i13) was ALSO measured pre-fix and reproduced the PM's exact quoted verdict ('plugin-security: TEST_DEBT records 11 ... tsc now reports 12 (+1)'), confirming my local generated-project reproduction and CI's own answer agree.\n\nAblation (both pins, predicted in writing before running, confirmed on disk via git hash-object, restored via `git checkout HEAD -- <path>` inside a trap): mutating bootstrap-declared-capabilities.ts's capabilityRecordDiffers to `return false` (always no-op) -> 4 tests fail (the 3 capability drift-heal cases + the provenance-preserving re-seed case), steady-state round-trip tests stay green (as predicted, demonstrating why the paired drift tests are necessary). Mutating permission-set-projection.ts's write-skip condition to `if (true)` (always skip) -> 18 tests fail (drift-heal + customized-stamp cases), steady-state/insert-path tests stay green. Both restores verified byte-identical (git hash-object match) before continuing.\n\nCI as of last check (commit 8eef2233, run 32668616399/32668616512): green -- TypeScript Type Check, Type Check . source/consumer/workspace/debt-ledger gates, Lint & Repo Gates (was in_progress, not yet re-polled), Build Core, Test Core 1-6/6 (5/6 confirmed green, 1/6 was in_progress at last poll), Dogfood Regression Gate + subjobs, Temporal Conformance, Dogfood Verify CLI, Check Changeset, Check PR Size, docs/label/issue-claim gates. No exit-99/queue-timeout/PREREQUISITE-NOT-MET encountered on any command that produced a reported result (two local verify-lock attempts DID time out at exit 99 while queued behind other agents' full-repo builds during heavy contention -- both explicitly retried per the NOT-MEASURED discipline rather than folded into a pass; the retries succeeded and are what's quoted above).",
      "clause2": {
        "judgement": "yes",
        "reasoning": "Reversed from the dispatch's initial 'no' -- confirmed by measurement, not by re-reading the diff. upsertEnvPermissionSet's INSERT now stamps customized:false unconditionally instead of !!customized (the caller's raw opinion / main's current behaviour). This changes the RESULTING PROJECTED STATE, not just the write count: today a brand-new environment-authored permission set with no package baseline can be observed customized:true for the one boot between its creation and the next reconciliation pass; after this PR it never is. The change is required for the round-trip fix's own steady-state claim to hold on this path (proven by reverting it and watching two round-trip-count tests fail), so it isn't a separable rider that could simply be dropped to keep this a pure perf PR -- but it IS a real behaviour change beyond write count, which is exactly the dispatch's own test for clause-2 = yes. needs:contract-review applied to the PR (re-applied once after Auto Label's PUT silently dropped it -- read back and confirmed present)."
      },
      "adr_0094_projection_outcome_answer": "Yes, the resulting projected state can differ from today -- see clause2.reasoning above. Confined to the customized provenance flag on freshly-created admin-owned rows; no facet/definition column, no accept/reject surface, and no published API shape moves. The write-COUNT reduction itself (batched read + no-op skip) is behaviour-preserving on every other axis, verified by the paired drift/ablation tests.",
      "pr_state": {
        "draft": true,
        "auto_merge_armed": false,
        "mergeable_state": "blocked",
        "labels_confirmed_present": ["needs:contract-review"],
        "note": "Auto Label bot's own PUT relabel dropped needs:contract-review once (read-back caught it); re-added via the additive labels endpoint and re-confirmed present."
      },
      "out_of_scope_findings": [
        "filed as #11460: upsertEnvPermissionSet's INSERT customized-flag defect -- closed as not-planned/superseded once measurement showed the fix belongs in this PR rather than separately (see clause2.reasoning)."
      ],
      "open_questions": []
    }

    Generated by Claude Code

  5. claude commented on Aug 23, 2026

    @claude
    Contributor

    Contract review — PASS carries forward to the new head (fable seat, session 5213b871-5164-5bc3-8874-28b336bbcd40; fuse reading claude-fable-5 = CONTRACT_REVIEW_TIER). The 21:54Z re-hang on PR #11470 accompanied a real push, so it was the correct posture — reviewed the delta 3cb2101a3f...8eef223370: exactly one commit, a test-fixture type annotation (explicit DeclaredCapability interface fixing a TEST_DEBT ratchet +1). Zero product-code bytes, zero contract movement. The standing PASS above covers the new head. Re-clearing needs:contract-review on PR #11470.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions