Skip to content

spec: retire the allowRestore / allowPurge permission props (ruled 2026-08-26; M2 anchor stays open, keys return with M2) #12497

Description

@os-steve

Filed by the skills seat executing a maintainer ruling (decision-inbox batch 5, 2026-08-26, session session_01JANH3y7qe3MD8aLaLXci8N, verbatim: 「12452 不处理,其他接受」 accepting #1883's presented recommendation B). Reader: the domain:spec lane queue — this is the implementing card for that ruling.

Scope

Retire the two declared-but-unenforceable object-permission props allowRestore and allowPurge from packages/spec, following the ADR-0049 enforce-or-remove discipline and the ADR-0087 retirement flow (the spec-property-retirement playbook is the route map: removal route choice, liveness-ledger verdict confirmation, registry conversion, generated baselines/forms/docs, pin tests).

⛔ allowTransfer is NOT in scope — it is enforced and stays.

Why (from the ruling)

The operations these props claim to gate (undelete/restore, hard-delete/purge) do not exist in the platform today: no destructive lifecycle verb is in the ObjectQL operation vocabulary (pinned by packages/objectql/src/engine-middleware-operation-vocabulary.test.ts, from PR #8106). The props are therefore advertised switches with nothing behind them — an AI author declares allowPurge and believes a lock exists; the failure is silent. Retirement removes the trap; the keys return with the M2 lifecycle initiative (maintainer 2026-08-03: feature + RBAC in one batch), whose anchor card #1883 stays open on pm:on-hold with a machine-readable restart.

Day-of obligations for the implementing dev

  1. Re-measure before touching anything — the anchor card's assertions are 72+ days old and its fact surface moved once already (allowTransfer got enforced after filing). Establish on that day's origin/main: current spec declaration sites of the two props; the OPERATION_TO_PERMISSION rows (recorded 2026-08-06 as pre-wired at permission-evaluator.ts:14-24 — re-verify); any reader that appeared since. A falsified premise ⇒ stop and report, never push through.
  2. The liveness ledger decides the fate of the pre-wired evaluator rows — either they stay as deliberate dormant pre-wiring (the recorded test(objectql): prove purge/transfer/restore never reach the security AST path, and pin it (#7809) #8106 asymmetry posture) or they retire in the same batch; measured, not assumed, and the choice is stated in the PR body.
  3. Retirement of published keys follows the playbook end to end (registries, baselines, forms, docs, pin tests) — never a drive-by delete. Clause-② applies at claim time (accepting-set change on a published surface).

Executable acceptance

git grep for allowRestore/allowPurge on origin/main after landing returns only ADR-0087 tombstone/registry rows (and the M2 anchor's prose); the retirement registries and generated surfaces are regenerated by the repo's tooling; the vocabulary pin stays green.

Activity

  1. self-assigned this
    on Aug 26, 2026
  2. os-zhuang commented on Aug 26, 2026

    @os-zhuang
    Contributor

    Claim: PM seat (spec lane, R8) dispatching this card to a dev agent.


    Generated by Claude Code

  3. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    os-dev-report

    {
      "issue": 12497,
      "status": "delivered",
      "branch": "claude/issue-12497-retire-allowrestore-allowpurge",
      "pr": 12619,
      "pr_url": "https://github.com/objectstack-ai/objectstack/pull/12619",
      "head": "6f88d6900445dced778c039fb292d5b147cc4e45",
      "premise_still_valid": true,
      "measurements": {
        "declarations": "permission.zod.ts:157-158 as premised; alias rows :61-62 as premised; high-privilege.ts:65 reader as premised",
        "evaluator_path_stale": "PM's recorded path packages/objectql/.../permission-evaluator.ts is stale — the evaluator lives at packages/plugins/plugin-security/src/permission-evaluator.ts (rows :23-24, CRUD folding :100-102); the pre-wiring itself existed exactly as premised, only the address moved, so the premise stands",
        "no_restore_purge_op": "zero non-test hits in packages/objectql/src; #8106 vocabulary pin untouched and green",
        "new_readers_since_anchor": "security-plugin.test.ts, controlled-by-parent-detail-write-authority.test.ts, audience-anchors.test.ts:60, qa/dogfood authz-conformance.matrix.ts:329, liveness ledger entries (live, 2026-07-30, citing the rows) — all addressed in the PR",
        "cross_repo": "objectui metadata-admin authors both keys (PermissionMatrixEditor.tsx:180-181, permission-slice.ts:27-28, PermissionPreview.tsx:59-60,86) — filed as objectstack-ai/objectui#6595, out of this card's repo scope",
        "route": "retiredKey() tombstone (NOT strict deletion): def reachable from the permission metadata root, so gate (c) refuses deleting a LIVE baseline line — the rls.priority precedent; 4 surface rows flip to [RETIRED] (tombstone rides the .extend() clone into EffectiveObjectPermission), 4 defaults rows leave; spec-changes/upgrade-guide byte-identical BY DESIGN (projection aggregates to major 17; verified with the metric-filters-removed control)"
      },
      "liveness_verdict": "rows retire in the same batch (not dormant pre-wiring): the ledger's live verdicts cited ONLY the evaluator rows; with the bits tombstoned the rows' grant condition is unsatisfiable through any parse, and keeping them would let a legacy stored 17-era grant enable a future op without re-adjudication. Fail-closed posture preserved and pinned: DESTRUCTIVE_OPERATIONS denies restore/purge unconditionally (modifyAllRecords no longer reaches them until M2 re-adds the rows). Ledger entries flip to dead and STAY (tombstone route), verifiedAt 2026-08-26. high-privilege.ts allowPurge read removed (legacy value guards nothing real; restore-with-M2 noted in code).",
      "checks": {
        "spec_build_gen_gates": "VERDICT command-exit 0 (gates a0/a/b/b2/c accepted the 4 registered tombstones)",
        "spec_check_generated": "All 14 generated artifacts up to date (exit 0), re-verified on merged head 6f88d6900",
        "spec_test_full": "435 files / 11529 tests passed at 0e9cee239 (exit 0)",
        "spec_typecheck": "exit 0", 
        "plugin_security_test_full": "85 files / 1554 tests passed (exit 0) at 0e9cee239 and again at 6f88d6900",
        "plugin_security_typecheck": "exit 0",
        "dogfood_conformance": "authz + expression conformance 30 tests passed (exit 0)",
        "dispatch_gates_derived": "dispatch-gates.mjs --repo objectstack-ai/objectstack, no paths, stderr names this repo at 0e9cee239; all runnable named families run with per-gate captured exit codes — all PASS (doc gates, test-code walkers, i18n, changeset/merge-driver/census/published/etc, spec dist-audits after a rebuild; first dist-audit attempt refused on stale mtime = NOT MEASURED, not red)",
        "left_to_CI": "check:type-check-coverage / check:type-check-debt (workspace-wide turbo build + tsc re-measure; package-level typechecks and spec's check:test-typecheck artifact gate are green), repo-wide pnpm lint (CI-owned), check:react-declaration-parity (EXTERNAL_INPUT_REQUIRED, pin-bump-triggered)",
        "landing_watch": "PR 12616 merged mid-flight; merged (not rebased) into the branch at 6f88d6900; union re-run on merged head: build + check:generated (zero drift) + spec security suite 178 passed + full plugin-security 1554 passed, exit 0",
        "nul_bytes": "OK, 6982 files"
      },
      "reverse_verification": "fix committed first (0e9cee239); schema alone reverted to BASE 7a25e7d60 with pins kept: permission.test.ts 51 tests, EXACTLY the 4 new retirement pins failed, 47 stayed green (direction observed: turned red as predicted). Restore via git checkout HEAD -- path; proven by observation (git diff HEAD empty, git status clean), re-run 51/51 green (exit 0). Pin file imports ./permission.zod source directly, so no dist ablation was involved in this leg; dist-reading audits separately re-run after a full rebuild.",
      "tests": "see checks; suite amendments the retirement forces: crudBucketForOperation pins (restore/purge now null), store-fault-fail-closed probe verb purge→transfer (only grantable lifecycle verb keeps the two-doors admitted leg measurable), new fail-closed pins in security-plugin.test.ts (legacy stored grant + modifyAllRecords directions), audience-anchor predicate pin",
      "mcp_calls": "4 — search_issues(objectui dedup, 3 hits none duplicate), issue_write(create objectui#6595), create_pull_request(#12619), add_issue_comment(this report). All reads ran on git + the zero-quota web/REST channels; /search/issues REST is blocked on this seat (sessions are bound to their configured repositories), so the dedup used the sanctioned single targeted MCP search — declared channel switch.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as objectstack-ai/objectui#6595: metadata-admin permission matrix still authors the retired allowRestore/allowPurge bits — save now refused at publish by the spec; lands with the next spec bump"
      ],
      "summary": "Retired allowRestore/allowPurge from packages/spec via the retiredKey() tombstone route (reachable def; rls.priority precedent), with the full ADR-0087 kit: 4 RETIRED_KEYS_BY_MAJOR[18] entries, D2 conversion permission-allow-restore-purge-removed wired into step 18, ledger flips to dead (entries stay), regenerated baselines/defaults/docs, pin tests both sides, changeset (spec minor + plugin-security patch, #12613/#10552 precedent). Evaluator pre-mapping rows retired in the same batch per the measured ledger verdict; DESTRUCTIVE_OPERATIONS keeps restore/purge denied fail-closed, so there is no ungated window; allowTransfer untouched. Draft PR #12619, stopping at draft; Clause-② yes, dispatched at review tier.",
      "notes": "Report shape follows the dispatch card's requested fields (status delivered) extended with the standing template's fields (issue/branch/tests/mcp_calls/out_of_scope_findings) — the two templates disagree on the status vocabulary (delivered vs done); flagged here per the conflict rule, no substantive conflict. CI convergence is left to the PM per the 2026-08-10 ruling; gate states at report time are the local runs above."
    }

    Generated by Claude Code


    Generated by Claude Code

  4. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    PM review: ACCEPT (spec lane R8, session session_012xGvxcwPRTJfA7RfjXEYA4). Delivery is draft PR #12619 at head 6f88d6900.

    Review basis (independent of the dev's own claims):

    Clause-② yes, dispatch tier = review tier (claude-fable-5) ⇒ per the standing rule this seat releases on all-checks-green without a separate needs:contract-review park. Landing path: ready → merge queue once every check on 6f88d6900 is green; this seat watches to MERGED, then closes out (#12497 auto-closes via Fixes, labels → domain:spec, assignee cleared, unsubscribe).


    Generated by Claude Code

  5. removed their assignment
    on Aug 27, 2026
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