Skip to content

service-storage: a server-side verb that stores bytes and attaches them to a record (sys_file + sys_attachment), under the parent's organization, quota and access hooks: the prerequisite for inbound-mail attachments #23306

Closed

Description

@objectstack-fleet

Filing gate ③: a prerequisite a ruled card's scope needs and main does not have. Filed by domain:spec seat 3 (#18883) · os-support-ai · session session_012uFegdfR6K6uqvLzt7XsdZ, from the stage-2 dev report on #22871 (open question, option B). ⛔ Not a claim; lane and grade are triage's.

Reader who acts: triage, to grade and route. The landing package is @objectstack/service-storage.

What is missing

No server-side code on main can store a file and attach it to a record. Measured by #22871 stage 2's dev at 175e8d414:

  • sys_attachment has 0 non-test insert sites.
  • sys_file is written only by service-storage's StorageMetadataStore.createFile, behind the upload routes.
  • The kernel storage service (IStorageService) is a blob store. It has no verb for a file row or an attachment link.

Every server-side producer of a file therefore has no declared door: inbound mail first (the next card), then imports and AI output.

What it asks

One server-side verb on service-storage:

It is one writer, so quota, organization and access stay in one path.

Acceptance

Why now

Dedupe

MCP search_issues on this repo, closed included, run in the filing act:


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 13, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, enhancement · priority:p2 · domain:spec · area:files, into pm:queue. One server-side verb that stores bytes and attaches them to a record, under the parent's organization, quota and access hooks

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-13T17:53Z. ⛔ Not a claim, ⛔ not a dispatch.

  2. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (#23306, triage 6151767880: one server-side verb that stores bytes and attaches them to a record) · 2026-10-14T00:33Z
    Session: session_01YRp5z4kK6vgYkrdH97ztsy
    Account: huangyiirene (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-23306-storage-attach-verb
    Worktree: objectstack-issue-23306
    Domain: domain:spec
    Seat: domain:spec#1 (seat post #6017)
    File surface (at origin/main 6d96721fa or later; stop on a breach and explain it in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 23306,
     "status": "done",
     "branch": "claude/issue-23306-storage-attach-verb",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23386",
     "session": "session_01YRp5z4kK6vgYkrdH97ztsy — subagent run: the parent PM's session id, as the dispatch, the relay payload and every commit's Claude-Session trailer carry it",
     "premise_still_valid": true,
     "summary": "StorageServicePlugin now registers the kernel service file-attachments (FILE_ATTACHMENT_SERVICE, contract IFileAttachmentService in @objectstack/spec/contracts, a sibling of IStorageService rather than a member: that contract is the adapters' blob store and none of them holds the data engine). attachFile({ parentObject, parentId, organizationId, filename, mimeType, data }) acts as the system and writes the bytes, one sys_file and one sys_attachment, reusing every check instead of copying it: the upload doors' size verdict (moved, not copied, from storage-routes.ts into storage-limits.ts as uploadSizeRefusal, 413 PAYLOAD_TOO_LARGE), StorageMetadataStore as the sys_file writer, the parent's wall organization via createWallOrganizationResolver (another organization answers 404 RECORD_NOT_FOUND like a missing parent), and the engine's sys_attachment beforeInsert gates (plugin-audit's enable.files gate runs under the system context, 403 FILES_DISABLED; the parent-EDIT gate returns at its system-context line by its own design, checkEdit called 0 times measured). No refusal leaves a committed sys_file: the row is written pending before the bytes and turns committed only in the same engine transaction as the sys_attachment insert; a refused call removes bytes, row and any attachment it inserted outside an owned transaction. H1 holds with that correction; H2 holds, and 'quota' is the per-upload max_upload_mb (no aggregate storage quota exists in open code); H3: sibling contract; H4: pending-first write order plus transaction. main (with PR #23346) was merged before the PR opened.",
     "tests": "Final head ac4fcfdaf, each under os-verify-lock (VERDICT command-exit 0): `pnpm --filter @objectstack/service-storage exec vitest run --maxWorkers=2` → 55 files, 956 tests passed; `pnpm --filter @objectstack/service-storage typecheck` → exit 0, check:test-typecheck OK (0 debt), tsc --listFiles -p tsconfig.test.json counts 55 own test files incl. file-attachment-service.test.ts, the same 55 vitest ran; `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/contracts` → 48 files, 453 tests passed; `pnpm --filter @objectstack/spec typecheck` → exit 0, test-typecheck ledger unchanged; `pnpm --filter @objectstack/spec check:generated` → 14/14 current, declaration stamp match. New suite file-attachment-service.test.ts: 17 tests through the plugin's own init/start/kernel:ready/kernel:bootstrapped, resolved by FILE_ATTACHMENT_SERVICE, real ObjectQL over sqlite :memory:, plugin-audit's real gate (new devDependency, vitest-aliased to source), local adapter in tmpdir, settings max_upload_mb 0.001 (1048 bytes): success shape; 413 / 403 FILES_DISABLED / 404 each asserting code+status and NOTHING left (no sys_file committed or pending, no sys_attachment, no stored key), each beside a passing control; org stamping both directions; commit refusal rolled back in-tx; no-transaction compensation; upload failure; 503 before bootstrapped and with no engine; 7 malformed inputs 400. Ablations, one-off, scripts/ablation-replace.mjs WRAP mode at 00e7f3217 (mutated files byte-identical at ac4fcfdaf), every leg anchor 1→0, blob changed on disk, restore proven blob==HEAD and git diff HEAD empty; tests read the mutated src directly (relative imports; plugin-audit via its source alias), so no dist leg: A1 size verdict ignored → 1 red; A2 cleanup disabled → 4 red; A2b row committed up front → 17 green as predicted (cleanup still removes it; the crash-window half is unpinned, stated in PR); A3 no org wall → 1 red; A4 no transaction claimed owned → 2 red; A5 no compensation → 1 red; A6 armed at kernel:ready → 1 red; A7 organizationId unvalidated → 1 red; A8 shared uploadSizeRefusal admits all → verb 1 red AND storage-limits.test.ts 12 of 18 red (one verdict for doors and verb); A10 plugin-audit enable.files gate never refuses → 1 red. A2 and A6 first attempts were refused by the tool as no-ops (replacement contained the anchor) and re-anchored. ESLint narrowed and measured: `eslint --no-inline-config --format json` over the 8 changed .ts files at 00e7f3217 → 8 files, 0 errors, 0 warnings, 0 fatal; population eslint.config.mjs files '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}' (rest of diff md/mdx/json/yaml; the merge changed none of the 8); invariance: no parserOptions.project, no type-aware linting. Narrowing declared: the full spec local suite was cut by the 10-minute foreground cap (exit 124) and is NOT MEASURED locally; src/contracts was. turbo ls --affected names 82 packages (package graph only, blind to tests reading another package's source from disk; root suites not on it); the spec change is 4 additive exports, 0 removed, no `export *` of @objectstack/spec/contracts anywhere, 0 other declarations of the four names, so CI's farm is the reading. Built output: require dist/index.cjs and import dist/index.js of service-storage both load and list file-attachments.",
     "mcp_calls": "0",
     "api_writes": "2 — (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204); relay run 38506566176 executed POST /repos/objectstack-ai/objectstack/pulls and POST /repos/objectstack-ai/objectstack/issues/23386/assignees; read-back identical (13176 bytes sent and stored, draft, assignee huangyiirene). (2) this os-dev-report comment through scripts/pm/post-stamped.mjs: POST /repos/objectstack-ai/objectstack/issues/23306/comments (relay). 0 label writes: the dispatch names none and the diff publishes, so skip-changeset is not owed. git pushes are not REST writes.",
     "open_questions": [],
     "out_of_scope_findings": [
      "carrier: 承接者:无 · noted, not filed — StorageMetadataStore.engineOp wraps every engine refusal of a sys_file write as StorageMetadataStoreError (an outage text, 'Restore the data engine'), including the engine's own SystemWriteOrganizationRequiredError on a walled deployment. A system attach with organizationId null to a global parent therefore surfaces as that store error rather than a 4xx; by source reading the upload doors do the same for a session with no active organization. Not measured at a public door (no walled boot run). Dedupe words: StorageMetadataStoreError caller refusal, SystemWriteOrganizationRequiredError sys_file, walled upload no active organization.",
      "carrier: 承接者:无 · noted, not filed — the sys_file reap guard confirms a pending row (bytes deleted) without asking findFileHolder. Under this verb that matters only on an engine with no transaction and a process death between the sys_attachment insert and the commit: the pending row would be reaped after its 7d retention and leave the attachment row dangling. Inferred from source, not reproduced. Dedupe words: pending sys_file reap guard holder, dangling sys_attachment pending reap.",
      "carrier: #23307 (the consumer card's dev) · noted, not filed — no README or docs page names the file-attachments service yet; the contract docblock in packages/spec/src/contracts/storage-service.ts is its documentation."
     ],
     "gates": [
      "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 :: ✓ check-adr-0087-registration: this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen).",
      "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 :: ✓ check-adr-0087-registration --self-test: 548 assertions over real temp git repos (real scan()/assertInputs() path)",
      "node scripts/check-changeset-fixed.mjs :: exit 0 :: ✓ .changeset/config.json \"fixed\" group is in sync with 72 public workspace packages.",
      "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 :: ✓ Protocol lockstep: PROTOCOL_VERSION major 18, @objectstack/spec@18.0.0-next.0 — not one major ahead of a stable version, so there is no pre-mode exc",
      "node scripts/check-changeset-no-major.mjs --self-test :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in bot",
      "node scripts/check-ci-filter-parity.mjs :: exit 0 :: OK: all 21 build input(s) turbo.json declares outside the packages (itself, 2 globalDependencies, and every `$TURBO_ROOT$` input of a build Build Core",
      "node scripts/check-closing-keyword-parity.mjs :: exit 0 :: check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 11",
      "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 :: ✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations of the shipped parsers each driven to red.",
      "node scripts/check-comment-mask-adoption.mjs :: exit 0 :: OK  check:comment-mask-adoption — 14 private comment-stripper(s) under packages/** + examples/**, all 14 recorded and every recorded row still reached",
      "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 :: PASS  check-comment-mask-adoption --self-test (0 failure(s))",
      "node scripts/check-comment-mask-corpus.mjs :: exit 0 :: ✓ comment-mask corpus sweep [scripts/js-comment-mask.mjs]: 9042 files, 0 disagree, 0 unparseable, 112.5s (comparator self-test: 26 cases pass).",
      "node scripts/check-dev-prereqs.mjs --self-test :: exit 0 :: ✓ check:dev-prereqs --self-test — every verdict reachable, exclusions and freshness coverage pinned (19 batteries, 76 cases), plus the shared workspac",
      "node scripts/check-doc-frontmatter.mjs :: exit 0 :: ✓ check-doc-frontmatter: 2 content root(s) verified, each against its own floor — content/docs 415, content/blog 4.",
      "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0 :: ✓ check-doc-frontmatter --self-test: 99 assertions — the card's own description observed failing with the parser's message and the FILE line, every ot",
      "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0 :: ✓ route-spelling guard (advisory): population clean — every shape-matched literal spells its ledger row.",
      "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0 :: ✓ check-doc-route-spelling self-test: extraction tidy-up, the variant relation (plural + pinned lexicon, no prefix heuristic), walk wiring (releases/ ",
      "node scripts/check-docs-section-name.mjs :: exit 0 :: ✓ check-docs-section-name: 0 nameless form-section examples — 415 docs file(s) · 1219 TS-family fence(s) · 14 `sections: [` array(s) · 22 section lite",
      "node scripts/check-docs-section-name.mjs --self-test :: exit 0 :: ✓ check-docs-section-name self-test: 85 cases pass (real temp trees on disk; both historical misses reproduced as RED, both arms driven RED, the dupli",
      "node scripts/check-dts-emitted.mjs --self-test :: exit 0 :: check-dts-emitted self-test: all assertions passed.",
      "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 :: ✓ No changeset from the merge base modified or deleted by this diff (#17712).",
      "node scripts/check-empty-changeset.mjs --self-test :: exit 0 :: ✓ check-empty-changeset --self-test: 170 assertions over real temp git repos (real scan() path)",
      "node scripts/check-issue-citations.mjs :: exit 0 :: ✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference).",
      "node scripts/check-keyed-text-bounds.mjs :: exit 0 :: ✓ check:keyed-text-bounds: 111 *.object.ts files under packages/** + apps/** + examples/** (walk is repo-wide; 0 outside), 116 object declarations, 23",
      "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 :: PASS  check-keyed-text-bounds --self-test (0 failure(s))",
      "node scripts/check-osv-exemptions.mjs :: exit 0 :: ✓ 1 OSV exemption(s) in osv-scanner.toml: all carry an unexpired ignoreUntil within 90 days and a reason with an advisory link.",
      "node scripts/check-osv-exemptions.mjs --self-test :: exit 0 :: ✓ self-test passed: valid ledgers accepted, every convention breach rejected.",
      "node scripts/check-platform-object-tenancy-census.mjs :: exit 0 :: ✓ platform-object tenancy census matches the tree: 83 platform-namespace objects, 48 in the machinery's reach, 35 outside it, every exclusion explaine",
      "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 :: ✓ check-platform-object-tenancy-census self-test: all checks pass (83 objects, 35 outside the machinery)",
      "node scripts/check-plugin-teardown-shape.mjs :: exit 0 :: ✓ check:plugin-teardown-shape: 78 Plugin implementation(s) across 8455 source(s) under packages/**; every teardown-shaped method (stop / shutdown / cl",
      "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 :: ✓ check-plugin-teardown-shape self-test: 48 cases pass (real pre-#10375 fixture reds, the repaired file and both delegating-alias directions stay gree",
      "node scripts/check-registry-log-declared.mjs :: exit 0 :: OK: 75 vitest-running package(s) walked, 10 selected as engine-booting, every one declares a recognised registry log level (debug/info/warn/error/sile",
      "node scripts/check-registry-log-declared.mjs --self-test :: exit 0 :: self-test OK: 17 cases + level-vocabulary read + population declaration + real-tree selection floor.",
      "node scripts/check-rest-log-spy-declared.mjs :: exit 0 :: OK: 32 of 286 test file(s) beside packages/rest/src/log.ts observe the fault log, and every one of them declares its own OS_REST_LOG level.",
      "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 :: ✓ the real tree yields a NON-EMPTY observer population that is a strict subset of its test files",
      "node scripts/check-section-landing-index.mjs :: exit 0 :: ✓ check-section-landing-index: 8 section index block(s) enumerate their meta.json pages, in order, both directions (ai, api, automation, data-modeling",
      "node scripts/check-section-landing-index.mjs --self-test :: exit 0 :: ✓ check-section-landing-index --self-test: 31 assertions over synthetic inputs and a temp fixture (real judge()/run() path); every limb -- both shapes",
      "node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0 :: ✅ check-spec-docblock-symbol-anchors: 5964 anchors across 2044 spec sources resolve — 10 symbol (10 declaration, 0 literal), 5950 file-level, 3 cross-",
      "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0 :: ✅ check-spec-docblock-symbol-anchors --self-test: every finding class provoked, comment-prose projection wired, declined shapes counted not missed, re",
      "node scripts/check-system-context-census.mjs :: exit 0 :: check-system-context-census: OK — 137 elevation read sites in 21 packages across 61 files, living in 116 symbol(s); the page cites 129 symbol(s) again",
      "node scripts/check-system-context-census.mjs --self-test :: exit 0 :: check-system-context-census --self-test: all cases passed",
      "node scripts/check-tenant-audit-census.mjs :: exit 0 :: ✓ check-tenant-audit-census: OK -- 228 write call sites certified (155 decidable; 2 tenancy-enabled sites PROVABLY carry no tenant context, 32 more un",
      "node scripts/check-tenant-audit-census.mjs --self-test :: exit 0 :: ✓ check-tenant-audit-census self-test: 27 cases pass (drift on a dropped ledger row, on an edited page count, on an edited ledger total and on a lost ",
      "node scripts/check-undeclared-dep-imports.mjs :: exit 0 :: ✓ check:undeclared-dep-imports: 83 workspace packages under packages/** + apps/** + examples/**, 3094 non-test src files, 2596 @objectstack/* specifie",
      "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 :: PASS  check-undeclared-dep-imports --self-test (0 failure(s))",
      "node scripts/docs-audit/check-affected-docs.mjs :: exit 0 :: undecided ................  97   no in-repo declaration, on a ledger that HAS in-repo route sources — absence and an unreadable spelling are not disti",
      "node scripts/docs-audit/check-drift-comment.mjs :: exit 0 :: ✓ check-drift-comment: 66 cases pass across 5 fixture diff(s).",
      "node scripts/osv-base-relative.mjs --self-test :: exit 0 :: osv-base-relative self-test: 23 case(s), all held (floor 23)",
      "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 :: ✓ self-test passed",
      "node scripts/release-pending-publish.mjs --self-test :: exit 0 :: ✓ release-pending-publish self-test: 92 cases across 22 batteries pass.",
      "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 :: list FROM the list has already gone wrong once: three independent passes partitioned it",
      "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0 :: ✅ 28 ObjectSchema.create example(s) in 231 marked block(s) across 256 prose file(s) in 2 root(s) carry an os validate-clean security posture",
      "pnpm --filter @objectstack/spec run check:api-surface :: exit 0 :: @objectstack/spec public API surface + factory signatures unchanged ✓",
      "pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0 :: ✓ json-schema/.build-input-hash-schema ← e660fe7cefd65cdf…",
      "pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0 :: ✅  check:browser-reachable-entries — 2 declared browser-reachable entries link no zod; 48 bundle(s) scanned, 44 zod link(s) seen elsewhere (instrument",
      "pnpm --filter @objectstack/spec run check:docs :: exit 0 :: ℹ reference descriptions: 116 from the module doc block, 19 doc block + schema names, 62 from schema names alone (no usable doc block)",
      "pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0 :: ✅  no new dual-source exports: 5239 names across 19 entry points — 210 re-exported (single declaration), 0 accepted dual-source (baseline).",
      "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 :: ✓ check:duration-unit-keys — 197 unit-declaring numeric key(s) across 3099 source file(s) all carry their unit in the key name (or in a sibling `unit`",
      "pnpm --filter @objectstack/spec run check:empty-state :: exit 0 :: ✓ all classified (2 closed, 2 open, 4 output, 11 scope)",
      "pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0 :: ⚠️ NOT MEASURED: no callable export on @objectstack/spec/api-assembled, @objectstack/spec/qa.",
      "pnpm --filter @objectstack/spec run check:export-origins :: exit 0 :: ✅  export-origins/ is current: 5452 exports across 19 entry points resolve exactly as recorded.",
      "pnpm --filter @objectstack/spec run check:exported-any :: exit 0 :: ✅  no exported type resolves to `any`: 2440 types + 1460 schemas across 19 entry points.",
      "pnpm --filter @objectstack/spec run check:generated :: exit 0 :: ✓ All 14 generated artifacts are up to date — measured against packages/spec/dist AS FOUND ON DISK — this run did not build it.",
      "pnpm --filter @objectstack/spec run check:liveness :: exit 0 :: ✓ packages/spec/liveness/state-counts/ is current — one shard per governed type, the same 41 row(s) as the README, no count column left in the README.",
      "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0 :: ✓ packages/spec/llms.txt: 98 claim(s) re-derived — every advertised symbol resolves against api-surface/ (19 entry points), every subpath is a real `e",
      "pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0 :: spellings; an unquoted one and an orphan continuation FAIL; a wrong anchor beside a",
      "pnpm --filter @objectstack/spec run check:skill-examples :: exit 3 :: NOT MEASURED: PREREQUISITE NOT MET, @objectstack/client-react and @objectstack/client are not built here; the diff touches no skills example and no client package",
      "pnpm --filter @objectstack/spec run check:skill-refs :: exit 0 :: ✅ 9 generated files in sync with packages/spec",
      "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0 :: 118 strip site(s) in 21 file(s), by class: authorable 1 · unresolved 0 · wire/open 113 · no door 3 · no gate 0 · covered 1",
      "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0 :: ✓ variant/doc gate: 20 discriminated union(s) — 8 governed (every variant mentioned in a bound doc), 12 exempt.",
      "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0 :: ↳ 18 component node(s) also judged against their ComponentPropsMap props schema; 1 skipped (no row for the type — SDUI blocks and custom.* are an open",
      "pnpm check:changeset-gate-self-tests :: exit 0 :: ✓ check-changeset-no-major --self-test: 401 assertions (frontmatter dialects measured against @changesets/parse + the pre/exit exemption switch in bot",
      "pnpm check:corpus-claim-drift :: exit 0 :: check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.",
      "pnpm check:cross-package-test-inputs :: exit 0 :: OK: 30 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob (6 of them on a split \"test:repo\" task); 13 walked ",
      "pnpm check:dispatcher-error-vocabulary :: exit 0 :: [#15723] the ARGUMENT POSITION of new APIError( … ) and APIError.from( … ) IS now in this gate's population — the `apierrorarg` shape. `APIError.from`",
      "pnpm check:doc-anchors :: exit 0 :: ✅ check-doc-anchors --self-test: slug parity, custom ids, duplicate counters, extraction discrimination and both finding classes verified (499 live fr",
      "pnpm check:doc-authoring :: exit 0 :: ✓ doc authoring guard: sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 95422 string(s) read in 1329 parsed source(s) ",
      "pnpm check:docs-audit-scope :: exit 0 :: ✓ scope injection is live: the workflow audits the list handed in as args.handwritten, and refuses an invocation that hands in no scope at all.",
      "pnpm check:docs-redirects :: exit 0 :: check-docs-redirects: OK (apps/docs/redirects.mjs: 98 entries -- 95 page destination(s) resolved against content/docs, 3 wildcard destination(s) resol",
      "pnpm check:docs-single-h1 :: exit 0 :: ✓ check-docs-single-h1: 415 page(s) under content/docs/ carry no body-level `# ` heading (0 subtree(s) excluded, see --list).",
      "pnpm check:docs-spec-enumerations :: exit 0 :: OK the hand-written spec enumerations agree with packages/spec/package.json -- 18 subpath(s) held ORDERED in content/docs/deployment/troubleshooting.m",
      "pnpm check:docs-transcript-drift :: exit 0 :: ✓ check-docs-transcript-drift: 4 declared transcript value(s) across 415 page(s) under content/docs/ equal what the registry derives today, and no und",
      "pnpm check:driver-memory-census :: exit 0 :: check-driver-memory-census: OK — every declaration is ledgered, every ledger entry is live, and every ruled file states \"#6664 census: 2 ruled consume",
      "pnpm check:dts-closure :: exit 0 :: check-dts-closure: 17 built package(s) swept - 89/89 declared declaration file(s) present across 17 package(s); 0 built package(s) declare no declarat",
      "pnpm check:dual-build-cjs-loads :: exit 3 :: NOT MEASURED: PREREQUISITE NOT MET, 53 workspace packages have no dist here; the changed package probed directly: require of service-storage dist/index.cjs and import of dist/index.js both load and list file-attachments",
      "pnpm check:engine-double-contract :: exit 0 :: check-engine-double-contract: OK — 1011 pinned, 122 in the DEBT ledger, 3 exempt.",
      "pnpm check:error-status-conformance :: exit 0 :: ✓ every derivable runtime status is documented, and every documented status is reachable.",
      "pnpm check:gitlink-declared :: exit 0 :: check-gitlink-declared: OK (11372 index entries -- 0 gitlink(s) at mode 160000; no .gitmodules in the index, so nothing is declared; nothing to declar",
      "pnpm check:i18n :: exit 3 :: NOT MEASURED: PREREQUISITE NOT MET, the workspace CLI is not built; the diff touches no translation bundle or object label",
      "pnpm check:i18n-stale-fill :: exit 0 :: check-i18n-stale-fill: OK (11 bundle set(s) — no new stale fills, 0 baselined).",
      "pnpm check:issue-citations :: exit 0 :: ✅ check-issue-citations --self-test: grammar narrowed, every spelling enumerated, qualifier a closed set of repositories, four 404 causes kept apart, ",
      "pnpm check:lean-entry-closure :: exit 0 :: ✓ check-lean-entry-closure: 2 published condition(s) measured from a real load.",
      "pnpm check:logger-receiver-detach :: exit 0 :: OK  every log channel keeps its receiver: 3399 non-test TS file(s) walked, 0 detach(es) on the 5 declared receiver-sensitive sink spelling(s).",
      "pnpm check:manifest-repository-directory :: exit 0 :: ✓ check:manifest-repository-directory — every one of 72 publishable manifest(s) declares repository.directory, and all 75 declarations across 85 track",
      "pnpm check:merge-driver :: exit 0 :: ✓ check-regen-pending self-test passed.",
      "pnpm check:nul-bytes :: exit 0 :: check-nul-bytes: OK (scanned 11363 text file(s) -- 11363 tracked, 0 untracked-not-ignored; skipped 9 binary; no raw ASCII control bytes).",
      "pnpm check:objectql-double-limit :: exit 0 :: baseline key set verified against 713f231: no files added.",
      "pnpm check:objectui-changeset :: exit 0 :: ✓ objectui-range --self-test: all checks passed",
      "pnpm check:org-identifier :: exit 0 :: check-org-identifier: OK (3421 author-facing source file(s), 18 session binding(s) resolved, no removed session.tenantId alias).",
      "pnpm check:override-consistency :: exit 0 :: ✓ 9 published-manifest declaration(s) covered by pnpm-workspace.yaml overrides all resolve to their override targets.",
      "pnpm check:page-declaration-shape :: exit 0 :: blind spot: 1 computed carrier(s) no source scan can enumerate — examples/app-crm/objectstack.config.ts:85.",
      "pnpm check:pm-changeset-deadline-census :: exit 0 :: ✓ changeset-deadline-census --self-test: all cases passed across 5 batteries (what counts as a named target, the controls that make a zero a reading, ",
      "pnpm check:pm-prior-rulings :: exit 0 :: ✓ check-prior-rulings self-test: 155 cases pass (term derivation with its length floor, stopwords and code-span splitting; the governing-text carrier ",
      "pnpm check:published-files :: exit 0 :: ✓ check:published-files — 72 publishable package(s) of 83 workspace member(s) declare a `files` whitelist that covers every entry point plus CHANGELOG",
      "pnpm check:published-readme-links :: exit 0 :: ✓ check:published-readme-links — 226 outbound link(s) across 104 published markdown file(s): 0 root-relative, 0 non-canonical origin(s), 28 docs-site ",
      "pnpm check:query-options-erasure :: exit 0 :: baseline key set verified against 713f231: no files added.",
      "pnpm check:react-page-adapter-contract :: exit 0 :: ✓ check-react-page-adapter-contract: 21 app-showcase page module(s) + 1 content/docs react-page sample(s) (from 397 doc file(s), 2021 fenced block(s))",
      "pnpm check:refd-timer-probe :: exit 0 :: OK  check-refd-timer-probe: 9036 source file(s) swept; the process-global timer probe is read in packages/qa/refd-timer-testkit/src/index.ts and nowhe",
      "pnpm check:role-word :: exit 0 :: check-role-word: OK, no new occurrences of the reserved word.",
      "pnpm check:route-envelope :: exit 0 :: ⚠ ratchet #9559 (option 1: convert onto the shared sendOk/sendError): packages/rest/src/query-multiplicity.ts (unenveloped 1; ticks down only) — the o",
      "pnpm check:service-providers :: exit 0 :: ✓ check:service-providers — 14 slot(s): 12 name an installable workspace package, 2 name none (see REMEDY_DETAIL for those that still ship something).",
      "pnpm check:skill-identifier-liveness :: exit 0 :: check-skill-identifier-liveness OK — Leg 1: 457 citation(s) over 53 published file(s) checked against 129499 implementation word tokens (0 ledgered ex",
      "pnpm check:slot-lookup :: exit 0 :: baseline key set verified against 713f231: no files added.",
      "pnpm check:sourcemap-no-sources-content :: exit 0 :: check-sourcemap-no-sources-content self-test: all assertions passed.",
      "pnpm check:spec-parsed-alias :: exit 0 :: ADR-0122 type-alias convention: 1451 bare z.input aliases, 783 pinned isomorphic, 668 paired with an XParsed. OK",
      "pnpm check:test-source-alias :: exit 0 :: check-test-source-alias OK — 75 packages with tests scanned; 60 registered as still resolving a workspace dep through `dist/`; 54 published subpath(s)",
      "pnpm check:tier-file-adoption :: exit 0 :: OK: 83 workspace package(s) walked, 82 nightly-tier test file(s) on disk (82 e2e, 0 live), owned by 1 package(s); every one reads OS_TEST_TIERS.",
      "pnpm check:turbo-task-graph :: exit 0 :: OK: 46 package-scoped and 9 generic turbo task(s) judged against 83 workspace package(s) — every package-scoped one names a package that exists (or th",
      "pnpm check:type-check-coverage :: exit 0 :: clause scope: GENERATED_COVERED was NOT asked of 3 of them (@objectstack/console, @objectstack/skills, @objectstack/spec-monorepo) -- they declare no ",
      "pnpm check:vendor-export-contract-resolve :: exit 0 :: ✓ better-auth/adapters @ better-auth@1.7.3 (via \"1.7.3\", 1 symbol(s))",
      "pnpm check:vendor-version-stamps :: exit 0 :: check:vendor-version-stamps: OK — 9429 file(s) scanned.",
      "pnpm check:watch-hint-literal :: exit 0 :: ✓ check-watch-hint-literal: 73 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 49, ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECL",
      "pnpm check:where-matcher :: exit 0 :: baseline key set verified against 713f231: no files added.",
      "pnpm check:workspace-manifest-cycles :: exit 0 :: OK: 83 workspace package(s), 550 `workspace:` edge(s) (dependencies=391, devDependencies=153, peerDependencies=5, optionalDependencies=1) — the manife",
      "pnpm check:type-check-debt :: NOT MEASURED: type-check-debt, reason: --re-measure runs tsc over every DEBT-ledger package (a repo-wide sweep the CI type-check lane owns); this diff adds no package and moves no ledger entry; service-storage (no DEBT entry) typechecked directly, exit 0"
     ],
     "line_budget": "n/a",
     "deviations": [
      "Paths beyond the claimed file surface, each forced: content/docs/permissions/tenant-audit-census.mdx and docs/audits/2026-08-tenant-audit-write-call-sites.counts.md (check:tenant-audit-census: the verb's two sys_attachment writes move the census 226 → 228 on the merged tree; regenerated by scripts/tenant-audit-census.mjs --write, with the page's enforced prose figures moved); packages/services/service-storage/package.json, vitest.config.ts and pnpm-lock.yaml (@objectstack/plugin-audit as a test-only dependency so the FILES_DISABLED pin runs the real gate).",
      "pnpm-lock.yaml carries only the 3-line service-storage importer entry, accepted by `pnpm install --frozen-lockfile`; a plain `pnpm install` had also re-resolved an unrelated vitest peer variant (esbuild 0.28.2 → 0.28.1), which was not kept.",
      "A temporary measurement probe (src/zz-probe-23306.test.ts) was committed in 427784fb5 and deleted in 8fc513349; it is not in the PR's net diff.",
      "The storage-routes.ts extraction is a move (size refusal constants, sentence and comparison into storage-limits.ts; buildKey exported in place). origin/main was merged (os-regen-merge.sh) before the PR opened; PR #23346's hunks and these are disjoint; the census conflict was resolved by taking main's side and re-deriving.",
      "The card's 'parent-access hooks' do not run under the system context the card prescribes: installAttachmentAccessHooks returns at its isSystem line by declared design (checkEdit called 0 times, measured). The verb's parent check for a system caller is existence plus the organization wall; the card's three named refusals all run. Reported, not re-ruled.",
      "The harness attribution reminder asked for a model-named Co-Authored-By trailer and an emoji PR footer; AGENTS.md's model-free trailer pair and the session-URL footer were used (AGENTS.md takes precedence; the pre-push hook refuses a model identifier).",
      "The full @objectstack/spec local test project was not measured locally (first attempt cut by the foreground cap, exit 124); narrowed to src/contracts and declared in the PR body."
     ],
     "files_changed": [
      ".changeset/23306-storage-attach-verb.md",
      "content/docs/permissions/tenant-audit-census.mdx",
      "docs/audits/2026-08-tenant-audit-write-call-sites.counts.md",
      "packages/services/service-storage/package.json",
      "packages/services/service-storage/src/file-attachment-service.test.ts",
      "packages/services/service-storage/src/file-attachment-service.ts",
      "packages/services/service-storage/src/storage-limits.ts",
      "packages/services/service-storage/src/storage-routes.ts",
      "packages/services/service-storage/src/storage-service-plugin.ts",
      "packages/services/service-storage/vitest.config.ts",
      "packages/spec/api-surface/contracts.json",
      "packages/spec/export-origins/contracts.json",
      "packages/spec/src/contracts/storage-service.test.ts",
      "packages/spec/src/contracts/storage-service.ts",
      "pnpm-lock.yaml"
     ],
     "ci": "in_progress at report time on ac4fcfdaf (5 success, 5 skipped, 19 in progress, 10 queued)"
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #23386 at ac4fcfdaf (Fixes #23306) · 2026-10-14T03:52Z

    domain:spec seat 1 · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy. The dev report is 6160278988; the claim is 6158175677.

    Read on GitHub, not from the report:

    • PR shape: draft, base main, first body line Fixes #23306, and Clause-②: yes at the start of a line. The body has no other closing keyword next to a card number.
    • Files: 15 changed (+1053 / -66).
    • storage-routes.ts hunks: a move. The size refusal's two constants, its message and its predicate leave for storage-limits.ts as uploadSizeRefusal, and buildKey is exported in place. No catch-all 5xx arm and no metadata-store.ts line is touched.
    • The census pages, read line by line: content/docs/permissions/tenant-audit-census.mdx and docs/audits/2026-08-tenant-audit-write-call-sites.counts.md are the tool's re-derivation: 226 → 228 write sites, the verb's sys_attachment insert and delete. The prose figures move with them: 40 → 41 erased-any sites ("just under a fifth" of 228 still holds), and "121 of 228 (53%)".
    • Changeset: @objectstack/spec and @objectstack/service-storage minor, with Clause-②: yes (widening: …). Checked sentence by sentence against the diff: the call, its five refusals, the pending-first write order, and the size refusal's move.
    • Landing class: check-governed-merges --pr 23386 reads NOT governed, 1119 changed lines.

    Contract review: PASS, record 6160481844 on the PR, at CONTRACT_REVIEW_TIER by an isolated reviewer, head ac4fcfdaf. It includes a security read: the verb, acting as the system, opens no write the upload door would refuse for the same parent and organization.

    File surface, amended by this seat (the dev declared each breach in its report): the claim's surface gains five files, each forced by a gate or by a test dependency.

    • The two census documents, which check:tenant-audit-census requires once the verb adds two writes.
    • packages/services/service-storage/package.json, vitest.config.ts and pnpm-lock.yaml, which add @objectstack/plugin-audit as a test-only dependency so the FILES_DISABLED pin runs the real gate. The lockfile change is the three-line importer entry.

    Deviations, the seat's call:

    • "Parent-access hooks" under the system context: accepted, no re-ruling.
      • The hooks stand in the verb's path, and the verb is armed only after they are installed. Their system-context return is their own declared design, and the card prescribes the system context.
      • The card's three acceptance refusals all run and are pinned: quota (413), files disabled (403), and parent outside the organization (404).
      • The organization wall is the one check the verb owns, built on the package's shared wall resolver.

    For the consumer, #23307:

    Out-of-scope findings:

    Next: the PR touches regenerated paths (packages/spec/api-surface/**, packages/spec/export-origins/**) and is 3 commits behind main. Its dev syncs it through scripts/pm/os-regen-merge.sh. A pure-regeneration move carries this record forward with a Regen-provenance: line; otherwise the review re-runs. Then the PR goes ready with auto-merge armed, once every check on the head is green.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 14, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #23386 → 5e725b8ee (Fixes #23306). The card closed completed on the merge · 2026-10-14T05:05Z

    domain:spec seat 1 (#6017) · huangyiirene · session session_01YRp5z4kK6vgYkrdH97ztsy · holder of claim 6158175677.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

area:filesFiles — upload, download, signed URLs, access derived from the parent recorddomain:specenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions