Skip to content

upgrade-impact comparison, access class (v18): a vendor upgrade that would orphan, re-enable or collide with a customer's access choice is refused until an administrator confirms, and raised at boot #24446

Closed

Description

@objectstack-fleet

Filing gate ③: a task the maintainer directed. Decision #24443 was ruled A, with the access class in v18 (ruling of record 6259332195; the maintainer's 「其他同意」 on the line 「推荐:A,访问类进 v18,其余 v18 后」).

Blocked-by: #24421 (PR #24428 adds the sharing_rule activation type that U1 reads)

What the ruling binds (verbatim, items 1–2, 4–5 of 6259332195)

  1. The comparison. One comparison of the customer's state (activation-ledger rows, overlays, clones, assignments) against the new registry runs at boot after a code-package change and at marketplace upgrade. It lists orphans, suspected re-enablements (the old name gone and a new name arrived in the same package, the old name carrying a switch-off or a clone), same-name collisions, and references broken inside clones and overlays.
  2. The access class (sharing rules, permission sets, positions, capabilities) is refused at marketplace upgrade until an administrator confirms, and raised as an administrator-visible warning at boot; operator-gated under a wall. Every other class is reported to the administrator, not only to the server log.
  3. Release: the access class (items 1–2 for the four access types) targets v18; the remaining classes and the retirement follow after v18.
  4. Record: ADR-0126 wall 3 (「no upgrade merges into or un-makes」 the customer's choices) becomes true in practice through this comparison; the stage that lands it says so in a dated note on ADR-0126 (Tier H, the maintainer merges). No rename spelling is added to the spec.

Measured starting points (origin/main e22defb13c; the seat's read-only measurement behind #24443)

  • Keys are bare names.
  • Existing fragments to reuse:
    • packages/plugins/plugin-security/src/catalog-reference-report.ts: dangling catalog references, failing closed and reported at boot;
    • objectql/src/security-catalog-namespace.ts:38-52: catalog name collisions refused at the package door;
    • the sharing orphan-grant sweep (plugin-sharing/src/sharing-rule-service.ts, sweepOrphanedRuleGrants).
  • The marketplace upgrade is a re-install over an existing ledger entry (packages/cloud-connection/src/marketplace-install-local-plugin.ts:2577, answer at :1698). It withdraws nothing (:1975), and both the old manifest (in the ledger) and the new one are in hand.
  • A clone permission set's body is never checked against objects or fields. Unlisted fields are visible (plugin-security/src/explain-engine.ts:886).
  • Every existing report is a server log line; none reaches the customer's administrator.

Stages (the seat cuts them; each measured first; each under 3,000 lines)

  • U1: the comparison core, plus the boot report for the access class.
    • It compares the customer's state against the registry after boot, for the four access types:
      • activation rows whose name the registry no longer resolves;
      • suspected re-enablements: the old name has gone and a new name has arrived in the same package, and the old name carries a switch-off or a clone;
      • collisions;
      • clone bodies naming an object or field the registry lacks;
      • managed-set field denies that a same-object clone lacks.
    • It raises an administrator-visible warning. Measure the existing channel, for example an administrator notification or a Setup health surface, and add no new surface without a stop-and-report. Under a wall, it is operator-gated.
    • It carries the ADR-0126 dated note as a separate Tier H docs PR, or rides a docs-only PR the seat cuts beside it.
  • U2: the marketplace refusal.
    • Before the install door writes anything, it runs the comparison against the incoming manifest.
    • If an access-class finding exists, it refuses with an ADR-0112 code naming each finding, until an administrator confirms (an explicit confirm flag or token on the same call).
    • Before U2, the repo:cloud seat reads cloud's remote-environment install path, which was not measured.
  • Order and serial constraints.

Acceptance

  • A permission set the customer switched off. The vendor renames it in an upgrade, and its own position binds the new name.
    • At boot, an administrator-visible warning names the old name, the new name and the affected positions.
    • At marketplace upgrade, the install is refused with that finding, until it is confirmed.
  • A sharing rule the customer switched off (ledger) or cloned. The vendor renames it. The same two outcomes apply.
  • A clone set that names a field the new version removed, or that lacks a field deny the managed set gained, is reported.
  • No finding: the upgrade proceeds unchanged.
  • Pins, at both doors, for each of the four types:
    • walled: operator-gated;
    • single: the administrator.

The post-v18 remainder (the other classes, and retiring packages/spec/src/kernel/package-upgrade.zod.ts) is a separate card.

Activity

  1. added
    priority:p1High: required for production / M2
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    and removed
    priority:p1High: required for production / M2
    on Oct 19, 2026
  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Scheduling note · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T04:10Z. The maintainer asked in the seat's chat: 「你所有的工作清单中,24446 这种优先级应该比较低吧」. Priority moves from p1 to p2. target:v18 stays, because ruling 6259332195 item 4 puts the access class in v18.

    What p2 means for dispatch:

    Why.

    • The defect predates this epic, and no incident has been reported. It needs a vendor rename on top of a customer customisation.
    • C8 is v18's spine.

    If v18's cut comes before this card is done, the seat brings it to the maintainer as the first candidate to move after v18. Moving it is the maintainer's word, not the seat's.

  3. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Release note · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-19T04:25Z. This card moves to 18.1. The maintainer's 「同意」 on the 18.0-fresh-installs scope (ruling record 6259608309 on #15194, item 3) amends item 4 of ruling 6259332195. target:v18 is removed, and the repo has no target:v18.1 label. The scheduling note 6259462343 still applies.

  4. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Maintainer decision: the access class moves from 18.0 to 18.1 · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-19T07:29Z

    Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.

    Provenance. The seat put its 18.0 GA leans to the maintainer in chat under the standing rule 6261447528 ("a metadata-protocol change is put to the maintainer for pulling forward"). The maintainer answered 「同意你的建议」.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: epic seat round · 2026-10-22T22:53Z · U1 of #24446 (the access-class upgrade comparison and its boot report), next in the seat's dispatch order now that blocker #24421 is closed (PR #24428 landed as 964a23700f). It moved to 18.1 per triage record 6262048067; p2 fill-in per scheduling note 6259462343.
    Session: session_01Y5583bs2RDd53MH6kM1ca3
    Account: os-steve
    Branch: claude/issue-24446-u1-access-upgrade-comparison
    Worktree: the cloud session's own checkout (mode:cloud)
    Domain: domain:services
    Seat: epic:#15194
    File surface:

  6. 12 remaining items

  7. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    U2 measurement · os-dev for the epic seat (session_01Y5583bs2RDd53MH6kM1ca3) · 2026-10-23T01:37Z · read-only, against origin/main 54df640b38 (U1 landed). Branch claude/issue-24446-u2-marketplace-access-refusal (claim 6355237423; its Claim: names this branch). ⛔ No code yet. This waits for the seat's go.

    1. The framework's install door: packages/cloud-connection/src/marketplace-install-local-plugin.ts

    POST /api/v1/marketplace/install-local → handleInstall (:1141). In order, at 54df640b38:

    step line writes?
    admission (manage_metadata), body parse, step 0 installMode :1150–1178 no
    inline manifest, or cloud fetch (step 1) :1180–1294 no (a network read)
    1b: the package id parsed through ManifestSchema.shape.id. Both branches converge here. :1300 no
    the ADR-0131 D12 walled refusal (refuseWalledCatalogPackage, :3695) :1353 no
    1c handshake, 1e mode permitted, 1d unrunnable code :1361–1455 no
    unmanaged leaves here for installUnmanaged, a copy into a new base and never an upgrade :1466 (its own)
    1f: a managed install over an unmanaged copy is refused :1476 no
    2: findConflict gives none, marketplace (the re-install) or user-code (refused) :1506, :2577 no
    2b: the ADR-0120 D5e global-unique gate. previousEntry = this.ledger.read(manifestId).entry :1533 no
    3: manifestService.register(converted) is the first write (the registry, plus the copy bridged into the metadata service) :1584, :1592 yes
    4: this.ledger.write(entry) :1630 yes
    4b–6: syncSchemas, binder, side effects, seed, announceHotInstall; the answer's upgradedFrom :1638–1698 yes
    • The re-install over an existing ledger entry is the findConflict === 'marketplace' case. That case is either a live ledger entry, or a package the registry holds whose ledger entry was lost. It runs the same steps as a fresh install, then answers upgradedFrom: 'previous-marketplace-version'.
    • Where the first write is: step 3, :1592. Nothing before it writes anything.
    • Where both manifests are in hand: from step 2b (:1533) to step 3.
      • The incoming one is manifest, converted forward at :1584. The conversion is pure, so it can move up.
      • The old one is previousEntry.manifest.
      • On the lost-ledger branch of findConflict there is no old manifest. But the comparison below does not need one: the registry still holds the old package's items, each carrying packageId.
    • So the pre-write check sits between 2b and 3 (a new step 2c). It runs only when conflict === 'marketplace'.
      • A fresh install has no customer choice of its own to un-make.
      • Running the check on a fresh install would also refuse on every pre-existing uncovered-deny pairing a new vendor set happens to touch. That is outside the ruling's "at marketplace upgrade".
    • Every caller of this door in this repository:
      • the CLI, packages/cli/src/commands/package/install.ts:203. It already branches on the D5e code literal (:217) and carries --confirm-global-uniques (:114, :200);
      • the door's own tests, packages/cloud-connection/src/marketplace-install-local-*.test.ts, about 30 files.
      • No other runtime caller exists. @objectstack/client names the route only in a comment (:2969). The console's install action lives in ../objectui, outside this repository.
    • A second door outside U2's file surface, for the seat: POST /api/v1/packages with overwrite (packages/runtime/src/domains/packages.ts:1184, through protocol.installPackage). It also replaces an installed package. It is not the marketplace door the ruling names, so I propose not to touch it in U2. If the seat wants it covered, it calls the same check (§2) in a follow-up card.

    2. The comparison: U1's collectAccessUpgradeFindings against the incoming manifest, with no copy of its rules

    collectAccessUpgradeFindings(engine, deps) (plugin-security/src/access-upgrade-report.ts:343) reads the registry through three injected seams only:

    • engine.getObject (objects and fields);
    • deps.catalog (list and resolve for permission sets, positions and capabilities, each entry carrying packageId);
    • deps.sharingRules.listDeclaredRules().

    The customer's state is read through engine.find: the ledger, and the sys_sharing_rule rows.

    So the check runs U1's function unchanged, twice, over a projected registry: the booted registry with package P's items replaced by the incoming manifest's.

    • The catalog: entries with packageId === P are dropped. The incoming permissions, positions and capabilities, after the forward conversion, are added as entries with packageId: P.
    • The declared sharing rules: the same, from sharingRules.
    • getObject: P's objects are taken from the incoming manifest. An object the old version shipped and the new one drops answers undefined.
    • find: passed through unchanged, because the customer's state is not projected.

    The two runs:

    • The current run uses the same projection with P's items unchanged, i.e. the booted registry.
    • The projected run uses the incoming manifest.
    • The door refuses on the delta only: findings in the projected run that the current run does not have. Without the delta, a finding that predates this upgrade, or one that another package caused, would block every re-install of every package. U1's boot report already raises those.
    • Identity: a finding is identified by its own fields, as U1's accessUpgradeSummary already reads them. The confirm token (§3) is U1's accessUpgradeDedupKey taken over the delta.

    Where it lives, and how the door reaches it. cloud-connection does not depend on plugin-security, and should not.

    • Recommended: plugin-security registers one in-process service, security.accessUpgradeCheck, beside its other security.* services (security-plugin.ts:1729–1747). Its shape is check({ packageId, manifest }) → { findings, findingSet } | { stopped, … }.
      • The projection is a small new module beside U1's, access-upgrade-precheck.ts. It builds three adapters and computes the delta.
      • U1's file is read-only, imported as it stands.
      • The door asks for the service as a capability question.
    • Alternative: a protocol-registered precheck, the registerUninstallCleanup pattern (metadata-protocol/src/protocol.ts:7174). It costs an edit to protocol.ts for a single check, so I do not recommend it.
    • The same service is what cloud's own path would call, if it has one (§5).

    Fail closed:

    • The check stops (U1's stopped: the ledger, the catalog or the declared rules unreadable). The door refuses 503 SERVICE_UNAVAILABLE, naming the store, and writes nothing. A comparison that did not happen is not "no finding".
    • No security.accessUpgradeCheck service (a composition without plugin-security, so no access item is enforced at all). The install proceeds, and one warn line says that no access check ran and why.

    3. The refusal

    The code: a new row, ACCESS_UPGRADE_CONFIRMATION_REQUIRED, under '@objectstack/cloud-connection' in packages/spec/src/api/error-code-ledger.zod.ts, beside UNIQUE_SCOPE_CONFIRMATION_REQUIRED (:1190).

    • Not an existing code. Neither UNIQUE_SCOPE_CONFIRMATION_REQUIRED nor a standard member fits, because each would send a client down the wrong branch.
      • The CLI branches on the D5e literal and prints per-index decisions for it.
      • CONFLICT / RESOURCE_CONFLICT already answer 1f's unmanaged-copy refusal, which a confirmation does not lift.
      • That is the same reasoning INSTALL_MODE_NOT_PERMITTED (:1166) was admitted on.
    • standardSynonymOf maps no standard member onto it: no member's tokens cover ACCESS_UPGRADE_CONFIRMATION_REQUIRED. I will run the admission test to confirm.
    • Clause-② and size: the row is a widening of the published code face. It adds one row (about 6 lines with its comment). The ledger's values are not an export, so I expect no api-surface change; I will run check:generated to prove it.

    Status 409, the D5e gate's status: the request is well formed and holds until the caller answers.

    check-governed-merges --test on the candidate list (the door, a new door test, plugin-security's two files, security-plugin.ts, the ledger file, the CLI's install.ts and one changeset, about 1,920 lines) exits 0: 0 of 8 paths governed, and the size is under 3,000. The ledger row is in packages/spec, which is not on the register.

    The confirm shape: a token on the same call, confirmAccessUpgrade: "<findingSet>".

    • What the refusal carries in error.details:
      • findings: per finding, its kind, type, old name, the candidate new names and binding positions (permission sets), the organization (sharing-rule collisions), and a one-line describe;
      • findingSet: U1's dedup-key form over the delta;
      • confirmWith: { body: { confirmAccessUpgrade: findingSet }, cli: 'os package install … --confirm-access-upgrade <findingSet>' }, the D5e gate's confirmWith shape.
    • The retry admits the install only if its token equals the findingSet the check computes on that call.
      • If the findings changed in between (another upgrade, another switch-off), the token no longer matches, and the call is refused again with the fresh set.
      • A bare true is not accepted. A confirmation is of the findings shown, never a standing "yes".
      • This deliberately departs from D5e's true, because here the confirmation un-makes a customer's security choice.
    • On a confirmed install:
      • one warn line names the confirmer, the package and the findingSet;
      • the ledger entry records accessUpgradeConfirmation: { findingSet, confirmedBy, confirmedAt }, like globalUniqueAttestation;
      • the next boot's U1 report still raises whatever stands. A confirmation admits the upgrade; it does not silence the report.

    What each caller sees. The door's existing admission already decides who can reach step 2c, so the refusal needs no audience logic of its own.

    • Walled (group, isolated): the D12 refusal at :1353 answers every non-operator 403 FORBIDDEN before step 1c. So the only caller who can see the findings and send the token is the platform operator: operator-gated.
      • The pins show both halves: a walled tenant administrator gets 403 and no findings (nothing leaks to a tenant), and the walled operator gets 409 with the findings.
    • single: the caller is a manage_metadata holder, i.e. the administrator (admin_full_access). They get 409 with the findings, and their token admits the install.
    • No finding: both postures proceed byte-for-byte unchanged. That is the CONTROL per type.

    4. Size and Tier

    file lines (est.)
    plugin-security/src/access-upgrade-precheck.ts (new: projection, delta, service) ~260
    plugin-security/src/security-plugin.ts (registers the service) ~25
    cloud-connection/src/marketplace-install-local-plugin.ts (step 2c, the refusal, token check, ledger record) ~150
    spec/src/api/error-code-ledger.zod.ts (one row) ~6
    cli/src/commands/package/install.ts (--confirm-access-upgrade <token>, prints the findings) ~50
    tests: a door test with the four types × (single admin, walled operator, walled tenant admin) × (finding, CONTROL); a stale token; a bare true; fail-closed; no service; ablation. Plus the precheck's unit tests ~1,250
    one changeset ~40

    About 1,700–1,900 lines. Not governed. Ordinary queue landing.

    • No cut is needed.
    • If the seat keeps the CLI out of the file surface, which the claim does not list, it drops about 50 lines. The CLI then shows the refusal's message and code without a dedicated flag.

    5. What the framework door needs from cloud's install path (6355241020)

    Nothing, for the framework door itself: it is self-contained.

    • If cloud's remote-environment path reaches this door, it is covered as is.
    • If it writes the ledger or registry through a path of its own, the same check is security.accessUpgradeCheck.check({ packageId, manifest }) in the target runtime. That path would call it before its first write, and answer with the same code, details and token.
    • I have not read cloud's path and do not guess it. The repo:cloud seat's answer decides whether a cloud-side card is needed.

    6. Recommendation

    Build as above:

    • a new step 2c in handleInstall, on re-install only, between the D5e gate and the first write;
    • U1's comparison run unchanged over a projected registry, refusing on the delta;
    • 409 ACCESS_UPGRADE_CONFIRMATION_REQUIRED (one new ledger row), with details.findings and a findingSet token confirmed on the same call;
    • fail closed on a stopped check;
    • the walled operator and the single administrator already isolated by the door's existing admission.

    Expected declarations, to be re-declared from the diff:

    • Clause-②: yes (narrowing): an upgrade that installed yesterday is now refused until it is confirmed. The changeset carries the BREAKING banner, the migration line (confirm with the token) and its ADR-0087 disposition.
    • Metadata-protocol: none: no authorable key changes, so the changeset level is patch.

    Questions for the seat:

    • (a) The CLI flag in or out of U2's file surface. I recommend in.
    • (b) POST /api/v1/packages overwrite left to a follow-up card. I recommend yes.
    • (c) A token, not a bare true. I recommend the token.

    Waiting for the seat's go, and for the repo:cloud seat's answer or the maintainer's ruling that U2 covers the framework door alone.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat GO · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-23T01:39Z. U2 of this card, on measurement 6355301005 (claim 6355237423). GO: build as measured.

    • The door: a new step 2c in marketplace-install-local-plugin.ts, between 2b (previousEntry) and the first write at step 3. It runs on a re-install only (findConflict === 'marketplace'); a fresh install has no customer choice of its own to un-make.
    • The comparison: U1's collectAccessUpgradeFindings runs unchanged, over the booted registry and over the projection with package P's items taken from the incoming manifest. The door refuses on the delta only. It is reached through the plugin-security service security.accessUpgradeCheck, so cloud-connection takes no dependency on plugin-security. A stopped check fails closed (503).
    • The refusal: 409 ACCESS_UPGRADE_CONFIRMATION_REQUIRED, a new error-code ledger row (check-governed-merges on the candidate list: 0 of 8 paths governed). details names each finding and carries the findingSet token and confirmWith.
    • The questions:
      • (a) The CLI's --confirm-access-upgrade is IN U2's surface. The CLI is the door's one caller in this repository; without the flag the confirm cannot be used.
      • (b) POST /api/v1/packages with overwrite (runtime/src/domains/packages.ts) is left to a follow-up card. The ruling names the marketplace upgrade. The seat files that card once this PR adds security.accessUpgradeCheck, so the second door calls the same check rather than a copy.
      • (c) A token, not a bare true. A stale token is refused again.
    • Postures: walled stays operator-gated, because the existing D12 refusal stops a tenant administrator before the check. single answers the administrator. Each type is pinned with a CONTROL at the door.
    • The repo:cloud read (6355241020) is no longer a precondition for building the framework door. The measurement shows the framework door needs nothing from cloud. The "before U2" line on this card was the seat's own sequencing guard, not part of ruling 6259332195. If cloud has its own install path, it calls the same service before its first write, in cloud's own follow-up. The request stays open for that.
    • Expected declarations: Clause-②: yes (narrowing) (a new service, code and confirm field; an upgrade refused) and Metadata-protocol: none. The dev re-declares from the diff.
    • Owed before ACCEPT: R1, and R4 under isolated and group on the final head; the harness file; the dispatch gates; and check-governed-merges --branch on the final file list. If that list turns governed, or passes 3,000 lines, stop and report.
  9. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24446,
      "status": "done",
      "branch": "claude/issue-24446-u2-marketplace-access-refusal",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25355",
      "session": "session_01U9Aia8cnVwrpSUL9KgVzdu",
      "premise_still_valid": true,
      "summary": "U2 only (Part of #24446), on measurement 6355301005 and GO 6355317159 (claim 6355237423). install-local gains step 2c: a managed re-install (findConflict === 'marketplace') asks the new plugin-security service security.accessUpgradeCheck, after the D5e gate and before the first write. The check runs U1's collectAccessUpgradeFindings unchanged twice, over the registry and over a projection with package P's catalog entries, declared sharing rules and objects/fields taken from the incoming manifest; it refuses on the delta only. A finding answers 409 ACCESS_UPGRADE_CONFIRMATION_REQUIRED (details.findings with a describe line each, details.findingSet token, details.confirmWith). The same call with confirmAccessUpgrade: <token> installs and records accessUpgradeConfirmation in the ledger entry; a bare true or a stale token is refused again. A stopped check answers 503 SERVICE_UNAVAILABLE and writes nothing. With no check service, the upgrade proceeds with one warn line. Walled is operator-gated by the existing D12 refusal; single answers the administrator. Also: one error-code ledger row (and its three generated reference pages), CLI --confirm-access-upgrade <token> with refusal rendering, and the flag documented in content/docs/deployment/cli.mdx. cloud-connection gains @objectstack/plugin-security as a devDependency only, for its door test; there is no cycle, since it already reaches it through runtime. Head ac8ce968, +1,471/-9 over 18 files, 0 governed, so ordinary Tier. Declared Clause-②: yes (narrowing) and Metadata-protocol: none, in the PR body and the changeset; every package is at patch, with the ADR-0087 marker not-required (no-migration-prescription). One limit added to U1's accepted ones: an object the incoming manifest adds that the registry does not yet hold is judged on its declared fields only.",
      "tests": "cloud-connection `pnpm test`: exit 0, 55 files / 704 passed, including the new marketplace-install-local-access-upgrade.test.ts with 34 cases. These are the four types (permission set, position, sharing rule, capability) × single admin 409 / isolated and group operator 409 / isolated and group tenant admin 403 with the check not called and no finding shown, plus CONTROLs under single and isolated (200); and the token installs and is recorded, true refused, stale token refused, a pre-existing finding does not block, a stopped check gives 503 and no write, no service gives 200 and a warning. plugin-security `pnpm test`: exit 0, 213 files / 4457 passed, 45 skipped, including access-upgrade-check.test.ts (15). That suite runs a real ObjectQL + SqlDriver and the service SecurityPlugin registers, over the four types with CONTROLs, plus a clone naming a field the upgrade removes (v1 read from the registry), a field deny the managed set gains, the delta, the fail-closed stop and unbound-before-start. cli: typecheck plus `vitest run --project unit` exit 0, 326 files / 4802 passed, including package-install-access-upgrade.test.ts (3). spec: error-code-ledger.test.ts 23/23; check:generated reported check:docs stale (the new code in three reference pages), fixed with --fix, then committed. typecheck plugin-security + cloud-connection exit 0; eslint on the touched files exit 0. Ablations, each committed first, then mutated and restored with git checkout HEAD, porcelain clean: (1) door step 2c disabled turns 21 door pins red (the 403 wall pins correctly stay green); (2) delta replaced by the whole projected report turns the 'predates' pin red in both suites; (3) catalog projection off turns 4 of the check's pins red. R1: `OS_LOG_LEVEL=info pnpm dev -- --fresh` on the showcase logs the access upgrade report clean with absent []. The same single runtime driven through the door: v1 install 200, switch-off 200, CONTROL 1.1.0 keeping the name 200, rename 2.0.0 gives 409 naming rfour_sales_rep, candidate rfour_sales_representative and position rfour_lead, bare true 409, token 200. R4 (code head 8c56e51e): production `objectstack serve` of the built showcase, persistent SQLite, NODE_ENV=production, OS_PLATFORM_OWNER_EMAIL=operator@r4.example. Temporary and reverted, tree clean: @objectstack/organizations declared in the showcase, membership policy auto with audience open (and its self-registration set) so a tenant could register, and the tenant's email_verified and a manage_metadata grant in its own organization written into the test DB. Under isolated and under group the operator got the same 200/200/200/409/409/200 sequence, with findingSet security.access_upgrade:7ce41192…. A tenant organization owner got 403 at the capability gate; given manage_metadata in their organization, 403 FORBIDDEN from the ADR-0131 D12 refusal; no findings shown, and the check never ran for them (no 'stopped' log line). An isolated restart after the confirmed upgrade: U1's boot report raised the re-enablement plus the operator line. No log carried 'was REFUSED' or 'reached no administrator'. Upgrade harness `packages/qa/dogfood/test/upgrade-18x-harness.dogfood.test.ts`, both fixture legs: exit 0, 16/16; its four upgraded boots each log the access report clean.",
      "gates": "`node scripts/pm/dispatch-gates.mjs --commands` exit 0, listing 133 commands, each run with its exit code captured before any pipe (head 8c56e51e): 129 exit 0 first time. Four exited 3, as PREREQUISITE refusals rather than findings. check-plugin-teardown-shape --self-test needed its pinned fixture commit 621a4876 in this shallow clone; after fetching it, exit 0. check:skill-examples, check:dual-build-cjs-loads and check:i18n-coverage needed built dists; after `pnpm build`, all exit 0. So 133/133 exit 0. After the final docs-only push: check-cli-command-ids, check-doc-anchors, check-cli-examples-parity, check-closed-ceremony-text, check-docs-transcript-drift, check:doc-authoring and check:role-word all exit 0. `node scripts/pm/check-governed-merges.mjs --branch …` exit 0: 0 of 17 paths governed, 1,463 lines ≤ 3000 at 8c56e51e. The final head adds only content/docs/deployment/cli.mdx (+17), which is not governed. CI on the final head ac8ce968, all success: Test Core (1–6/6 and the aggregate), Build Core, TypeScript Type Check, Lint & Repo Gates, Governed Surface Queue Guard, Check Changeset, Check PR Size, Part-of PR must not also close its card, The card this PR closes must claim this branch, both single-writer and same-issue guards, Spec property liveness, Validate Package Dependencies, Check Documentation Links, Dogfood Regression Gate, Build Docs, and every Lint/Type Check lane. The opt-in harness, tarball smoke and dogfood shards were skipped by design.",
      "dogfood_battery": "8c56e51e · `os-verify-lock: VERDICT command-exit 0` (`pnpm --filter @objectstack/dogfood test`, no shard, no path) · Test Files 289 passed | 3 skipped (292) · Tests 2491 passed | 18 skipped (2509) · no failures. The final head ac8ce968 differs only by content/docs/deployment/cli.mdx, so the battery was not re-run.",
      "mcp_calls": "github read tools: issue_read ×3, pull_request_read ×4 (get #25311 ×1, get_check_runs ×3); claude-code-remote: send_message ×2 (seat), subscribe_pr_activity ×1, send_later ×1. No GitHub write tool.",
      "api_writes": "3 through scripts/pm (fleet-write relay, POST /repos/objectstack-ai/objectstack/dispatches): the measurement comment 6355301005 on #24446, pr_create #25355 (draft, assignee os-steve), and this os-dev-report comment. Plus git push ×6 on the branch.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: none (follow-up the seat already holds) · POST /api/v1/packages with overwrite (runtime/src/domains/packages.ts) also replaces an installed package and is not checked; per GO 6355317159 the seat files that card, which calls security.accessUpgradeCheck · noted, not filed",
        "class: none (observation, as in U1) · no example app declares @objectstack/organizations, and a walled production serve of the showcase also needs a declared membership policy (OS_AUTH_MEMBERSHIP_POLICY), an OS_SECRET_KEY, and for a self-registering tenant an audience posture with its self-registration set; R4 set these temporarily · carrier: none · noted, not filed",
        "class: none (pre-existing doc gap) · content/docs/deployment/cli.mdx's `os package install` options table does not list --install-mode (ADR-0131 D6); left as is, outside U2 · noted, not filed"
      ]
    }
  10. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat note · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-23T03:44Z. On U2's contract review (PR #25355, PASS 6356845701), the seat rules the reviewer's three escalations:

  11. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-23T05:01Z. U2: PR #25355 merged into main as 448374d3f8. The card stays open for the ADR-0126 dated note (ruling item 5).

  12. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: epic seat round · 2026-10-23T05:02Z · the ADR text #24446's ruling owes, now that U1 and U2 have landed (PR #25311 54df640b38; PR #25355 448374d3f8). One Tier H docs PR also carries the ADR-0131 D5 line owed by #25286 (#15212 ledger 6352526831).
    Session: session_01Y5583bs2RDd53MH6kM1ca3
    Account: os-steve
    Branch: claude/issue-24446-adr-0126-wall-3-note
    Worktree: the cloud session's own checkout (mode:cloud)
    Domain: domain:services
    Seat: epic:#15194
    File surface:

  13. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24446,
      "status": "blocked",
      "branch": "claude/issue-24446-adr-0126-wall-3-note",
      "pr": null,
      "session": "session_01HbVJUgUf5HuWXPQvAo4sFg",
      "premise_still_valid": true,
      "summary": "Both notes are written, committed and pushed at 6cddca9 on claude/issue-24446-adr-0126-wall-3-note, one commit over origin/main 448374d3f8, +64/-0, exactly the claim's two files. ADR-0126 §6 wall 3 gains a dated note (2026-10-23) covering U1 (#25311, 54df640b38) and U2 (#25355, 448374d3f8): who is told (the administrator under single, the operator's log under a wall), the findingSet confirm token, the two §9 limits, the two open doors (#25382, #25366) and the other classes after v18. ADR-0131 D5 gains a dated note beside its 2026-10-22 note: the re-slug-to-default rule from #25286 (09e07c259a), the removed move and the stranding. No decision text changed. Blocked before opening the PR: the only write channel I have cannot open it the way the claim and the dispatch require. See open_questions.",
      "tests": "n/a: docs-only diff (two ADR notes); no package touched, no test owed.",
      "gates": "Head 6cddca9. `node scripts/pm/dispatch-gates.mjs --commands` derived 20 commands; all 20 exit 0 (each exit captured before any pipe). `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first exited 3 (PREREQUISITE: formula/lint not built); after `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under os-verify-lock (VERDICT command-exit 0) it exits 0. `dispatch-gates --ran`: '20 derived famil(ies) accounted for, 20 run, 0 NOT-MEASURED (a DERIVED zero, all 20 recorded an exit code and none of them is 3)'. `check-governed-merges --branch claude/issue-24446-adr-0126-wall-3-note`: exit 3, 'GOVERNED, landing tier: H', docs/adr/** x2, 64 changed lines (expected). Changeset: none; docs/adr/** publishes nothing and the claim allows no other file, so `skip-changeset` is to ride pr_create's labels. CI: NOT MEASURED, no PR yet.",
      "dogfood_battery": "n/a: no `!` in the planned title and no (narrowing) arm",
      "mcp_calls": "0 GitHub MCP calls; reads used `gh api` GETs",
      "api_writes": "1: this comment (fleet-write relay, op comment, POST /repos/objectstack-ai/objectstack/issues/24446/comments). The branch push is git, not REST.",
      "open_questions": [
        {
          "question": "How should the PR be opened? The relay's pr_create (scripts/pm/fleet-write/ops.mjs) FORCES draft: true, and on a Tier H diff its governed-review follow-up POSTs requested_reviewers for every GOVERNED_APPROVERS account. The dispatch says 'never open a draft first' and 'the seat requests the maintainer's review; you do not'. No GitHub MCP create-PR tool is in this session, and a bare REST write is barred.",
          "options": [
            "A: I open it through the relay pr_create (draft, assignee as the card's, label skip-changeset, body with Part of #24446). The executor's follow-up requests the approvers at create time. The seat readies it when it chooses.",
            "B: the seat opens the PR itself from head 6cddca9 with the body I hand over, so its own batching of the maintainer's request is kept. I then post the final os-dev-report and message the seat on CI.",
            "C: I open it via the relay and the seat accepts both deviations in writing on this card first."
          ],
          "recommendation": "A, if the seat confirms that the relay's automatic approver request is acceptable here: it is the designed path for every seat-opened Tier H PR, and the draft is what os-dev.md's DoD prescribes. Otherwise B."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24446,
      "status": "done",
      "branch": "claude/issue-24446-adr-0126-wall-3-note",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25384",
      "session": "session_01HbVJUgUf5HuWXPQvAo4sFg",
      "premise_still_valid": true,
      "summary": "PR #25384 at head 6cddca9 (+64/-0, two ADR files, notes only) supersedes the blocked report 6357957117. ADR-0126 §6 wall 3 gains a dated note: U1 (#25311, 54df640b38) and U2 (#25355, 448374d3f8) make the wall true in practice for the access class. The note covers who is told, the findingSet token, the two §9 limits, the open doors #25382 and #25366, and the other classes after v18. ADR-0131 D5 gains a dated note for #25286 (09e07c259a): the re-slug-to-default rule, the removed move and the stranding. Opened through the relay per the seat's ruling A: a forced draft, with skip-changeset, assignee os-steve and Part of #24446. The relay requested reviewers hotlong and os-zhuang, read back. The seat then readied the PR; I did not touch its draft state.",
      "tests": "n/a: docs-only diff; no package touched.",
      "gates": "Local, head 6cddca9: dispatch-gates --commands derived 20 commands, all exit 0, and --ran gives a DERIVED zero. check:doc-formula-expressions exited 3 (prerequisite) until formula and lint were built, then 0. check-governed-merges --branch: GOVERNED, Tier H, as expected. CI on 6cddca9: 51 check runs, 33 success, 18 skipped, 0 failure; mergeable_state clean.",
      "dogfood_battery": "n/a: no `!` in the title and no (narrowing) arm",
      "mcp_calls": "0 GitHub MCP calls; 2 claude-code-remote send_message to the seat",
      "api_writes": "3 via the fleet-write relay: POST issues/24446/comments (6357957117, blocked report); pr_create (POST /pulls, then POST issues/25384/assignees, POST issues/25384/labels, then the executor's governed-review requested_reviewers); POST issues/24446/comments (this report)",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  15. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and closing · epic PM session_01Y5583bs2RDd53MH6kM1ca3 (os-steve) · 2026-10-23T07:24Z. PR #25384 (the ADR text owed by ruling 6259332195 item 5) merged into main as b22ab8d6fb, approved by the maintainer (os-zhuang). Every part of this card's access-class scope has landed:

    part landed as
    U1: the comparison core and the boot report PR #25311 (54df640b38)
    U2: the marketplace refusal until confirmed PR #25355 (448374d3f8)
    The ADR-0126 dated note (wall 3 true for the access class), with the ADR-0131 D5 slug rule PR #25384 (b22ab8d6fb)

    Carried in the shared queue, not owed here:

    Closed as completed.

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions