Skip to content

print page ③ of #8346: a render service with one headless-Chromium driver renders a print page under the requesting user's principal and archives the PDF as a sys_file (M2) #22269

Closed

Description

@objectstack-fleet

Ruled: 6204149204 · OQ5 → B, two placements of the one Chromium driver (a local executable, or a remote browser endpoint; cloud uses the remote one) · maintainer 「22269 同意」

Filed by the domain:spec seat 3 (seat post #18883, session_01RPo7FUd6bSnAfkWMAKi848). This is card ③ of the split that #8346's ruling of record 6051470224 orders (letter B′, maintainer 「8346 B′」). ⛔ Not a claim.

The ruling (verbatim, the clauses this card carries)

M2, the archive: a render service with one engine driver, headless Chromium, which renders the same page under the requesting user's principal (a short-lived credential; no system principal) and stores the PDF as a sys_file attached to the record, downloadable and usable as an email or flow attachment; a deployment without the driver configured reports it loudly at startup, it does not fail silently at the first request. ⛔ No in-process layout driver: a page model needs a browser to render.
③ the render service, the Chromium driver and the service-storage in-process archive entry (domain:services)

What it builds on

What this card does

  1. A render service with one driver, headless Chromium. It renders a bound print page for a record into a PDF.
  2. The requesting user's principal, through a short-lived credential. ⛔ No system principal: a user can never archive what they could not read.
  3. The archive: the PDF is stored as a sys_file attached to the record, through service-storage's in-process archive entry. It is downloadable, and usable as an email or flow attachment.
  4. Loud at startup: a deployment without the driver configured says so at boot, not at the first request.
  5. Refusal cases pinned: no driver; a page that does not declare print; a caller who cannot read the record.

⛔ No in-process layout driver (pdfmake class), per the ruling's Not-taken list.

Order

Card ① (#22158) landed through PR #22193, so this card is workable now. It may be built in parallel with ②; the output should match ②'s on-screen print rendering.

Reader who acts: the domain:services execution seat. The lane is named by the ruling; priority:p2 and target:v18 are inherited from #8346. The contract review is owed, per the ruling's "each card carries its refusal cases and the contract review".

Activity

  1. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling recorded: the Chromium driver gets two placements, a local executable or a remote browser endpoint, and cloud uses the remote one · pm:on-hold → pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-15T23:07Z

    Source. The maintainer answered 「22269 同意」 in this session's live chat on 2026-10-15T22:55Z. The question was the triage seat's: whether a Chromium render service is too heavy for the cloud.

    What was presented.

    • Measured on objectstack-ai/cloud origin/main c8eca869:
      • The tenant runtime (apps/objectos/wrangler.toml) runs on Cloudflare Containers standard-1: 1/2 vCPU and 4 GiB. Production allows at most 5 shards, and each shard serves many environments in one Node process.
      • The control plane (apps/cloud) is a single container, singleton-enam.
      • Both images are node:22-slim, and the build's prune step deletes playwright-core. Neither image has Chromium or a CJK font.
    • The gap in the design round. The report 6059301379 reads "Cloud's runtime shape is NOT MEASURED", and OQ5 B (a remote browser endpoint) was not taken because "No named deployment needs B".
    • The risk. A local Chromium in a shard shares that shard's 1/2 vCPU and 4 GiB with every environment on it. A burst of renders can push the shard out of memory.
    • The options:
      • A: local only, as ruled in 6059363716;
      • B: a remote endpoint for cloud, and local for self-hosted deployments;
      • C: no server side.
    • Recommended: B.

    The ruling (B).

    • OQ5 → B. The driver keeps one engine, headless Chromium; export: PDF / print document generation — re-planned for v18, superseding the #1301 NOT_PLANNED closure #8346 B′ is unchanged, so ⛔ there is still no in-process layout driver. It gets two placements:
      • a local executable (OS_PRINT_CHROMIUM_PATH or the plugin option, as ruled);
      • a remote browser endpoint that the deployment configures.
    • Startup loudness and status() cover both placements. A driver that is absent, unreachable or misconfigured says so at start().
    • Cloud uses the remote placement. The candidate the presentation named is Cloudflare Browser Run. Self-hosted deployments keep the local placement.
    • Cloud's adoption is a cloud card. This card's seat files it once the option's name is on a PR. That follows the B′ split's pattern: cite the declared shape rather than predict it.
    • The short-lived credential leaves the process. It goes to the remote browser, and the maintainer accepted that with this letter. The pins already owed apply to both placements: the TTL, revoke in finally, and the refusal order before any mint or launch.
    • Owed by the build round: a CJK render pin. A print page with Chinese text renders its glyphs, not empty boxes, on each placement CI can reach. The seat names how the remote placement is measured.
    • Unchanged: OQ1–OQ4 and OQ6–OQ11 as ruled in 6059363716, and the grade (p2, domain:services, target:v18).

    State.

    • The restart condition is met. The body's Restart-when: asks for a console pin that names objectui 2312bd9a6b or a descendant. .objectui-sha on main has been 1f23d5b070fa since PR chore(objectui): move the console pin to 1f23d5b070fa, carrying objectstack-ai/objectui#12475 and objectstack-ai/objectui#12493 #23709 (2026-10-15T18:17Z), which is 80 commits ahead of 2312bd9a6b and 0 behind (compare, read in this act).
    • The triage seat missed it for the rounds since 18:17Z, because its unlock scan reads only Restart-when: closed #N. Recorded on the R428 record.
    • OQ11's staging condition holds too: card ② is at the pin.
    • This act: the Restart-when: line is replaced by a Ruled: line, and pm:on-hold → pm:queue.

    ⛔ Not a claim.

  2. objectstack-fleet commented on Oct 15, 2026

    @objectstack-fleet
    ContributorAuthor

    Grade p2 → p1: the maintainer names PDF for 18.0 GA · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-15T23:15Z

    Source. The maintainer said 「PDF 要赶 18.0 正式版」 in this session's live chat on 2026-10-15, under the standing 「发版本优先的都p1」. The parent is #8346, and the same act moves it to p1.

    ⛔ Not a claim.

  3. added
    priority:p1High: required for production / M2
    and removed on Oct 15, 2026
  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Retriage request: the lane for the build round's single PR · domain:services seat 2 (#21118) · os-elon-musk · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T00:31Z. ⛔ Not a claim. pm:queue stays; pm:retriage is added in this act.

    What the seat asks triage to answer: which lane claims the packages/spec member that OQ1 A puts in this card's build, given that the build is one landing.

    Why this seat cannot claim the card as ruled:

    • OQ1 A (seat ruling 6059363716, kept unchanged by the ruling of record 6204149204) declares the short-lived principal-session mint as an optional IAuthService member in packages/spec/src/contracts/auth-service.ts. Read on origin/main 140238a47b: no such member exists yet (git grep -n -i "mint\|principalSession\|shortLived" origin/main -- packages/spec/src/contracts/auth-service.ts → 0; control interface IAuthService → 1).
    • OQ11 B, also unchanged, makes the build one landing: S1 to S4 together, now that card ② is at the pin.
    • This lane's job description (references/lanes/services.md, 范围) holds zero packages/spec: a card that touches it goes to the spec seat. SKILL.md's cross-domain exception path lets triage name ONE lane for a cross-domain PR that cannot be split. The two rules point different ways for this card, so the seat acts on the stricter one and asks.
    • The build is Clause-②: yes under every option set, as the design report measured (6059301379, field 5_the_home.packages_spec): a new published package, a public AuthManager method, a service-storage export, and the IAuthService member. The unlock note 6164922728 read "Clause-②: no"; the claim will declare yes either way, with a contract-tier review before the queue.
    • The design report sizes the build at about 3,300–3,800 lines before the remote placement and the CJK pin. Over 3,000 lines, the landing takes the four-piece path (SKILL.md, 路径分叉), so the maintainer's approval sits on it whichever lane claims.

    Options:

    • (i) Cross-domain exception: this lane claims the whole PR. The claim lists packages/spec/src/contracts/auth-service.ts (one optional member, its test, the api-surface regen) on its file surface, with a cross-lane declaration to the spec seat. One dev, one PR, the fastest route for a p1 GA card. It overrides this lane's zero-packages/spec line for one card.
    • (ii) Split along the lanes (the seat's recommendation). A Seam: card for the spec seat carries S2 whole as a vertical seam: the IAuthService member, the AuthManager mint, and its pins (the better-auth refresh trap on both lanes, the TTL, revoke in finally). This card keeps S1 (the service-storage archive entry), S3 + S4 (@objectstack/service-print, both placements per 6204149204, the CJK render pin, the page binding). Both are dispatched now and built in parallel. This card's PR lands after the seam card, and the seam's acceptance is this card's consumer reading the member. OQ11 B's reason for one landing (a seam with no consumer, for a wait this lane could not bound) no longer holds once both are in flight.
    • (iii) The spec seat claims the whole card as a vertical seam. Most of the diff (a new service package, the Chromium driver, the storage archive) would then sit outside the spec lane.

    Why (ii): it keeps each package with its lane's single writer, puts the contract member under the spec seat's review, and starts the services build at once. (i) is quicker by one hand-off, and triage can name it if the GA date outweighs the lane line.

    Not asked: OQ1–OQ11 and the two-placement ruling stand. This seat does not reopen them.


    Generated by Claude Code

  5. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 16, 2026
  6. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Retriage answered: split along the lanes (the seat's option ii) · S2 goes to the new domain:spec seam card #23772; this card keeps S1 and S3 + S4 in domain:services · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T00:57Z

    Read in this act: the seat's request 6205245677, the design report's size table (6059301379, 7_size), the B′ ruling's lane naming (6051470224: "③ the render service, the Chromium driver and the service-storage in-process archive entry (domain:services)"), and references/lanes/services.md 「零持有 packages/spec」.

    • The seam card auth(spec,plugin-auth): a short-lived principal-session mint, an optional IAuthService member with its AuthManager implementation, the seam #22269's print service consumes (S2 of #22269) #23772 (domain:spec, p1, target:v18, pm:queue) carries S2 whole as a vertical seam: the optional IAuthService member, the AuthManager mint behind it, and its pins (the better-auth refresh trap on both lanes, the TTL, revoke in finally, and the refusals). That is about 560–640 lines, by the report's S2 row plus the spec part.
    • This card keeps:
      • S1, the service-storage archive entry (about 570 lines);
      • S3 + S4, @objectstack/service-print: both placements per 6204149204, the CJK render pin and the page binding.
    • Claim with Clause-②: yes. The new published package and the storage export widen the public surface, so a contract-tier review is owed before the queue, as the B′ ruling's own lane naming implies.
    • Order. The two cards are built in parallel, and this card's PR lands after the seam card's. The seam's acceptance is this card reading the member. OQ11 B's reason for one landing (a seam with no consumer) no longer holds once both are in flight.
    • Size. Without S2, this card is still near the 3,000-line line once the remote placement and the CJK pin are in. If it crosses, it takes the four-piece path (SKILL.md 路径分叉).
    • Labels: pm:retriage is removed. p1, domain:services, target:v18 and pm:queue stand.

    ⛔ Not a claim.

  7. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 16, 2026
  8. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-16T01:47Z
    Session: session_018VqJPGA1NDNSCZsnecsChP (the dispatching seat; the dev runs as a cloud session that seat post #21118 names)
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22269-print-render-service
    Worktree: objectstack-issue-22269 (in the cloud session's container)
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface (at origin/main 6362c66ca8 or later; stop on a breach and explain it in the report):


    Generated by Claude Code

  9. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer from domain:spec seat 1 (#6017) · os-elon-musk · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-16T02:51Z. ⛔ Not a claim.

    The mint this card consumes is accepted and armed. PR #23786 (#23772) has seat ACCEPT 6206618703 and contract review PASS 6206929519; it is ready with auto-merge armed and lands first, per #23772's Order. Three points the review hands to this card's consumer, none blocking the mint:

    1. Pass the request's own context. mintPrincipalSession(context, …) mints for whatever userId the context names, which is the platform's standing in-process trust. This card's PR should hand the mint the incoming request's ExecutionContext unchanged, never one it assembles. This card's review should check it.
    2. Impersonation attribution. ExecutionContext carries no impersonation marker. So a render triggered while an admin impersonates a user mints a session for that user with impersonatedBy unset, and the mint's login audit row names the user alone. The reach is no wider than what the admin already holds, for at most 120 s. Whether a render under impersonation mints at all, or how it is attributed, is decided at this card's door. A server-set marker on the context would be a spec widening, and that is a maintainer decision, not this card's.
    3. Audit rows. Each mint fires the existing session.create.after login writer, and revoke() fires the logout writer, both with no endpoint path. This card decides whether a render is audited differently.

    For a later better-auth bump: the non-extension rests on two facts of better-auth 1.7.3 (the hook's endpoint context arrives through async-local storage, and every updateSession caller targets the context's own session). The three real-better-auth lane pins in plugin-auth are the tripwire.


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 22269,
     "status": "done",
     "branch": "claude/issue-22269-print-render-service",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23811",
     "session": "session_01W2XFCTBfK4LabgqovmvSgK (cloud dev dispatched by domain:services seat 2, session_018VqJPGA1NDNSCZsnecsChP)",
     "premise_still_valid": true,
     "summary": "New package @objectstack/service-print: PrintServicePlugin registers the in-process print service (status(), printRecordToFile). One engine, headless Chromium via playwright-core 1.63.0 (pinned exact, this package only), on two placements: local executable (OS_PRINT_CHROMIUM_PATH / chromiumPath) and remote DevTools WebSocket endpoint (OS_PRINT_BROWSER_ENDPOINT / browserEndpoint), both needing OS_PRINT_CONSOLE_URL / consoleUrl; probed and reported once at start(), endpoint never logged beyond scheme+host. Refusal chain principal -> engine -> page/print/default-record-page -> record read as the caller (RECORD_NOT_FOUND, 403 folded to 404) -> FILES_DISABLED, all before any mint or launch; then mint (TTL 60 s) -> render -> IFileAttachmentService.attachFile -> revoke in finally. Showcase gains showcase_invoice_document (invoice's default record page, declares print) and enable.files on showcase_invoice. BINDING STATE: #23772's mint is not on origin/main; the service reads it through one adapter (src/principal-session.ts, principalSessionMinterOf) and status() reports unavailable until it lands. Measured with #23772 merged locally: its minted session keeps the real console on 'Opening your console...' (both member and admin) while a normal sign-in session renders the print page; see open_questions.",
     "tests": "At head 51858e22b: @objectstack/service-print typecheck + test 59/59 (config, service refusal order with code+status and 'mint/render/archive never called', revoke in finally after failed render and refused archive, TTL, no credential in messages; plugin start() loudness vs a missing executable and a dead ws endpoint carrying a token; real Chromium 141 on BOTH placements: render as the session cookie, A4 MediaBox from @page, CJK ToUnicode pin, TIMEOUT/504 on a page with no print root, remote context+cookie gone and remote browser alive). Dogfood test/print-service-archive.dogfood.test.ts 7/7 on a real showcase boot (unconfigured status, 503, 403 system; 404 unknown page; 400 non-print page; 404 another user's invoice under RLS with caller/system control; archive = one committed sys_file + one sys_attachment). Showcase vitest 413/413, showcase + dogfood typecheck clean. CJK ablation (no code change; Chromium narrowed to a Latin-only fontconfig via FONTCONFIG_FILE): both placements red 'Chinese characters printed as empty boxes ... expected [ '发', '票', '客', '户', '名', '称', ...(12) ] to deeply equal []'; restored env green 7/7. One-time end-to-end (throwaway worktree, never pushed; console built at .objectui-sha f96f771140 via scripts/build-console.sh; #23772 branch merged): with a normally signed-in session my driver rendered /_console/apps/com.example.showcase/showcase_invoice/record/ID on both placements: pdfinfo 1 page 594.96 x 842 pts (A4), pdftotext shows header 'ObjectStack Showcase Ltd · 示例有限公司', Invoice/Amounts sections, Line Items table, footer '感谢惠顾', '1/1'; pdffonts includes WenQuanYiZenHei; 2.9-4.1 s per render. Same run with the minted session: TIMEOUT/504 (console never leaves boot). Measured archive attribution on the real boot: sys_file.owner_id null, sys_attachment.uploaded_by null, created_by null, organization stamped.",
     "gates": "Derived 129 commands (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths) at head 51858e22b; each run with exit captured to a file before any pipe: 128 exit 0, 1 exit 3 = pnpm check:vendor-export-contract-resolve PREREQUISITE NOT MET ('could not install better-auth@1.7.3' via npm in this container) -> NOT MEASURED, CI-owned. --ran reconciliation: '129 derived famil(ies) accounted for - 128 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)'. Findings fixed during the rounds: check:section-landing-index, check:system-context-census (row 47d), check:docs-audit-scope (--write), check:i18n-coverage (page label zh-CN), check:dispatcher-error-vocabulary (refusal helpers made file-local with literal codes), check:llms-txt (count 71->72), check:cross-package-test-inputs (dogfood globs), check:slot-lookup (typed lookups). PR check-runs: in_progress at report time (not waited on, per contract).",
     "mcp_calls": "0",
     "api_writes": "2 - POST /repos/objectstack-ai/objectstack/pulls (draft, via fleet relay pr_create; plus its assignee leg POST /repos/objectstack-ai/objectstack/issues/23811/assignees, same stroke) and this os-dev-report comment POST /repos/objectstack-ai/objectstack/issues/22269/comments via scripts/pm/post-stamped.mjs (relay). Plus git push (not REST) of claude/issue-22269-print-render-service. No label writes (budget names none).",
     "line_budget": "2786 additions + 9 deletions = 2795 changed lines vs merge-base (35 files; pnpm-lock.yaml +78/-1 included). Under 3000, so no 维护者速读 section. The #23772 bind adds roughly 10-20 lines.",
     "files_changed": "packages/services/service-print/** (new, 16 files); packages/qa/dogfood/test/print-service-archive.dogfood.test.ts; examples/app-showcase/{objectstack.config.ts, src/data/objects/invoice.object.ts, src/ui/pages/index.ts, src/ui/pages/invoice-document.page.ts, src/system/translations/index.ts}; content/docs/ui/{print-pages.mdx, index.mdx, meta.json}; content/docs/plugins/packages.mdx; content/docs/protocol/backward-compatibility.mdx; content/docs/permissions/system-context.mdx; .changeset/{config.json, 22269-print-render-service.md}; scripts/publish-smoke-pack.mjs; scripts/docs-audit/handwritten-docs.json; scripts/cross-package-test-inputs.mjs; turbo.json; packages/spec/llms.txt; pnpm-lock.yaml",
     "deviations": [
      "packages/spec/llms.txt edited (outside the claim's surface, which excludes packages/spec/**): the package count 71->72, forced by the new package (check:llms-txt reds otherwise); changeset gains '@objectstack/spec': patch. No other spec change.",
      "turbo.json + scripts/cross-package-test-inputs.mjs + content/docs/permissions/system-context.mdx + scripts/docs-audit/handwritten-docs.json: gate-required registry touches not named in the claim's surface. The cli tests that cite cross-package-test-inputs.mjs (packages/cli/test/*) were not run locally; the edit is one glob in the dogfood row, self-test ran green; CI owns them.",
      "pnpm-lock.yaml: pnpm's own re-resolution also moved packages/apps/account's vitest peer to esbuild 0.28.1 (tool output kept, not hand-edited).",
      "Page reads go through the data engine's schema registry (IObjectQLEngine.registry getItem/getObject), not the metadata service: measured, metadata.get('page', name) answered undefined for the showcase's package page on a real boot.",
      "Archive gap NOT closed (per dispatch): attachFile rows carry no requester attribution and the parent-EDIT gate does not run; the service's bar is READ as caller.",
      "Process: my first two gate-runner passes lost commands to a stdin bug in my loop (a command read the loop's stdin); the third pass ran all 129 with stdin from /dev/null and is the one reported. Two runners briefly overlapped once; logs from that pass were discarded.",
      "Did not subscribe to PR activity (the dispatch contract overrides the platform's auto-watch posture)."
     ],
     "open_questions": [
      {
       "question": "#23772's minted session does not boot the real console (blocks this card's real render; its seam acceptance 'this card reading the member' fails today). Measured with #23772 merged locally: on every get-session read better-auth renews the short session (updatedAt advances 02:44:41.671 -> 42.080 -> 42.117, expiresAt clamped back by holdPrincipalSessionExpiry), and the console loops get-session + organization/get-full-organization + organization/list on 'Opening your console...' for member and admin alike; a normal sign-in session for the same user renders the print page. An experiment aborting the renewal write was declined by this session's permission classifier, so the cause is strongly indicated, not proven.",
       "options": [
        "A: #23772 stops the read-time renewal write for a minted session (abort it, or the signed dont-remember cookie the design report named) and adds a pin that a minted cookie boots the console",
        "B: this card adds a second credential lane (bearer via localStorage) — not recommended: a second lane for one consumer"
       ],
       "recommendation": "A, because the defect is in the mint's read behaviour and every future consumer of the member hits it; this card then binds with no change beyond principal-session.ts."
      },
      {
       "question": "Which page a record prints on. At the pin the console renders a record only on its bound page (default record page), so the service requires the requested page to be the object's default record page (VALIDATION_ERROR/400 otherwise). Real use (an invoice record view plus a separate invoice document) needs an objectui surface: a route that renders a NAMED print page for a record (RecordDetailView calls usePageAssignment(objectName) without pageName).",
       "options": [
        "A: keep the default-page rule until objectui ships a named-page print route, then widen the rule in the same PR that binds to it",
        "B: drop the page parameter and always print the bound page"
       ],
       "recommendation": "A: the caller names the document it expects, refusal is loud, and widening needs no API change."
      },
      {
       "question": "Requester attribution of the archived PDF: attachFile writes sys_file.owner_id null and sys_attachment.uploaded_by null (measured), and skips the parent-EDIT gate (system context). A read-only user can therefore attach a printed PDF to a record they can read but not edit.",
       "options": [
        "A: an IFileAttachmentService input naming the acting user (spec lane), stamped on both rows, with the parent-EDIT gate run for it",
        "B: accept READ as the bar (M2's wording) and system attribution"
       ],
       "recommendation": "A for attribution (audit trail); the EDIT-vs-READ bar is the seat's/maintainer's call."
      },
      {
       "question": "Configuration surface: OS_PRINT_CONSOLE_URL / consoleUrl is a third setting beside the two ruled placements (the browser must be told where the console is; no in-process accessor gives this deployment's reachable URL).",
       "options": [
        "A: keep it explicit (current)",
        "B: default it from OS_BASE_URL"
       ],
       "recommendation": "A: explicit composition; a remote browser cannot reach the default anyway."
      },
      {
       "question": "CJK on CI: the Chromium test file runs only where OS_TEST_PRINT_CHROMIUM_PATH or PLAYWRIGHT_BROWSERS_PATH/chromium exists (it warns 'SKIPPED' otherwise). ubuntu-latest CJK fonts unverified, so Test Core likely skips it: NOT MEASURED in CI.",
       "options": [
        "A: a CI step provisioning Playwright chromium with deps (governed .github/workflows edit, maintainer's)",
        "B: leave it measured in agent containers only"
       ],
       "recommendation": "A when the maintainer agrees; the pin is the ruling's ask on 'each placement CI can reach'."
      }
     ],
     "out_of_scope_findings": [
      "class: b · reach: named producer: os lint on examples/app-showcase warns 'page showcase_invoice_document: sets print but this page property is planned ... (not read YET)' while objectui PageRenderer (pin f96f771140) and @objectstack/service-print read it · evidence: liveness-planned-property in the lint output; packages/spec liveness ledger row for page.print · Seam: spec:PageSchema.print → renderer:PageRenderer, runtime:service-print · dedupe words: page print liveness planned, liveness-planned-property print, page.print ledger",
      "carrier: objectui card ② owner · noted, not filed: on the real PDF the record:details 'Show N empty fields' toggle reaches paper, and record:line_items column headings print field names (description, quantity) rather than labels"
     ]
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    REWORK (patch round 1) → PR #23811 comment 6208156693 · domain:services seat 2 · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T04:34Z. The mint fix is sub-issue #23813 (claimed 6208138185, dispatched); this PR binds after it lands.


    Generated by Claude Code

  12. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 22269,
     "round": "patch round 1 (seat record 6208156693 on PR #23811) — addendum",
     "status": "rework",
     "branch": "claude/issue-22269-print-render-service",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23811",
     "head": "fb36ebb70",
     "session": "session_01W2XFCTBfK4LabgqovmvSgK",
     "premise_still_valid": true,
     "summary": "Item 1 done: the archive runs as the requesting user. One writer: @objectstack/service-storage's FileAttachmentService gains a caller-scoped face, ICallerFileAttachmentService (exported type, same 'file-attachments' slot, no IFileAttachmentService/spec change): assertMayAttach(parentObject, parentId, context) throws the insert's own refusal with no write (PERMISSION_DENIED/403 when ISecurityService.explain says the caller may not create sys_attachment; ATTACHMENT_PARENT_ACCESS/403 from the attach rule's verdict, now an exported function mayEditAttachmentParent shared with the beforeInsert gate), and attachFileAs(input, context) writes sys_file.owner_id = caller and inserts sys_attachment under the caller's context unchanged (gates judge the caller; uploaded_by stamped by the existing hook). The print service calls assertMayAttach in its files step before any mint or launch, archives through attachFileAs, and reports unavailable (503) if the slot cannot attach as the caller (never a system-attributed PDF). Item 2 (re-run with a MINTED session) is NOT done: #23813 is still open. The bind itself landed last round (4aa215ac5) on IAuthService.mintPrincipalSession; when #23813 merges, merge origin/main and run the real render with a minted session on both placements. Status rework = item 2 outstanding by order, not by defect.",
     "tests": "At fb36ebb70: service-storage typecheck + full test 1028/1028 (59 files) incl. 7 new pins in file-attachment-service.test.ts (system/no-user context refused PERMISSION_DENIED/403 on both verbs; attachFileAs writes owner_id and uploaded_by = caller; sharing deny -> ATTACHMENT_PARENT_ACCESS/403 from the real insert gate with nothing left and assertMayAttach answering the same; explain allowed:false -> PERMISSION_DENIED/403 before the attach rule; allow admitted; parent walled in another org -> RECORD_NOT_FOUND/404; att_memo -> FILES_DISABLED/403 as the caller). service-print typecheck + test 63/63 (new: assertMayAttach refusals both codes before any mint/render, files refusal wins over the attach rule, slot without attachFileAs -> 503, archive called with the caller's own context by identity). Dogfood print test 9/9 on a real showcase boot: admin requester -> sys_file.owner_id and sys_attachment.uploaded_by equal ctx.userId; showcase_auditor (reads every invoice, edits none, no attachment create) -> PERMISSION_DENIED/403 before mint/render; real-mint pin as before. Ablations (scripts/ablation-replace.mjs, restore blob == HEAD each): (1) drop the pre-check in print-service.ts (source-imported) -> 'expected vi.fn() to not be called at all, but actually been called 1 times'; (2) insert as system in file-attachment-service.ts, rebuilt + dist preflight -> 'expected null to be <userId>' (uploaded_by); (3) drop the owner_id stamp, rebuilt + dist preflight -> 'expected null to be <userId>'. Void legs declared: my first runs of (2) and (3) without a rebuild stayed green because dogfood reads service-storage's dist (no-op, discarded); a first retry of (3) used a replacement esbuild folded away, so the preflight refused it and nothing ran. Restore legs: dist preflight --absent for every marker.",
     "gates": "132 derived at fb36ebb70 (node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack); every exit captured to a file before any pipe: 132 exit 0. --ran: '132 derived famil(ies) accounted for - 132 run, 0 NOT-MEASURED (a DERIVED zero - all 132 recorded an exit code and none of them is 3)'. Fixed in this round: check:system-context-census (row 47e for requireUserContext), check:slot-lookup (typed ISharingService lookup).",
     "mcp_calls": "0",
     "api_writes": "1 this round - this addendum comment POST /repos/objectstack-ai/objectstack/issues/22269/comments via scripts/pm/post-stamped.mjs (relay); plus git push. Cumulative: pr_create (+assignee leg), 2 os-dev-report comments.",
     "line_budget": "3224 additions + 65 deletions = 3289 changed lines vs merge-base (40 files). Over 3000: the 维护者速读 section is drafted below for the seat to append (see deviations).",
     "files_changed_this_round": "packages/services/service-storage/src/{file-attachment-service.ts, file-attachment-service.test.ts, attachment-access-hooks.ts, storage-service-plugin.ts, index.ts}; packages/services/service-print/{package.json, src/print-service.ts, src/print-service.test.ts}; packages/qa/dogfood/test/print-service-archive.dogfood.test.ts; content/docs/permissions/system-context.mdx (row 47e); .changeset/22269-print-render-service.md ('@objectstack/service-storage': minor); pnpm-lock.yaml (+3: service-print -> service-storage)",
     "deviations": [
      "PR body NOT edited for the 3000-line section: os-dev.md says a dev writes the PR body once and the seat writes later edits (that file wins over the dispatch on conflict). The draft is in maintainer_brief_draft below, ready to append verbatim under a 'patch round 1' mark.",
      "PR body's 'Binding state' paragraph is stale since 4aa215ac5 (bound to IAuthService.mintPrincipalSession); the archive paragraph is stale since this round (now attachFileAs as the caller). Suggested replacement text is in the summary above.",
      "attachment-access-hooks.ts edited (an extraction, behaviour unchanged: the beforeInsert gate calls the same verdict body through mayEditAttachmentParent); service-storage full suite green.",
      "The real-boot ATTACHMENT_PARENT_ACCESS leg is pinned at service-storage's layer (real insert gate, a sharing service answering deny), not in dogfood: in the showcase no member set grants attachment create without invoice edit, so the dogfood reader is refused one step earlier, PERMISSION_DENIED."
     ],
     "open_questions": [
      {
       "question": "The pre-check's PERMISSION_DENIED leg asks ISecurityService.explain({ object: 'sys_attachment', operation: 'create' }) for the caller. It mirrors the CRUD middleware by construction (explain's contract), but it is a second question asked before the insert asks it.",
       "options": [
        "A: keep (refusal before any mint/launch, as ordered)",
        "B: ask only the attach rule up front and let object-permission refusals arrive at the insert, after the render"
       ],
       "recommendation": "A, because the order puts every refusal before the mint, and explain is the contract's 'would the middleware allow this' answer."
      },
      {
       "question": "In the showcase, no non-admin member may create attachments (showcase_contributor, showcase_auditor, the default profile grant none on sys_attachment), so only the admin can print an invoice to a file there.",
       "options": [
        "A: leave the showcase as is (the print service follows the app's permissions)",
        "B: grant sys_attachment create to showcase_contributor so the demo shows a member printing their own invoice"
       ],
       "recommendation": "B in a follow-up showcase change, outside this card."
      }
     ],
     "maintainer_brief_draft": "## 维护者速读(草稿)\n\n(patch round 1 追加 —— 由 dev 起草,席位代为写入 PR 正文)\n\n### 改了什么\n- 新增 `@objectstack/service-print`:把记录的打印页面在无头 Chromium 里渲染成 PDF,并作为附件挂到这条记录上。引擎只有 Chromium 一种,两种部署位置:本机可执行文件(`OS_PRINT_CHROMIUM_PATH`)或远程浏览器端点(`OS_PRINT_BROWSER_ENDPOINT`),另需 `OS_PRINT_CONSOLE_URL`。\n- 以请求人本人的身份渲染:为请求人签发 60 秒的短期会话,打印结束(成功或失败)立即吊销。\n- 本轮新增:归档也以请求人身份写入。`service-storage` 的 `file-attachments` 服务增加\"以调用者身份\"的一面(`assertMayAttach`、`attachFileAs`),同一个写入口;两行记录(`sys_file`、`sys_attachment`)都记名请求人,附件的所有闸门对请求人生效。\n- showcase 增加发票打印页,发票开启附件。\n\n### 为什么改\n- 裁决 #8346 B′(M2)要求服务端生成与控制台打印一致的 PDF 并归档;OQ7 A 要求\"以调用者身份挂附件\"。上一轮用系统身份归档,附件不记名、也绕过了\"需对记录有编辑权\"的规则,本轮按裁决改正。\n\n### 风险与代价(含回滚)\n- 能看到记录但无权编辑、或无权上传附件的用户,现在会在渲染前就被拒绝(403),与他们在界面上手动上传的结果一致。\n- 依赖 #23813(签发的会话目前无法让控制台启动);该修复合入前,真实打印跑不通,服务会以 503 明确拒绝。\n- 新包带入 `playwright-core`(仅此包);未配置时不加载浏览器。\n- 回滚:从部署中去掉 `PrintServicePlugin` 即可;`service-storage` 新增的方法不影响原有 `attachFile`。整体回滚 = revert 本 PR。\n\n### 席位意见\n\n### 你要做的\n- 变更超过 3,000 行,需要你批准合入。\n- CI 是否安装中文字体由你决定(属于 `.github/workflows` 受管面)。\n",
     "out_of_scope_findings": []
    }

    Generated by Claude Code

  13. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 22269,
     "round": "patch round 1, item 2 (the real render with a MINTED session) — addendum",
     "status": "done",
     "branch": "claude/issue-22269-print-render-service",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23811",
     "head": "834de8390",
     "session": "session_01W2XFCTBfK4LabgqovmvSgK",
     "premise_still_valid": true,
     "summary": "Merged origin/main (merge commit fff7ed71c, carries #23813's fix 0cfd0c49bd; lockfile auto-merged, no conflict; the merge also moved .objectui-sha to daeb77fc84dd). The real render now runs end to end through the print service's own path with a session minted by IAuthService.mintPrincipalSession, on BOTH placements, and is committed as a render pin: packages/qa/dogfood/test/print-service-render.dogfood.test.ts. No production code changed this round.",
     "tests": "Render pin at 9252cd175, 6/6 (834de8390 adds only the input declarations for its gate), Console bundle built at the pin daeb77fc84dd with scripts/build-console.sh, Chromium 141 at /opt/pw-browsers/chromium. Setup per placement: a real showcase boot via bootShowcase with StorageServicePlugin (local adapter, temp root), AuditPlugin, createConsoleStaticPlugin(packages/console/dist) and PrintServicePlugin; a node http listener on 127.0.0.1 on a free port hands every request to the booted stack (stack.raw) and back, and consoleUrl is that listener plus /_console. LOCAL: PrintServicePlugin({ chromiumPath }). REMOTE: the test spawns the same executable with '--headless=new --no-sandbox --remote-debugging-address=127.0.0.1 --remote-debugging-port=0 --user-data-dir=TMPDIR', reads 'DevTools listening on ws://…' from its stderr and passes that endpoint as PrintServicePlugin({ browserEndpoint }); the plugin connects to it only through connectOverCDP (the remote path), and the endpoint is never logged. The requester is the seeded admin, because no non-admin showcase profile may create attachments (seat answer Q2: no showcase change). Measured per placement, each an assertion: (1) status() ready with that placement; (2) the console booted as the requester: every get-session the browser made during the print answered user.id === the requester (set equality; at least one); (3) the PDF archived: sys_file committed, mime application/pdf, owner_id === requester; sys_attachment on the invoice with file_id and uploaded_by === requester; the stored bytes start '%PDF-' and their length equals the reported size; (4) the minted session gone after success: its cookie on GET /api/v1/auth/get-session answers [401, 'UNAUTHENTICATED']; (5) a forced failure after the render (the storage service's upload throws once): the refusal propagates, the session is likewise [401, 'UNAUTHENTICATED'], no sys_attachment is left on the invoice, and the refusal message does not contain the minted cookie value. Wall per print: local 6.1 s success / 5.6 s failure, remote 4.8 s / 4.3 s (shared box). Credential scan of the full verbose log: 0 hits for 'session_token', 0 for 'set-cookie'; the 2 'bearer ' hits are boot INFO lines about OAuth over HTTP. The test only reads the minted cookie (via an observing wrapper on the auth service's mint) and never prints it. BIND ABLATION: principal-session.ts 'return { mint: (context, ttlSeconds) => auth.mintPrincipalSession!(context, { ttlSeconds }) };' -> 'return (globalThis as any).ablatedBindMarker;' via scripts/ablation-replace.mjs; service-print rebuilt and 'ablation-dist-preflight @objectstack/service-print ablatedBindMarker' found the marker in dist (the dogfood test itself imports service-print's source); result 6 failed (6): 'expected { state: unavailable, …(3) } to match object { state: ready, placement: local }' and the failure case refused 503 before any render; restore 'blob == HEAD (69671ca8b402) and git diff HEAD is empty', rebuilt, '--absent' preflight passed. After restore, at 9252cd175: dogfood print tests 15/15 (render 6 + archive 9), dogfood typecheck clean, service-print typecheck + 63/63, service-storage file-attachment-service 24/24, showcase 413/413.",
     "gates": "132 derived at 834de8390 (dispatch-gates --commands --repo objectstack-ai/objectstack, no paths), each exit captured to a file before any pipe: 132 exit 0. --ran: '132 derived famil(ies) accounted for - 132 run, 0 NOT-MEASURED (a DERIVED zero - all 132 recorded an exit code and none of them is 3)'. Found and fixed this round: check:cross-package-test-inputs (the render test's existence check on packages/console/dist/index.html, now declared in scripts/cross-package-test-inputs.mjs and turbo.json). An earlier reconciliation I ran against a partial record (19 UNRUN) is void and superseded by this one.",
     "mcp_calls": "0",
     "api_writes": "1 this round - this addendum, POST /repos/objectstack-ai/objectstack/issues/22269/comments via scripts/pm/post-stamped.mjs (relay); plus git push. Cumulative: pr_create (+assignee leg), 3 os-dev-report comments. PR body not edited.",
     "line_budget": "3437 additions + 64 deletions = 3501 changed lines vs merge-base (41 files). The 维护者速读 draft is in the previous addendum (comment 6209378010, field maintainer_brief_draft); with this round its 风险 bullet on #23813 is now resolved.",
     "files_changed_this_round": "merge of origin/main; packages/qa/dogfood/test/print-service-render.dogfood.test.ts (new); scripts/cross-package-test-inputs.mjs and turbo.json (one input each: packages/console/dist/index.html)",
     "deviations": [
      "The render pin needs a built Console bundle and a Chromium; neither is built or provisioned in this suite's CI job, so CI skips it with a SKIPPED warning (NOT MEASURED in CI). It was measured here, at the heads above.",
      "The console + closure build held the verify lock 16m56s, which the lock reported as holder-side starvation; it was one foreground-waited run (build-console.sh plus the dogfood and service-print closures).",
      "The merge moved .objectui-sha (f96f771140 to daeb77fc84dd, from main); the render pin ran against that pin, not the f96f771140 of the earlier measurements.",
      "scripts/cross-package-test-inputs.mjs and turbo.json widened by one entry each (gate-required)."
     ],
     "open_questions": [
      {
       "question": "Should CI build the Console and provision Chromium so the render pin runs on every PR or on the full battery?",
       "options": [
        "A: run it in the full battery (main hourly), which already builds the Console for the Console Pin Gate, by adding Playwright's chromium with deps there (.github/workflows, a governed surface)",
        "B: leave it measured in agent containers only"
       ],
       "recommendation": "A, the maintainer's call (governed surface); it is the only test that crosses mint, Chromium, the real console and the archive in one run."
      }
     ],
     "out_of_scope_findings": []
    }

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 22269,
     "round": "patch round 1, item 2 — CI fix for check:cross-package-test-inputs (addendum)",
     "status": "done",
     "branch": "claude/issue-22269-print-render-service",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23811",
     "head": "19f0174a1",
     "session": "session_01W2XFCTBfK4LabgqovmvSgK",
     "summary": "Fixed the red on 834de83901 the gate's way: (1) respelled the render pin's read to name the bundle entry (new URL('../../../console/dist/index.html', import.meta.url); the directory is its dirname, proven identical to the old spelling); the scan still could not hold the glob on a tree with no bundle, so (2) the dogfood entry in scripts/cross-package-test-inputs.mjs gains heldBy { 'packages/console/dist/index.html': ['packages/qa/dogfood/test/print-service-render.dogfood.test.ts'] }. The glob and its turbo.json input stay. No production code changed.",
     "tests": "At 19f0174a1 on a fresh worktree (no Console bundle, as in CI): dogfood typecheck clean; dogfood print tests 9 passed | 6 skipped (15), the render pin printing 'SKIPPED: no built Console … The real render was NOT measured on this host'. The render pin itself was last measured green 6/6 at 9252cd175 (previous addendum 6212023512); since then only its path spelling changed, and node shows the old and new spellings resolve to the same directory (true).",
     "gates": "Reproduced CI on a fresh worktree at 834de83901: check:cross-package-test-inputs exit 1 with CI's verdict. After the fix: check:cross-package-test-inputs exit 0 and check:turbo-task-graph exit 0, both with no bundle on disk and again with a placeholder packages/console/dist/index.html (exit 0; placeholder removed); check-cross-package-test-inputs --self-test exit 0. Full derived set at 19f0174a1: 132 commands, each exit captured before any pipe, 132 exit 0; --ran: '132 derived famil(ies) accounted for - 132 run, 0 NOT-MEASURED (a DERIVED zero - all 132 recorded an exit code and none of them is 3)'.",
     "mcp_calls": "0",
     "api_writes": "1 this round - this addendum via scripts/pm/post-stamped.mjs; plus git push. PR body not edited.",
     "line_budget": "3446 additions + 64 deletions = 3510 changed lines vs merge-base (41 files).",
     "deviations": [
      "Why my earlier local run exited 0 on 834de83901 and CI did not: my worktree then held the built Console (packages/console/dist, from scripts/build-console.sh), so the gate's roster saw a real file at the declared path; CI checks out with no bundle, the path names nothing on disk, and the glob read as unheld. Reproduced by running the gate on a fresh worktree.",
      "The render pin is not re-measured at 19f0174a1 (rebuilding the Console costs about 17 minutes of the shared lock); the change since its green run is the path spelling only, proven equivalent."
     ],
     "open_questions": []
    }

    Generated by Claude Code

  15. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    REWORK (patch round 2) → PR #23811 comment 6213065863 · domain:services seat 2 · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T10:03Z. Contract record 6213039702 PASS at 19f0174a13; one item owed before landing and five small fixes ride one round.


    Generated by Claude Code

  16. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 22269,
     "round": "patch round 2 (REWORK 6213065863 items 1-6, amendment 6213089444 item 7) — addendum",
     "status": "done",
     "branch": "claude/issue-22269-print-render-service",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23811",
     "head": "f653bf5a7",
     "session": "session_01W2XFCTBfK4LabgqovmvSgK",
     "summary": "All seven items done; no new feature or surface beyond the ordered refusals. (1) Cookie-redaction pin in service-print's Test Core suite with an injected launcher (new src/chromium-driver.unit.test.ts, no browser): goto rejects with the cookie value -> INTERNAL_ERROR/500 carrying '[redacted]' and not the value; also a remote endpoint token is redacted. (2) revoke: the print's finally now calls revokeQuietly — a rejecting revoke never replaces the outcome (success stays success; the original refusal wins) and is logged at error naming the user and a sha256 fingerprint (12 hex) of the session token, never the token or cookie; the 60 s TTL is named as the bound. (3) chromium-driver renderPdf runs open, newContext, addCookies, newPage, goto, the ready wait, the fonts wait and page.pdf inside ONE deadline (Promise.race with the budget); a stall answers TIMEOUT/504 within the budget and the cleanup closes what was opened. (4) CHOSEN: move the re-probe after step 5. Step 2 still refuses an unconfigured/misconfigured engine or a missing service; an engine whose last probe failed (with everything else composed) is re-probed only after the files and attach checks, so no browser is launched or connected before every refusal; the changeset and print-pages.mdx say so. (5) Docs/TSDoc: a Printing section with the three OS_PRINT_* rows in content/docs/deployment/environment-variables.mdx; print-pages.mdx's refusal table gains the two step-5 rows (PERMISSION_DENIED may not create attachments; ATTACHMENT_PARENT_ACCESS) plus the re-probe, 30 s budget and revoke notes; the two stale FileAttachmentService.mayAttach references -> assertMayAttach; organizationOfParent's doc now says system for attachFile, caller for attachFileAs; credential hygiene: wss:// off this host and ws:// only on loopback (config.ts header, print-pages.mdx, env-var row), and a consoleUrl with userinfo is REFUSED in code as misconfigured without echoing the credentials (pinned in config.test.ts). (7) MEASURED first in service-storage's real harness (caller tenant org_a, input org_b): parent in org_a -> RECORD_NOT_FOUND/404 (already walled); GLOBAL parent -> the sys_file row was first written in org_b, then the caller-context insert failed with an incidental VALIDATION_FAILED and no status: not cleanly walled. So attachFileAs now refuses up front, before anything is read or written, any organizationId other than the caller's own (context tenantId, or null when it names none) with PERMISSION_DENIED/403; pinned for the global parent, an org_a parent and a null input.",
     "tests": "At f653bf5a7: service-storage typecheck + test 1029/1029; service-print typecheck + test 73/73 (new: 4 driver unit pins, 3 re-probe-order pins, 2 revoke pins, 1 userinfo pin; the real-Chromium driver file ran on both placements against the fixture console, covering the restructured render); dogfood typecheck clean; dogfood print tests 9 passed | 6 skipped (the render pin skips with its warning: no Console bundle in this fresh worktree). Ablations (scripts/ablation-replace.mjs; both test files import source, so no dist leg): (A) chromium-driver.ts secrets list without input.cookie.value -> 1 failed: \"expected 'The print engine failed at the local …' to contain '[redacted]'\"; restore 'blob == HEAD (58598ba5a5f8) and git diff HEAD is empty'. (B) file-attachment-service.ts org check gated on an unset global -> 1 failed: \"expected [ 'VALIDATION_FAILED', undefined ] to deeply equal [ 'PERMISSION_DENIED', 403 ]\"; restore 'blob == HEAD (9e935c0f318a) and git diff HEAD is empty'.",
     "gates": "132 derived at f653bf5a7, each exit captured before any pipe: 132 exit 0. --ran: '132 derived famil(ies) accounted for - 132 run, 0 NOT-MEASURED (a DERIVED zero - all 132 recorded an exit code and none of them is 3)'.",
     "mcp_calls": "0",
     "api_writes": "1 this round - this addendum via scripts/pm/post-stamped.mjs; plus git push. PR body not edited; no workflow edits.",
     "line_budget": "3771 additions + 68 deletions = 3839 changed lines vs merge-base (43 files).",
     "deviations": [
      "The real render through the real Console was not re-run at f653bf5a7 (a Console build costs about 17 minutes of the shared lock). The render path changed (item 3, the deadline restructure); it is covered at this head by the real-Chromium driver tests on both placements (fixture console, cookie-gated print root, A4, CJK pin, timeout) and by the injected-launcher stall pins. Last real-console run: 6/6 at 9252cd175.",
      "Item 7's refusal is PERMISSION_DENIED/403 rather than the insert's own tenant refusal: measured, the insert path has no tenant refusal for this case (it failed late with VALIDATION_FAILED and no status, after a sys_file was already written in the other organization)."
     ],
     "open_questions": [
      {
       "question": "Item 4: re-probe after step 5 (chosen) vs. narrowing the wording",
       "options": [
        "A: re-probe after step 5 (done): the sentence 'no browser before any refusal' stays literally true",
        "B: keep the early re-probe and narrow the docs"
       ],
       "recommendation": "A, recorded as chosen."
      }
     ],
     "out_of_scope_findings": []
    }

    Generated by Claude Code

  17. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 22269,
     "round": "patch round 3 (REWORK 6214660811) — addendum",
     "status": "done",
     "branch": "claude/issue-22269-print-render-service",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23811",
     "head": "78e50355a",
     "session": "session_01W2XFCTBfK4LabgqovmvSgK",
     "summary": "Items 1-3 done; no new feature or surface. (1) Bounded cleanup: every open now returns a disposer; each close in the cleanup waits at most CLOSE_GRACE_MS (2 s). Remote: the context is closed, then the CDP connection (playwright opens the context with disposeOnDetach, so dropping the connection disposes it even when the close never answers). Local: to get a process handle, the local placement now launches through chromium.launchServer({ executablePath }) and connects to it (same playwright-core 1.63.0, same headless Chromium, still one browser per render); disposal closes the connection, then the server, and kills the process (BrowserServer.kill) when the close does not settle. The abandoned render disposes whatever it receives after the deadline (an open that settles late is closed and never given a context). The driver header comment now claims what the pins hold: one deadline over every step, a bounded cleanup after it, nothing opened late kept. (2) No raw console URL: describeConsoleUrl gives scheme and host, or '(a value that is not a URL)'; used by the two refusals after the userinfo check and by start()'s info line. (3) The revoke-failure log is pinned through the PLUGIN (new src/print-service-plugin.revoke-log.test.ts, playwright-core mocked by a staged launcher so it runs on every host): a failed revoke after a successful print reaches the plugin's logger at error, by fingerprint, without the token.",
     "tests": "At 78e50355a: service-print typecheck + test 79/79 (new: remote close-never-settles -> TIMEOUT/504 within budget + grace with context and connection closes attempted; local close-never-settles -> TIMEOUT/504 within budget + grace and the server killed; late open -> closed after it arrives, newContext never called; console URL with userinfo and a non-numeric port -> '(a value that is not a URL)', 'topsecret' in no reason, log line or status; console URL with a query described as scheme and host; the plugin revoke-log pin). The real-Chromium driver tests ran on both placements through the new local launchServer path. Dogfood typecheck clean; dogfood print archive 9/9. REAL RENDER re-measured at 78e50355a with the Console rebuilt at daeb77fc84dd: print-service-render.dogfood.test.ts 6/6 on both placements (verbose: engine ready; prints through the real console as the requester with both rows naming them and the session gone; forced failure still revokes and leaves nothing); 0 'session_token' hits in the log. Ablations (scripts/ablation-replace.mjs; tests import source): (A) the abandoned-render check gated on an unset global -> 1 failed: 'expected +0 to be 1' (the late browser is never closed); restore 'blob == HEAD (1ee266530c19) and git diff HEAD is empty'. (B) the plugin's 'logger: ctx.logger' removed -> 1 failed: 'expected [] to have a length of 1 but got +0'; restore 'blob == HEAD (628070c93c9b) and git diff HEAD is empty'.",
     "gates": "132 derived at 78e50355a, each exit captured before any pipe: 132 exit 0. --ran: '132 derived famil(ies) accounted for - 132 run, 0 NOT-MEASURED (a DERIVED zero - all 132 recorded an exit code and none of them is 3)'.",
     "mcp_calls": "0",
     "api_writes": "1 this round - this addendum via scripts/pm/post-stamped.mjs; plus git push. PR body not edited; no workflow edits.",
     "line_budget": "4074 additions + 68 deletions = 4142 changed lines vs merge-base (44 files).",
     "deviations": [
      "The local placement's launch moved from chromium.launch to chromium.launchServer + chromium.connect, the one way playwright-core exposes the browser process to kill (BrowserServer.kill). Same library, version and engine; measured on a real Chromium through the driver suite and the real-console render on both placements.",
      "The Console build for the render held the verify lock 12m42s; gates ran unlocked beside it.",
      "The record's other notes (a storage pin for a tenant-less null caller, the whitespace-only tenantId predicate, two wording lines) were not carried: no hunk this round touches them."
     ],
     "open_questions": [],
     "out_of_scope_findings": []
    }

    Generated by Claude Code

  18. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
     "issue": 22269,
     "round": "patch round 3 — CI addendum (Test Core 6/6 red at 78e50355a2)",
     "status": "done; CI re-runs on the merge head",
     "branch": "claude/issue-22269-print-render-service",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/23811",
     "head": "5a16f29db (merge of origin/main into 78e50355a; no conflict, no code change on this PR's side)",
     "session": "session_01W2XFCTBfK4LabgqovmvSgK",
     "ci_failure": {
      "check": "Test Core (6/6), job 116959459657, run 38896876200, event on head 78e50355a2 itself (not a merge ref)",
      "failed_task": "@objectstack/metadata-protocol#test (pnpm run test exited 1)",
      "recoverable_output": "none: the retained log starts at 12:14:31 inside @objectstack/runtime's output, after metadata-protocol's own lines; the check run carries no annotations (output summary/text empty); check-test-completeness reported 'OK (13 of 21 scheduled package(s) reported ... 31843 test(s) declared and all accounted for)'.",
      "root_cause": "NOT established. It does not reproduce, and nothing in this PR reaches the package: metadata-protocol does not import service-print, service-storage or the showcase (its turbo test inputs are its own tree plus scripts/check-durability-degradation-log-level.mjs; this PR touches none of them). I am not calling it a flake; I am saying the failing test is unknown and the evidence available does not tie it to this diff.",
      "reproduction": [
       "At 78e50355a exactly (the CI head), after install + build of the package's closure: pnpm --filter @objectstack/metadata-protocol test -> exit 0, Test Files 242 passed | 3 skipped (245), Tests 20595 passed | 19 skipped.",
       "At 5a16f29db (merged with origin/main): same command -> exit 0, Test Files 242 passed | 3 skipped (245), Tests 20598 passed | 19 skipped.",
       "A load run beside @objectstack/runtime's suite was cut by my own background time limit (SIGTERM, no failing test printed) — not counted as evidence either way."
      ],
      "on_plain_main": "Not run separately: the PR-side checkout passes, so there is no failing test name to look for on main. main's own CI runs around 12:30-12:45 were cancelled by newer pushes and do not answer it."
     },
     "tests": "At 5a16f29db: service-print typecheck + test 79/79; service-storage typecheck + test 1029/1029 (59 files); metadata-protocol 242 files passed as above.",
     "gates": "Re-derived at 5a16f29db: 132. First pass 128 exit 0 + 4 exit 3 (prerequisite: built dists missing in a fresh worktree - check:skill-examples, check:dual-build-cjs-loads, check:i18n, check:i18n-coverage); after a full build those 4 exit 0. --ran: '132 derived famil(ies) accounted for - 132 run, 0 NOT-MEASURED (a DERIVED zero - all 132 recorded an exit code and none of them is 3)'.",
     "api_writes": "git push (merge commit) + this addendum via scripts/pm/post-stamped.mjs. PR body not edited; PR stays draft; no workflow edits.",
     "line_budget": "4074 additions + 68 deletions = 4142 changed lines vs origin/main (44 files), unchanged.",
     "deviations": [
      "A second addendum this round (the first, comment 6215865756, was posted before the red was reported); this one carries the new head and the CI finding as the seat asked."
     ],
     "open_questions": [
      "If the fresh CI on 5a16f29db reds metadata-protocol again, the seat may want the run-summary artifact (test-core-run-summary-6-of-6) read by someone with artifact access: this session cannot download Actions artifacts, and the log cap hides the test output."
     ],
     "out_of_scope_findings": []
    }

    Generated by Claude Code

  19. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23811 comment 6218191492 · domain:services seat 2 · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T14:55Z. 4,142 changed lines is over the 3,000-line threshold, so this waits for a human approval by os-zhuang or hotlong. The PR stays draft, labelled needs-user-decision, with review requested from both.


    Generated by Claude Code

  20. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Human approval recorded · domain:services seat 2 · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-17T00:41Z.

    os-zhuang approved PR #23811 (review 5555060516), marked it ready, armed auto-merge, and it entered the merge queue at 2026-10-17T00:29Z. That is the human-merge path this 4,142-line PR owes (check-governed-merges.mjs: over the 3,000-line line). needs-user-decision is cleared in this act. Landing is read when the queue merges it, against the seat record 6218191492.


    Generated by Claude Code

  21. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 2 (#21118) · os-elon-musk · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-17T01:01Z.

    Landed: PR #23811 (Fixes #22269) merged through the merge queue as b15f8a1997. That commit is on origin/main (git merge-base --is-ancestor).

    • Human-merge path, as owed: 4,142 changed lines, over the 3,000-line line. os-zhuang approved it (review 5555060516) and enqueued it at 2026-10-17T00:29Z, and it merged at 2026-10-17T01:01Z.
    • The seat record of the review: 6218191492.
    Probe on origin/main Reading
    packages/services/service-print/src/ source and test files 12
    service-print/src/chromium-driver.ts (the one headless-Chromium driver) present

    What an admin sees: a print page renders through one headless-Chromium driver, under the requesting user's principal, and is archived as a sys_file PDF (M2, card ③ of #8346). Its follow-ups were filed from the ACCEPT: objectui#12715, objectui#12717, #23968 and #23985. E1 and E2 (CI Chromium and Console; CJK fonts) remain the maintainer's.

    pm:dispatched is cleared and the assignee is released in this act. The dev session is archived.


    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

No one assigned

    Labels

    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedenhancementNew feature or requestpriority:p1High: required for production / M2target:v18

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions