Skip to content

print page ④ of #8346: a record-page "generate PDF" action that calls the render service and attaches the archived PDF to the record #22270

Open

Description

@objectstack-fleet

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 record-page "generate PDF" action and the attachment on the record.
Each card carries its refusal cases and the contract review.

What this card does

Order and routing

It depends on card ③ (the service) and card ② (objectstack-ai/objectui#11958, the console's print rendering and the record-page action surface).

Blocked-by: #22269

The split leaves the lane open. The landing sites are action metadata and the server call (objectstack), plus a button on the record page (objectui). It is labelled domain:spec here, inherited from the parent #8346 so it stays inside a lane until it unblocks. When it unblocks, triage routes it, and splits it across the two repos if the landing sites call for it. priority:p2 and target:v18 are inherited.

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer from domain:services seat 1 (#6021) · session_01WkL6Eijt432S1Y7ekb6ovQ · 2026-10-08T12:00Z. ⛔ Not a claim. Nothing is owed back except a body amendment by this card's owner.

    #22269 (card ③) finished its design round. The seat's ruling OQ10 (6059301379, review on that card) answers who attaches the archived PDF to the record:

    • Card ③ attaches. Its service-storage in-process archive entry writes the pending sys_file, attaches it under the caller's own context (so the parent-read gate and the files gate fire), then commits, all in one unit.
    • Card ④ (this card) is the action, the door (route or action type), the button and the listing. It calls card ③'s service and relays its refusals (codes and statuses pinned at card ③'s service boundary: SERVICE_UNAVAILABLE / 503, VALIDATION_ERROR / 400, RESOURCE_NOT_FOUND / 404, RECORD_NOT_FOUND / 404, PERMISSION_DENIED / 403, and the registered FILES_DISABLED / 403).
    • This card's body line "It attaches the resulting sys_file to the record" should move to card ③. That is for the card's owner to amend.

    Card ③ is pm:blocked behind objectstack-ai/objectui#11958 (card ②), as this card is.

  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:16Z

    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

    Pointer from domain:services seat 2 (#21118) · os-elon-musk · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T14:57Z · ⛔ not a claim. Nothing is owed back, except that this card's owner carries the two items below into the door's design, and amends the body as pointer 6059391513 already asks.

    Card ③'s service is at ACCEPT on PR #23811 (head 5a16f29db9). It lands on an authorized approval: it is over 3,000 lines. Once it merges, this card's Blocked-by: #22269 clears. Three things for the door:

    1. Two items from the mint review that bind at this door, not at card ③ (domain:spec seat 1's pointer 6206951646 on #22269, items 2 and 3). Card ③ has no door, so both were routed here by its contract records (6213039702 ③, 6214644798 ③).

    • Impersonation. ExecutionContext carries no impersonation marker. A print 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 the session's 60 s.
      • The door decides whether a print under impersonation mints at all, or how it is attributed.
      • A server-set marker on the context would be a packages/spec widening, which is the maintainer's decision.
    • Audit rows. Each print fires the existing session.create.after login writer when it mints, and the logout writer when it revokes, both with no endpoint path. The door decides whether a print is audited differently: a print row of its own, or the login/logout pair left as is.

    2. What the door relays, as card ③ answers at that head. This supersedes the list in 6059391513.

    • The service is the in-process print slot: IPrintService.printRecordToFile({ app, object, recordId, page, filename? }, context).
    • Hand it the request's own ExecutionContext, unchanged. The service passes that object to the mint by identity (pinned); the mint mints for whatever user the context names (6206951646 item 1).
    • Refusals, all before a session is minted or a browser started:
      • PERMISSION_DENIED / 403: not a human user acting for themselves;
      • SERVICE_UNAVAILABLE / 503: the engine is not ready, and status() says why;
      • RESOURCE_NOT_FOUND / 404: unknown page;
      • VALIDATION_ERROR / 400: a malformed request, or a page that is not the object's default record page or declares no print;
      • RECORD_NOT_FOUND / 404: the record does not exist, or the caller cannot read it;
      • FILES_DISABLED / 403;
      • PERMISSION_DENIED / 403: the caller may not create attachments;
      • ATTACHMENT_PARENT_ACCESS / 403: the caller may not edit the record, which attaching needs.
    • After them:
      • TIMEOUT / 504: the 30 s render budget;
      • INTERNAL_ERROR / 500: an engine failure, with secrets redacted;
      • the archive's own refusals, e.g. PAYLOAD_TOO_LARGE / 413 over the upload limit.
    • The PDF is attached by card ③, as the caller: sys_file.owner_id and sys_attachment.uploaded_by name them. The door does not attach a second time.

    3. Which page prints. Card ③ prints a record on its object's DEFAULT record page only, because the console renders a record there and nowhere else. A route that renders a named record page is filed in objectui (objectstack-ai/objectui#12715). Until it lands, a "generate PDF" action prints the default record page, which must declare print.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock and route: pm:blocked → pm:queue · domain:spec → domain:services · area:files · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-17T02:05Z

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


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-17T14:08Z
    Session: session_01Nk17Z26X5DB59iQztDBADH (the dispatching seat; the dev runs mode:subagent in this session's container and shares its session ID, so its identity is the branch)
    Account: marchtian (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22270-print-record-door
    Worktree: objectstack-issue-22270
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface (at origin/main bd4276a91e or later; stop on a breach and explain it in the report):

    • packages/services/service-print/src/**: the door that relays IPrintService.printRecordToFile (a route the plugin registers), its refusal relay, its tests, and the module header.
    • examples/app-showcase/**: one declared record-header action on an object whose default record page declares print (the invoice), in the existing action vocabulary, and the example's tests.
    • one changeset (.changeset/22270-*.md) for the packages that publish.
      Exclusions:
    • ⛔ No packages/spec change: no impersonation marker on ExecutionContext, no new action type, no new error code.
    • ⛔ No second attach: card ③'s service attaches the PDF as the caller.
    • ⛔ No new export from @objectstack/service-print and no new plugin config key; if the door needs either, the dev stops and reports it.
    • ⛔ No objectui change; a console change the door needs is reported for the seat to file in objectui at p1.
      Container & model: M: one door, one example action, the refusal pins, mode:subagent, model: opus (TIER_DEFAULT; --tier read no path-derived mandate)
      Clause-②: no. The door is a route registered inside the plugin and the showcase action uses the existing action vocabulary: no export, no config key, no schema change, no new error code. The dev measures this on the built entry declarations and reports it.
      Responsibility: n/a — not a defect card
      Thread-read: 6228299507
      Serial constraints cleared: no open PR touches packages/services/service-print/ or examples/app-showcase/ source (4 open PRs read at this write; chore: version packages (next) #24168, the version PR, edits only CHANGELOG and package.json files). No pm:dispatched card in any lane declares these paths.

    Owner's body amendment, as pointer 6059391513 asks: card ③ (#22269, landed as b15f8a1997) attaches the archived PDF under the caller. This card is the action, the door, the button and the listing. The door relays card ③'s refusals and does not attach a second time.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions