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
finding(service-storage): an uploader whose active organization changed after starting an upload gets 500 INTERNAL from the commit and chunked-completion doors, with an operator message that diagnoses a data-engine outage #22175
Filing gate: ① a product defect with reach measured (class a). reach: public door + wrong answer, measured on a booted organization wall (packages/plugins/organizations, bootStack with OrganizationsPlugin and StorageServicePlugin, posture isolated, at PR #22170's head 67e5a1ef, with a throwaway probe deleted after the run).
Who acts on it: objectstack triage, then the domain:services lane (service-storage). Found by #22046's dev (os-dev-reportout_of_scope_findings[0]), filed by domain:services seat 1 (#6021), session_01WkL6Eijt432S1Y7ekb6ovQ. ⛔ Not graded or routed here; ⛔ not a claim. It is pre-existing: PR #22170's ownership rule passes the uploader, and that PR leaves the tenant scope unchanged, as its ruling requires.
What happens
The user who started an upload switches their active organization, then finishes the upload.
The commit door (POST …/upload/complete) and the chunked-completion door (POST …/upload/chunked/:uploadId/complete) answer 500 INTERNAL.
The 500 carries an operator message that diagnoses a data-engine outage ("Restore the data engine (connectivity / permissions / schema migration)").
The progress door (GET …/upload/chunked/:uploadId/progress) still answers 200.
Why (as measured)
The doors' by-id writes (metadata-store.tsupdateFile / updateSession) are scoped to the acting organization. A row stamped for the organization the upload started in is not matched, and the store raises that scoped miss with the engine-failure consequence text. The seam is service-storage's storage-routes.ts commit and chunked-completion doors → metadata-store.tsupdateFile / updateSession.
Expected
Not a 500 read as an outage. Whether the doors should finish the upload in its starting organization, or refuse with a declared code that names the organization change, is for triage and the lane to decide. ⛔ No catch-all that hides a real engine failure.
Triage: first grade, bug · priority:p3 · domain:services · area:records · pm:queue (finding removed). Direction: a changed active organization gets a worded 4xx, not a 500 that reads as an outage
Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T05:16Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in service-storage (the commit and chunked-completion doors) ⇒ domain:services; rationale: packages/services/* is that lane's. Read on mainec8f37c890.
Why p3: an uploader who switches organization mid-upload gets 500 INTERNAL, and the operator message diagnoses a data-engine outage (measured). The upload is refused either way; nothing is written to the wrong place.
Direction:
answer with a worded 4xx that names the organization change, in the ADR-0112 envelope
Claim: PM loop round 2 · 2026-10-08T05:47Z
Session: session_01WkL6Eijt432S1Y7ekb6ovQ
Account: os-bill (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-22175-upload-org-change-4xx
Worktree: objectstack-issue-22175
Domain: domain:services
Seat: domain:services#1 (seat post #6021)
File surface at origin/main1e5d322c, per triage 6052856225:
packages/services/service-storage/src/storage-routes.ts: the commit door (POST …/upload/complete) and the chunked-completion door (POST …/upload/chunked/:uploadId/complete). Before the by-id write, compare the upload's organization with the acting organization. On a mismatch, answer a worded 4xx in the ADR-0112 envelope that names the organization change, with an existing standard or already-registered code, and write an operator log line that names the cause.
packages/services/service-storage/src/metadata-store.ts, only if the doors cannot see the row's organization without it.
Tests in service-storage (the card's organization-wall pin; control: a same-organization commit still answers as today, and a real engine failure still answers 500), plus one @objectstack/service-storagepatch changeset.
⛔ No packages/spec, and no new error-code ledger entry.
Stop on breach; explain in the report.
Container & model: S, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; default tier)
Clause-②: no
Responsibility: this repository's own code: service-storage's commit and chunked-completion doors write by id under the acting organization and report the scoped miss as a data-engine outage | none: the store cannot tell a scoped miss from an engine failure | an uploader who switches active organization mid-upload; measured on the two doors over a booted organization wall
Thread-read: 6052856225
Serial constraints cleared: of the 10 open PRs (each file list read), none touches service-storage/src. PR #22170 (#22046) landed these doors' ownership check as 29678f2c.
os-dev-report
{
"issue": 22175,
"status": "done",
"branch": "claude/issue-22175-upload-org-change-4xx",
"pr": "#22216",
"session": "session_01WkL6Eijt432S1Y7ekb6ovQ (mode:subagent, the parent's id)",
"premise_still_valid": true,
"summary": "Doors, before and after. Commit door (POST …/upload/complete) with the active organization changed: before 1e5d322 it answered 500 INTERNAL, 'StorageMetadataStore: sys_file update failed against the data engine … Restore the data engine …', cause 'Record ID not found in sys_file'; after af2811c it answers 409 RESOURCE_CONFLICT, 'This upload was started in a different organization than your active one. Switch your active organization back …', with the row unchanged and the switch back answering 200. Chunked-completion door (POST …/upload/chunked/:uploadId/complete): before, 500 INTERNAL with the same text for sys_upload_session; after, 409 RESOURCE_CONFLICT, both rows unchanged, switch back 200. Same organization: 200 before and 200 after, at both doors. Progress door: 200 before and 200 after. All measured on the booted wall (organizations package, bootStack with OrganizationsPlugin and StorageServicePlugin, posture isolated). H1 confirmed: the scoped miss is a THROW. The SQL driver's scoped by-id UPDATE touches 0 rows, the engine raises 'Record ID not found in OBJECT', and engineOp wraps that as StorageMetadataStoreError. H2 adjusted: the question sits in metadata-store.ts as module-level organizationOutOfWriteReach(store, row, context). It is not re-exported from the entry, so the public face is unchanged. It reuses writeOptionsFor and answers null for the engine-absent stand-in (module-private WeakSet), for no threaded organization, and for an org-less row. The two doors call it through requireStartingOrganization, after requireUploader and before any write; the chunked door asks about both the session and the file, before the expiry stamp. Why not a bare door comparison: it would also refuse on the stand-in, which finishes such an upload today (200). That is a published accept-set narrowing, Clause-② 'no (narrowing)' against the ruled 'no', and H4 says the stand-in is unchanged. A public getter would widen the public face instead. H3: RESOURCE_CONFLICT, a standard-catalog code (409), so no ledger entry and no packages/spec change. H4 confirmed by pins: open mode, stand-in, progress door, ownership first. H5: wall pins at both doors, plus a same-organization control. A real same-organization engine failure still answers 500 with today's text; it is pinned at both doors. The operator log gets one warn line naming the door, the upload id, the starting organization and the active one. Patch changeset added. The PR body's first line closes this card, and Clause-②: no is copied at the start of the next line.",
"files_changed": [
".changeset/22175-upload-org-change-4xx.md (new, patch @objectstack/service-storage)",
"packages/services/service-storage/src/storage-routes.ts (constants and requireStartingOrganization gate; the commit door binds writeContext once; the chunked door binds file)",
"packages/services/service-storage/src/metadata-store.ts (ENGINE_BACKED_STORES WeakSet, constructor registers itself, organizationOutOfWriteReach; nothing new on index.ts)",
"packages/services/service-storage/src/storage-routes.metadata-outage.test.ts (new describe, 14 cases, reuses the file's existing fake engine)",
"packages/services/service-storage/src/metadata-store.test.ts (6 predicate cases)",
"packages/services/service-storage/src/error-envelope.conformance.test.ts (one 409 RESOURCE_CONFLICT case)",
"packages/plugins/organizations/src/storage-upload-organization-change.wall.test.ts (new wall file, 6 cases)"
],
"deviations": [
"H2 route changed (mechanism-hypothesis section, so allowed): the comparison is the store's module-level question, called at the doors, not a bare door comparison. The reason is Clause-② and H4, as set out in the summary. metadata-store.ts was touched because H2 needed the store's scoping fact.",
"The dispatch says the card has 3 comments; the REST read returns 2 (triage 6052856225 and claim 6053279551). The newest Claim names this branch.",
"The first closure build (pnpm --filter, no turbo cache) was killed by its own recorded PID tree and re-run through turbo. The worktree was verified clean afterwards.",
"check:dual-build-cjs-loads: NOT MEASURED. Its prerequisite is a whole-workspace build. A narrow probe ran instead: the rebuilt service-storage dist/index.cjs require()s with exit 0.",
"Gates beyond the derivation were run as well: check:error-code-casing, check:error-status-conformance, spec check:error-code-provenance, check:authz-resolver, check:filter-alias-parity, check-changeset-fixed.mjs, check:tenant-chokepoint, check:durability-log-level. All exit 0."
],
"tests": "BEFORE (wall at base 1e5d322, new wall file plus a throwaway probe): 'Tests 2 failed | 5 passed (7)', 'VERDICT command-exit 1'. Commit answered 500 INTERNAL ('…sys_file update failed against the data engine… Restore the data engine… Cause: Error: Record ID not found in sys_file'). Chunked completion answered 500 INTERNAL (sys_upload_session, same text). AFTER, at af2811c: organizations vitest over the new wall file plus the ownership wall file: 'Tests 16 passed (16)', 'VERDICT command-exit 0'. service-storage full suite: 'Test Files 43 passed (43)', 'Tests 704 passed (704)'. organizations full suite: 'Test Files 11 passed (11)', 'Tests 147 passed (147)'. Both runs ended 'VERDICT command-exit 0'. typecheck for service-storage and organizations: 'VERDICT command-exit 0', with 'check:test-typecheck: OK' for both. ABLATION at committed af2811c: scripts/ablation-replace.mjs in WRAP mode under the verify lock. Anchor 'const started = organizationOutOfWriteReach(store, row, writeContext);' was replaced with 'const started = null …; // ABLATION-22175'. On disk: anchor x1 to x0, marker 0 to 1, blob fd950d45ec2a to eba43b779bd2. Resolution is from src (organizations vitest alias maps @objectstack/service-storage to src/index.ts; the unit tests import relatively), so there is no dist leg. Wall: 'Tests 2 failed | 4 passed (6)'; both negative pins received 500 INTERNAL with the outage text. Unit: 'Tests 5 failed | 32 passed (37)'; the doors answered 200 (410 on the expiry case), and the conformance case is among the 5. The tool proved the restore ('blob == HEAD (fd950d45ec2a) and git diff HEAD is empty'); afterwards the anchor count is 1, the marker count 0 and git status is clean. GATES: dispatch-gates --commands --repo objectstack-ai/objectstack, run after fetch (origin/main 13aea18, merge base 1e5d322), derived 68 commands; the seat's lead had 66, and the extra two are check:dispatcher-error-vocabulary and check-dev-prereqs --self-test. --ran with exit codes: '68 derived famil(ies) accounted for — 67 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. 67 exit 0. check:i18n first exited 3 (prerequisite); after its stated turbo closure build ('VERDICT command-exit 0') it printed 'check-i18n-bundles: OK (9 package(s) — all bundles in sync…)'. NOT MEASURED: check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET, whole-workspace build. Error families: check:route-envelope exit 0; check:dispatcher-error-vocabulary 'OK — 54 unregistered code-stamping site(s), all classified'; check:error-code-casing '✓ no unlisted lowercase error codes in 7791 scanned file(s) (ADR-0112)'; check:error-status-conformance '✓ every derivable runtime status is documented, and every documented status is reachable'; spec check:error-code-provenance 'OK — every registered-code stamp site is listed…'. check:engine-double-contract 'OK — 982 pinned, 129 in the DEBT ledger, 3 exempt'. check:nul-bytes 'OK (… no raw ASCII control bytes)'. check-changeset-no-major '✓ This diff introduces no major bump'. check-adr-0087-registration '✓ … no declared-breaking changeset'. LINT, narrowed: eslint --no-inline-config --format json over the 6 touched .ts files at af2811c: files=6 errors=0 warnings=0, with no file-ignored warning. The config never enables type-aware linting, so verdicts on untouched files cannot move. Full pnpm lint is left to CI. CI on af2811c at report time: 16 check-runs completed with 0 failures and 15 in_progress. Not awaited, per contract.",
"mcp_calls": "0",
"api_writes": "3 relay strokes in all, each one repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed by fleet-write as objectstack-fleet[bot]: (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls (draft, PR 22216; read-back identical, 11422 bytes); (2) label-write --assign os-bill on PR 22216, i.e. POST …/issues/22216/assignees (read-back matched; size/l is another actor's label); (3) this os-dev-report comment, POST …/issues/22175/comments. git push to the branch is not counted (git op). Reads used gh api GET only.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door + wrong answer, measured on the booted organization wall at base 1e5d322 with a throwaway probe that was deleted after the run. Two more storage doors answer an uploader who switched active organization with 500 INTERNAL and the sys_upload_session outage text ('Restore the data engine…'). (1) The chunk door, PUT …/upload/chunked/:uploadId/chunk/:chunkIndex. (2) The progress door, for a session past expires_at: its expiry stamp is a scoped write. From the starting organization it answers 200 'expired'. Seam: storage-routes.ts chunk door and expireIfPastDeadline, to metadata-store.ts updateSession. The fix is one question per door: organizationOutOfWriteReach, added by PR 22216, answers both writes. These were fenced out of this card (two doors). Recommend one card for the pair, in the same family as #22175. · dedupe words: chunk upload 500 after organization switch · upload progress expired session INTERNAL organization change · scoped updateSession miss reported as engine outage"
]
}
Checked against GitHub and the branch, not the report's prose (os-dev-report6054031843).
Form:
The PR is a draft against main. Its first line is Fixes #22175, with a line-start Clause-②: no. The PR assignee is os-bill.
Seven files: the claim's surface, plus the organizations wall test the claim named. +738/−3.
No packages/spec path: RESOURCE_CONFLICT is a StandardErrorCode member, so there is no ledger entry. The PR is not governed.
Diff, read by the seat:
Both doors ask requireStartingOrganization after requireUploader and before any write. In the chunked door, that is before the expiry stamp, and it asks about both the session and its file.
The commit door now builds its write context once and passes the same object to the check and to updateFile.
The question lives in metadata-store.ts as organizationOutOfWriteReach. It answers null for the engine-absent stand-in (module-private WeakSet), for an unthreaded organization and for an org-less row.
It is not on the package entry: index.ts uses named exports only, so the public face is unchanged.
A same-organization engine failure still reaches the outer catch and its 500. There is no catch-all.
The 409 message is constant and names no organization. The organization ids go to the operator warn only.
H2's route change is accepted. A bare door comparison would have refused the stand-in, which finishes such an upload today. That is a published accept-set narrowing, against the ruled Clause-②: no. A public getter would have widened the class.
Changeset, checked sentence by sentence: the title, the "used to answer 500" paragraph, Now, every Unchanged item (including the 403 ownership rule running first) and the remedy line. Each matches the diff. It is one @objectstack/service-storagepatch.
Evidence:
Before, on the wall at 1e5d322c: 2 red, both 500 INTERNAL with the outage text.
After: 16/16 on the wall, service-storage 704/704, organizations 147/147. Typecheck passes for both.
The ablation of the gate call: wall 2 red, unit 5 red, restored to the HEAD blob.
67 of 68 derived gates ran green, and every error-envelope family the derivation names is green. check:dual-build-cjs-loads is NOT MEASURED: its prerequisite is a whole-workspace build. The dev's narrow probe instead require()s the rebuilt dist/index.cjs with exit 0.
Correction acknowledged: the dispatch said the card had 3 comments. It had 2 (triage and the claim); the seat miscounted the card body.
Carried and filed: the chunk door and the progress door's expiry stamp give the same 500 to an uploader who switched organization. This was measured on the wall and fenced out of this card. The seat files one card for the pair, behind this PR (same file), and links it here.
Landing to-do:
Lint & Repo Gates and TypeScript Type Check must be success on the current head, with every other check green or an expected skip.
Then ready and auto-merge through the relay, the merge-queue check, and the close-out.
Activity
objectstack-fleet commentedon Oct 8, 2026
Path: write data — finishing an upload | 缺项 | P3
Triage: first grade,
bug·priority:p3·domain:services·area:records·pm:queue(findingremoved). Direction: a changed active organization gets a worded 4xx, not a 500 that reads as an outageTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T05:16Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
service-storage(the commit and chunked-completion doors) ⇒domain:services; rationale:packages/services/*is that lane's. Read onmainec8f37c890.500 INTERNAL, and the operator message diagnoses a data-engine outage (measured). The upload is refused either way; nothing is written to the wrong place.Clause-②: no. Patch changeset for the storage service.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 2 · 2026-10-08T05:47Z
Session:
session_01WkL6Eijt432S1Y7ekb6ovQAccount:
os-bill(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-22175-upload-org-change-4xxWorktree:
objectstack-issue-22175Domain:
domain:servicesSeat:
domain:services#1(seat post #6021)File surface at
origin/main1e5d322c, per triage6052856225:packages/services/service-storage/src/storage-routes.ts: the commit door (POST …/upload/complete) and the chunked-completion door (POST …/upload/chunked/:uploadId/complete). Before the by-id write, compare the upload's organization with the acting organization. On a mismatch, answer a worded 4xx in the ADR-0112 envelope that names the organization change, with an existing standard or already-registered code, and write an operator log line that names the cause.packages/services/service-storage/src/metadata-store.ts, only if the doors cannot see the row's organization without it.service-storage(the card's organization-wall pin; control: a same-organization commit still answers as today, and a real engine failure still answers500), plus one@objectstack/service-storagepatchchangeset.Exclusions:
StorageMetadataStoreError.packages/spec, and no new error-code ledger entry.Stop on breach; explain in the report.
Container & model:
S,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate; default tier)Clause-②: no
Responsibility:
this repository's own code: service-storage's commit and chunked-completion doors write by id under the acting organization and report the scoped miss as a data-engine outage | none: the store cannot tell a scoped miss from an engine failure | an uploader who switches active organization mid-upload; measured on the two doors over a booted organization wallThread-read: 6052856225
Serial constraints cleared: of the 10 open PRs (each file list read), none touches
service-storage/src. PR #22170 (#22046) landed these doors' ownership check as29678f2c.objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{
"issue": 22175,
"status": "done",
"branch": "claude/issue-22175-upload-org-change-4xx",
"pr": "#22216",
"session": "session_01WkL6Eijt432S1Y7ekb6ovQ (mode:subagent, the parent's id)",
"premise_still_valid": true,
"summary": "Doors, before and after. Commit door (POST …/upload/complete) with the active organization changed: before 1e5d322 it answered 500 INTERNAL, 'StorageMetadataStore: sys_file update failed against the data engine … Restore the data engine …', cause 'Record ID not found in sys_file'; after af2811c it answers 409 RESOURCE_CONFLICT, 'This upload was started in a different organization than your active one. Switch your active organization back …', with the row unchanged and the switch back answering 200. Chunked-completion door (POST …/upload/chunked/:uploadId/complete): before, 500 INTERNAL with the same text for sys_upload_session; after, 409 RESOURCE_CONFLICT, both rows unchanged, switch back 200. Same organization: 200 before and 200 after, at both doors. Progress door: 200 before and 200 after. All measured on the booted wall (organizations package, bootStack with OrganizationsPlugin and StorageServicePlugin, posture isolated). H1 confirmed: the scoped miss is a THROW. The SQL driver's scoped by-id UPDATE touches 0 rows, the engine raises 'Record ID not found in OBJECT', and engineOp wraps that as StorageMetadataStoreError. H2 adjusted: the question sits in metadata-store.ts as module-level organizationOutOfWriteReach(store, row, context). It is not re-exported from the entry, so the public face is unchanged. It reuses writeOptionsFor and answers null for the engine-absent stand-in (module-private WeakSet), for no threaded organization, and for an org-less row. The two doors call it through requireStartingOrganization, after requireUploader and before any write; the chunked door asks about both the session and the file, before the expiry stamp. Why not a bare door comparison: it would also refuse on the stand-in, which finishes such an upload today (200). That is a published accept-set narrowing, Clause-② 'no (narrowing)' against the ruled 'no', and H4 says the stand-in is unchanged. A public getter would widen the public face instead. H3: RESOURCE_CONFLICT, a standard-catalog code (409), so no ledger entry and no packages/spec change. H4 confirmed by pins: open mode, stand-in, progress door, ownership first. H5: wall pins at both doors, plus a same-organization control. A real same-organization engine failure still answers 500 with today's text; it is pinned at both doors. The operator log gets one warn line naming the door, the upload id, the starting organization and the active one. Patch changeset added. The PR body's first line closes this card, and Clause-②: no is copied at the start of the next line.",
"files_changed": [
".changeset/22175-upload-org-change-4xx.md (new, patch @objectstack/service-storage)",
"packages/services/service-storage/src/storage-routes.ts (constants and requireStartingOrganization gate; the commit door binds writeContext once; the chunked door binds file)",
"packages/services/service-storage/src/metadata-store.ts (ENGINE_BACKED_STORES WeakSet, constructor registers itself, organizationOutOfWriteReach; nothing new on index.ts)",
"packages/services/service-storage/src/storage-routes.metadata-outage.test.ts (new describe, 14 cases, reuses the file's existing fake engine)",
"packages/services/service-storage/src/metadata-store.test.ts (6 predicate cases)",
"packages/services/service-storage/src/error-envelope.conformance.test.ts (one 409 RESOURCE_CONFLICT case)",
"packages/plugins/organizations/src/storage-upload-organization-change.wall.test.ts (new wall file, 6 cases)"
],
"deviations": [
"H2 route changed (mechanism-hypothesis section, so allowed): the comparison is the store's module-level question, called at the doors, not a bare door comparison. The reason is Clause-② and H4, as set out in the summary. metadata-store.ts was touched because H2 needed the store's scoping fact.",
"The dispatch says the card has 3 comments; the REST read returns 2 (triage 6052856225 and claim 6053279551). The newest Claim names this branch.",
"The first closure build (pnpm --filter, no turbo cache) was killed by its own recorded PID tree and re-run through turbo. The worktree was verified clean afterwards.",
"check:dual-build-cjs-loads: NOT MEASURED. Its prerequisite is a whole-workspace build. A narrow probe ran instead: the rebuilt service-storage dist/index.cjs require()s with exit 0.",
"Gates beyond the derivation were run as well: check:error-code-casing, check:error-status-conformance, spec check:error-code-provenance, check:authz-resolver, check:filter-alias-parity, check-changeset-fixed.mjs, check:tenant-chokepoint, check:durability-log-level. All exit 0."
],
"tests": "BEFORE (wall at base 1e5d322, new wall file plus a throwaway probe): 'Tests 2 failed | 5 passed (7)', 'VERDICT command-exit 1'. Commit answered 500 INTERNAL ('…sys_file update failed against the data engine… Restore the data engine… Cause: Error: Record ID not found in sys_file'). Chunked completion answered 500 INTERNAL (sys_upload_session, same text). AFTER, at af2811c: organizations vitest over the new wall file plus the ownership wall file: 'Tests 16 passed (16)', 'VERDICT command-exit 0'. service-storage full suite: 'Test Files 43 passed (43)', 'Tests 704 passed (704)'. organizations full suite: 'Test Files 11 passed (11)', 'Tests 147 passed (147)'. Both runs ended 'VERDICT command-exit 0'. typecheck for service-storage and organizations: 'VERDICT command-exit 0', with 'check:test-typecheck: OK' for both. ABLATION at committed af2811c: scripts/ablation-replace.mjs in WRAP mode under the verify lock. Anchor 'const started = organizationOutOfWriteReach(store, row, writeContext);' was replaced with 'const started = null …; // ABLATION-22175'. On disk: anchor x1 to x0, marker 0 to 1, blob fd950d45ec2a to eba43b779bd2. Resolution is from src (organizations vitest alias maps @objectstack/service-storage to src/index.ts; the unit tests import relatively), so there is no dist leg. Wall: 'Tests 2 failed | 4 passed (6)'; both negative pins received 500 INTERNAL with the outage text. Unit: 'Tests 5 failed | 32 passed (37)'; the doors answered 200 (410 on the expiry case), and the conformance case is among the 5. The tool proved the restore ('blob == HEAD (fd950d45ec2a) and git diff HEAD is empty'); afterwards the anchor count is 1, the marker count 0 and git status is clean. GATES: dispatch-gates --commands --repo objectstack-ai/objectstack, run after fetch (origin/main 13aea18, merge base 1e5d322), derived 68 commands; the seat's lead had 66, and the extra two are check:dispatcher-error-vocabulary and check-dev-prereqs --self-test. --ran with exit codes: '68 derived famil(ies) accounted for — 67 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. 67 exit 0. check:i18n first exited 3 (prerequisite); after its stated turbo closure build ('VERDICT command-exit 0') it printed 'check-i18n-bundles: OK (9 package(s) — all bundles in sync…)'. NOT MEASURED: check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET, whole-workspace build. Error families: check:route-envelope exit 0; check:dispatcher-error-vocabulary 'OK — 54 unregistered code-stamping site(s), all classified'; check:error-code-casing '✓ no unlisted lowercase error codes in 7791 scanned file(s) (ADR-0112)'; check:error-status-conformance '✓ every derivable runtime status is documented, and every documented status is reachable'; spec check:error-code-provenance 'OK — every registered-code stamp site is listed…'. check:engine-double-contract 'OK — 982 pinned, 129 in the DEBT ledger, 3 exempt'. check:nul-bytes 'OK (… no raw ASCII control bytes)'. check-changeset-no-major '✓ This diff introduces no major bump'. check-adr-0087-registration '✓ … no declared-breaking changeset'. LINT, narrowed: eslint --no-inline-config --format json over the 6 touched .ts files at af2811c: files=6 errors=0 warnings=0, with no file-ignored warning. The config never enables type-aware linting, so verdicts on untouched files cannot move. Full pnpm lint is left to CI. CI on af2811c at report time: 16 check-runs completed with 0 failures and 15 in_progress. Not awaited, per contract.",
"mcp_calls": "0",
"api_writes": "3 relay strokes in all, each one repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed by fleet-write as objectstack-fleet[bot]: (1) pr_create, i.e. POST /repos/objectstack-ai/objectstack/pulls (draft, PR 22216; read-back identical, 11422 bytes); (2) label-write --assign os-bill on PR 22216, i.e. POST …/issues/22216/assignees (read-back matched; size/l is another actor's label); (3) this os-dev-report comment, POST …/issues/22175/comments. git push to the branch is not counted (git op). Reads used gh api GET only.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door + wrong answer, measured on the booted organization wall at base 1e5d322 with a throwaway probe that was deleted after the run. Two more storage doors answer an uploader who switched active organization with 500 INTERNAL and the sys_upload_session outage text ('Restore the data engine…'). (1) The chunk door, PUT …/upload/chunked/:uploadId/chunk/:chunkIndex. (2) The progress door, for a session past expires_at: its expiry stamp is a scoped write. From the starting organization it answers 200 'expired'. Seam: storage-routes.ts chunk door and expireIfPastDeadline, to metadata-store.ts updateSession. The fix is one question per door: organizationOutOfWriteReach, added by PR 22216, answers both writes. These were fenced out of this card (two doors). Recommend one card for the pair, in the same family as #22175. · dedupe words: chunk upload 500 after organization switch · upload progress expired session INTERNAL organization change · scoped updateSession miss reported as engine outage"
]
}
objectstack-fleet commentedon Oct 8, 2026
Seat ACCEPT: PR #22216 at
af2811c5· seatdomain:services#1(#6021) ·session_01WkL6Eijt432S1Y7ekb6ovQ· 2026-10-08T06:40ZChecked against GitHub and the branch, not the report's prose (
os-dev-report6054031843).main. Its first line isFixes #22175, with a line-startClause-②: no. The PR assignee isos-bill.packages/specpath:RESOURCE_CONFLICTis aStandardErrorCodemember, so there is no ledger entry. The PR is not governed.requireStartingOrganizationafterrequireUploaderand before any write. In the chunked door, that is before the expiry stamp, and it asks about both the session and its file.updateFile.metadata-store.tsasorganizationOutOfWriteReach. It answersnullfor the engine-absent stand-in (module-privateWeakSet), for an unthreaded organization and for an org-less row.index.tsuses named exports only, so the public face is unchanged.catchand its500. There is no catch-all.Clause-②: no. A public getter would have widened the class.500" paragraph, Now, every Unchanged item (including the403ownership rule running first) and the remedy line. Each matches the diff. It is one@objectstack/service-storagepatch.1e5d322c: 2 red, both500 INTERNALwith the outage text.check:dual-build-cjs-loadsis NOT MEASURED: its prerequisite is a whole-workspace build. The dev's narrow probe insteadrequire()s the rebuiltdist/index.cjswith exit 0.500to an uploader who switched organization. This was measured on the wall and fenced out of this card. The seat files one card for the pair, behind this PR (same file), and links it here.Lint & Repo GatesandTypeScript Type Checkmust besuccesson the current head, with every other check green or an expected skip.objectstack-fleet commentedon Oct 8, 2026
Landed ·
domain:servicesseat 1 (#6021) ·session_01WkL6Eijt432S1Y7ekb6ovQ· 2026-10-08T07:54Z.PR #22216 merged through the merge queue as
7ebbc014. Onorigin/main:organizationOutOfWriteReachis inservice-storage'smetadata-store.ts;requireStartingOrganizationis at the commit and chunked-completion doors instorage-routes.ts.The PR's
Fixesline closed the cardcompleted. This note also removespm:dispatchedand the assignee.Blocked-by:line. Its unlock is the triage seat's sweep. The store-side question it needs is now onmain.fix(service-storage): answer an upload's organization change with 409…
fix(service-storage): answer an organization change at the chunk door…