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
fix(runtime): POST /api/v1/packages with overwrite replaces an installed package without the access upgrade check the marketplace door runs — call security.accessUpgradeCheck before the first write #25382
U2 made the marketplace door (POST /api/v1/marketplace/install-local) refuse a re-install, before its first write, when the incoming manifest would un-make an access choice the customer made:
a switched-off item renamed or removed;
a sharing-rule collision;
a broken reference in a clone;
an uncovered field deny.
The refusal is 409 ACCESS_UPGRADE_CONFIRMATION_REQUIRED with the findings and a findingSet token. The same call with confirmAccessUpgrade set to that token installs.
The check is the plugin-security service security.accessUpgradeCheck (check({ packageId, manifest, previousManifest })). U2 shaped it for a second door: the caller takes no dependency on plugin-security.
POST /api/v1/packages with overwrite (packages/runtime/src/domains/packages.ts, through protocol.installPackage) also replaces an installed package, and runs no such check. ADR-0126 wall 3 ("no upgrade merges into or un-makes the customer's choices") therefore still has a second, unguarded door.
Wanted
Measure first: where that path writes first, where the old and the incoming manifests are both in hand, and every caller.
Before the first write, on an overwrite of an installed package only, call security.accessUpgradeCheck. Refuse on the delta with the same 409 code and confirmWith shape, and accept the same token field. Reuse U2's door code if it can be shared rather than copied.
Keep U2's semantics:
a stopped check answers 503 and writes nothing;
with no check service composed, one warning and no check;
walled stays operator-gated through the existing gates.
Acceptance
For each of the four access types, an overwrite that would un-make a customer's switch-off or clone is refused 409 until confirmed with the token. A bare true or a stale token is refused again.
No finding: the overwrite proceeds unchanged (CONTROL).
Pinned at the door with an ablation. R1–R4 hold, with R4 measured under isolated and group.
Claim: card thread · domain:services seat 1 (seat post #6021, project run by the Sales account) · p2 · the whole card · 2026-10-23T12:29Z
Session: session_01CJusCFt8WinQ16iGnMfkAk
Account: os-sales (the seat's linked user; the card's assignee)
Branch: claude/project-thread-305vwm (the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)
Worktree: objectstack-issue-25382
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
File surface (read on origin/mainf0fdd45; stop on a breach and explain it in the report):
MEASURE FIRST, as the card asks. The first deliverable is the reading of what POST /api/v1/packages with overwrite writes, against what security.accessUpgradeCheck projects. If the reading shows the door can change an access-class item, the build surface is: packages/runtime/src/domains/packages.ts (one gate before the first write, through the same service) plus a door pin beside it, and U2's refusal builder moved where both doors can import it.
If the reading shows the door cannot change any access-class item, the deliverable is a premise report with no PR, handed back to the seat.
Lane note: that build surface is packages/runtime (a domain:cli path by the domain table), not a services package. Both are lanes of this project; the seat decides whether to relabel if a build follows.
os-dev-report
{
"issue": 25382,
"status": "done",
"branch": "claude/project-thread-305vwm",
"pr": null,
"session": "session_01CJusCFt8WinQ16iGnMfkAk (card thread, claim 6364901414)",
"premise_still_valid": false,
"summary": "Premise falsified on origin/main f0fdd45: POST /api/v1/packages with overwrite cannot un-make an access choice, so there is no second unguarded door for ADR-0126 wall 3, and wiring security.accessUpgradeCheck there would refuse overwrites on findings the write never makes. MEASURED, the card's three asks. (1) Where it writes first: packages/runtime/src/domains/packages.ts:1427 protocol.installPackage (fallback limb :1442 registry.installPackage). That writes the package ROW only: metadata-protocol protocol.ts:32756 registry.installPackage(manifest, 'authored') then :32971 sys_packages publish with metadata {}; the registry's own contract says a re-install does not unregister the items an earlier install registered and that this door stores the manifest alone (objectql registry.ts:2849, :3103); the sys_packages rehydrate re-installs manifests only (service-package index.ts:751). No permission set, position, capability, sharing rule, object or field is registered or withdrawn. (2) Where both manifests are in hand: just before :1427 (the body manifest, and registry.getPackage(id).manifest). Neither can carry an access-class item: the body is parsed strictly through PackageInstallBodySchema (spec package-api.zod.ts:517, :279), which refuses permissions as a list, positions, capabilities, sharingRules and object definitions, 400 before any write. And the ADR-0070 D2 gate (:1410, package-writability.ts:77) already refuses an overwrite of a code-booted or marketplace-installed id, so only org-authored packages reach the write. (3) Every caller: @objectstack/client packages.install(m, { overwrite }) (client index.ts install), the objectui console outside this repo; no in-repo CLI or MCP caller. WHY WIRING IT WOULD BE WRONG: the check projects the package's catalog entries and declared sharing rules as replaced by the incoming body (access-upgrade-check.ts:137), and this door's body always ships none, so every switch-off on, or customer reference to, an item the package holds becomes a finding the write does not cause (leg B), on every overwrite of that package. RECOMMENDATION for the seat (re-triage input, not a ruling): close not_planned with this reading; the measured reason is that the door moves no access-class item. No narrower build is owed: a door that later carries collections would need the closed spec body widened first (spec-lane, Clause-② yes), which is where wiring this check belongs. Lane note: had a build been owed, its surface was packages/runtime (domain:cli by the domain table), not a services package.",
"tests": "Probe only, never committed (scratch test in packages/runtime/src/domains, run then deleted; copy kept for the seat in the project share as issue-25382/probe-overwrite-access.test.ts). Rig: real ObjectQL over driver-sql better-sqlite3 in memory, the real HttpDispatcher POST /packages door on the protocol limb (real ObjectStackProtocolImplementation, sys_packages publish as a double), the real SecurityPlugin started on the same engine and its registered security.accessUpgradeCheck; org base com.acme.base v1 installed through the door; the base holds permission set base_sales, position base_lead, capability base.export (registry items owned by the base, as sys_metadata hydration registers them); customer state = a switch-off on base_sales in sys_metadata_activation and a customer set cust_exporter naming base.export. pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 on the probe file, base f0fdd45, exit 0, Test Files 1 passed, Tests 4 passed. LEG A (the write): overwrite to 2.0.0 answers 201, the row reads 2.0.0, and the catalog (permission, position, capability lists with owners) plus the activation ledger are deep-equal before and after; base_sales still resolves with _packageId com.acme.base. LEG B (the check, asked with the two manifests the door holds, and again with the registry fallback): outcome findings, one orphan (permission base_sales, switchedOff true) and one broken reference (cust_exporter missing capability base.export), findingSet security.access_upgrade:34587872cd92f6882da2a7c47bbc9624, both false per leg A. CONTROL: registry.uninstallPackage on the same rig changes the snapshot (base_sales and base.export gone), so leg A's equality is not vacuous. LEG C: overwrite bodies carrying permissions (list), positions, capabilities, sharingRules or an object definition each answer 400 VALIDATION before any write (sys_packages publish not called, row stays 1.0.0). Built first: turbo build of the runtime dependency closure under os-verify-lock, 32 tasks, VERDICT command-exit 0.",
"gates": "n/a: no diff (worktree clean at f0fdd45 after deleting the probe), so dispatch-gates derives no family and nothing is owed locally or in CI.",
"dogfood_battery": "n/a: no PR",
"mcp_calls": "3: mcp__github__issue_read (get, get_comments) on this card, mcp__github__search_pull_requests for in-flight PRs; no write tool",
"api_writes": "3 relay dispatches as objectstack-fleet[bot]: label-write labels_add pm:dispatched, labels_remove pm:queue, assign os-sales (run 39953572584); post-stamped claim comment 6364901414 (run 39953679267); this report comment",
"open_questions": [],
"out_of_scope_findings": []
}
The seat accepts the report as the card's re-triage input, and closes the card as not planned on its measured reason: POST /api/v1/packages with overwrite moves no access-class item, so it is not a second, unguarded door for ADR-0126 wall 3.
The seat re-read the load-bearing sites on origin/mainf0fdd45bbc:
objectql/src/registry.ts near :2847: the registry's own contract says a bare install (POST /api/v1/packages) "stores the manifest alone", and installPackage registers no item.
runtime/src/domains/packages.ts:1410:requireWritablePackage (the ADR-0070 D2 gate) runs before the write. So only an org-authored package id reaches protocol.installPackage (:1427).
spec/src/api/package-api.zod.ts:PackageInstallBodySchema is a union of the wrapped request and the strict manifest. The probe's leg C measures that bodies carrying permission sets, positions, capabilities, sharing rules or an object definition are refused with 400 before any write.
The report's probe, run on that base with real ObjectQL, a SQL driver, the door and SecurityPlugin, passed 4 of 4 checks:
leg A: the catalog and the activation ledger are deep-equal before and after the overwrite;
control:uninstallPackage changes the same snapshot, so leg A's equality is not vacuous;
leg B: the U2 check, asked here, reports an orphan and a broken reference, and leg A shows the write makes neither.
So wiring the check as the card asks would refuse every such overwrite on findings the write never makes. A door that later carries access-class collections would first need the closed spec body widened (spec lane, Clause-②: yes). That card is where wiring this check belongs.
The probe copy is kept in the project share and is not committed. Labels: pm:dispatched off, assignee cleared.
Activity
docs(adr): ADR-0126 wall 3 holds for the access class; ADR-0131 D5 re…
objectstack-fleet commentedon Oct 23, 2026
Claim: card thread ·
domain:servicesseat 1 (seat post #6021, project run by the Sales account) · p2 · the whole card · 2026-10-23T12:29ZSession:
session_01CJusCFt8WinQ16iGnMfkAkAccount:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/project-thread-305vwm(the harness-pinned branch for this session; it names no issue, so the Duplicate Fix Guard's branch-name hint will not match — this claim is the identity record)Worktree:
objectstack-issue-25382Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface (read on
origin/mainf0fdd45; stop on a breach and explain it in the report):POST /api/v1/packageswithoverwritewrites, against whatsecurity.accessUpgradeCheckprojects. If the reading shows the door can change an access-class item, the build surface is:packages/runtime/src/domains/packages.ts(one gate before the first write, through the same service) plus a door pin beside it, and U2's refusal builder moved where both doors can import it.packages/runtime(adomain:clipath by the domain table), not a services package. Both are lanes of this project; the seat decides whether to relabel if a build follows.packages/spec: the wrapped install body is a closedstrictObject, so a new confirm field on it is spec-lane work. If the build needs it, I stop and report.Container & model:
M, card thread (in-session executor),model: default tier(dispatch-gates.mjs --tieron this surface: "no path-derived mandate"),effort: highClause-②: no
Metadata-protocol: none
Basis for the Clause-② line: the measurement changes nothing published. A build that refuses an overwrite narrows the door and is re-declared from the diff; a confirm field on the closed spec body would widen it and is not in this claim.
Responsibility:
packages/runtimedoor code is said to leave ADR-0126 wall 3 unguarded | the marketplace door's step 2c already covers its own path | reached by amanage_metadataholder undersingle, the operator under a wall; measured before any changeThread-read: none
Serial constraints cleared: no open PR names fix(runtime): POST /api/v1/packages with overwrite replaces an installed package without the access upgrade check the marketplace door runs — call security.accessUpgradeCheck before the first write #25382 (search
repo:objectstack-ai/objectstack 25382: only the closed docs PR docs(adr): ADR-0126 wall 3 holds for the access class; ADR-0131 D5 records the single-posture re-slug rule (#24446) #25384); no remote head namesissue-25382. U2 (PR cloud-connection, plugin-security: a marketplace upgrade that would un-make an access choice is refused until it is confirmed (#24446 U2) #25355) has landed.Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
os-dev-report
{
"issue": 25382,
"status": "done",
"branch": "claude/project-thread-305vwm",
"pr": null,
"session": "session_01CJusCFt8WinQ16iGnMfkAk (card thread, claim 6364901414)",
"premise_still_valid": false,
"summary": "Premise falsified on origin/main f0fdd45: POST /api/v1/packages with overwrite cannot un-make an access choice, so there is no second unguarded door for ADR-0126 wall 3, and wiring security.accessUpgradeCheck there would refuse overwrites on findings the write never makes. MEASURED, the card's three asks. (1) Where it writes first: packages/runtime/src/domains/packages.ts:1427 protocol.installPackage (fallback limb :1442 registry.installPackage). That writes the package ROW only: metadata-protocol protocol.ts:32756 registry.installPackage(manifest, 'authored') then :32971 sys_packages publish with metadata {}; the registry's own contract says a re-install does not unregister the items an earlier install registered and that this door stores the manifest alone (objectql registry.ts:2849, :3103); the sys_packages rehydrate re-installs manifests only (service-package index.ts:751). No permission set, position, capability, sharing rule, object or field is registered or withdrawn. (2) Where both manifests are in hand: just before :1427 (the body manifest, and registry.getPackage(id).manifest). Neither can carry an access-class item: the body is parsed strictly through PackageInstallBodySchema (spec package-api.zod.ts:517, :279), which refuses permissions as a list, positions, capabilities, sharingRules and object definitions, 400 before any write. And the ADR-0070 D2 gate (:1410, package-writability.ts:77) already refuses an overwrite of a code-booted or marketplace-installed id, so only org-authored packages reach the write. (3) Every caller: @objectstack/client packages.install(m, { overwrite }) (client index.ts install), the objectui console outside this repo; no in-repo CLI or MCP caller. WHY WIRING IT WOULD BE WRONG: the check projects the package's catalog entries and declared sharing rules as replaced by the incoming body (access-upgrade-check.ts:137), and this door's body always ships none, so every switch-off on, or customer reference to, an item the package holds becomes a finding the write does not cause (leg B), on every overwrite of that package. RECOMMENDATION for the seat (re-triage input, not a ruling): close not_planned with this reading; the measured reason is that the door moves no access-class item. No narrower build is owed: a door that later carries collections would need the closed spec body widened first (spec-lane, Clause-② yes), which is where wiring this check belongs. Lane note: had a build been owed, its surface was packages/runtime (domain:cli by the domain table), not a services package.",
"tests": "Probe only, never committed (scratch test in packages/runtime/src/domains, run then deleted; copy kept for the seat in the project share as issue-25382/probe-overwrite-access.test.ts). Rig: real ObjectQL over driver-sql better-sqlite3 in memory, the real HttpDispatcher POST /packages door on the protocol limb (real ObjectStackProtocolImplementation, sys_packages publish as a double), the real SecurityPlugin started on the same engine and its registered security.accessUpgradeCheck; org base com.acme.base v1 installed through the door; the base holds permission set base_sales, position base_lead, capability base.export (registry items owned by the base, as sys_metadata hydration registers them); customer state = a switch-off on base_sales in sys_metadata_activation and a customer set cust_exporter naming base.export. pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 on the probe file, base f0fdd45, exit 0, Test Files 1 passed, Tests 4 passed. LEG A (the write): overwrite to 2.0.0 answers 201, the row reads 2.0.0, and the catalog (permission, position, capability lists with owners) plus the activation ledger are deep-equal before and after; base_sales still resolves with _packageId com.acme.base. LEG B (the check, asked with the two manifests the door holds, and again with the registry fallback): outcome findings, one orphan (permission base_sales, switchedOff true) and one broken reference (cust_exporter missing capability base.export), findingSet security.access_upgrade:34587872cd92f6882da2a7c47bbc9624, both false per leg A. CONTROL: registry.uninstallPackage on the same rig changes the snapshot (base_sales and base.export gone), so leg A's equality is not vacuous. LEG C: overwrite bodies carrying permissions (list), positions, capabilities, sharingRules or an object definition each answer 400 VALIDATION before any write (sys_packages publish not called, row stays 1.0.0). Built first: turbo build of the runtime dependency closure under os-verify-lock, 32 tasks, VERDICT command-exit 0.",
"gates": "n/a: no diff (worktree clean at f0fdd45 after deleting the probe), so dispatch-gates derives no family and nothing is owed locally or in CI.",
"dogfood_battery": "n/a: no PR",
"mcp_calls": "3: mcp__github__issue_read (get, get_comments) on this card, mcp__github__search_pull_requests for in-flight PRs; no write tool",
"api_writes": "3 relay dispatches as objectstack-fleet[bot]: label-write labels_add pm:dispatched, labels_remove pm:queue, assign os-sales (run 39953572584); post-stamped claim comment 6364901414 (run 39953679267); this report comment",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commentedon Oct 23, 2026
席位核对 · premise falsified · closed not planned · 2026-10-23T12:39Z
domain:servicesseat 1 (seat post #6021) ·os-sales·session_016g7nYP9wdNx1GDa5XDr7Uy· card thread claim6364901414, os-dev-report6365067991(premise_still_valid: false,pr: null)The seat accepts the report as the card's re-triage input, and closes the card as not planned on its measured reason:
POST /api/v1/packageswithoverwritemoves no access-class item, so it is not a second, unguarded door for ADR-0126 wall 3.The seat re-read the load-bearing sites on
origin/mainf0fdd45bbc:objectql/src/registry.tsnear:2847: the registry's own contract says a bare install (POST /api/v1/packages) "stores the manifest alone", andinstallPackageregisters no item.runtime/src/domains/packages.ts:1410:requireWritablePackage(the ADR-0070 D2 gate) runs before the write. So only an org-authored package id reachesprotocol.installPackage(:1427).spec/src/api/package-api.zod.ts:PackageInstallBodySchemais a union of the wrapped request and the strict manifest. The probe's leg C measures that bodies carrying permission sets, positions, capabilities, sharing rules or an object definition are refused with 400 before any write.The report's probe, run on that base with real ObjectQL, a SQL driver, the door and SecurityPlugin, passed 4 of 4 checks:
uninstallPackagechanges the same snapshot, so leg A's equality is not vacuous;So wiring the check as the card asks would refuse every such overwrite on findings the write never makes. A door that later carries access-class collections would first need the closed spec body widened (spec lane,
Clause-②: yes). That card is where wiring this check belongs.The probe copy is kept in the project share and is not committed. Labels:
pm:dispatchedoff, assignee cleared.Generated by Claude Code