Skip to content

feat(spec,automation,app-shell)!: a flow screen field typed file collects an upload, and the run resumes with the uploaded file's id - #26134

Merged
objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-h2ifka
Oct 26, 2026
Merged

objectstack-fleet[bot] merged 7 commits into
mainfrom
claude/project-thread-h2ifka

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Part of #25801

Clause-②: yes (narrowing)
Metadata-protocol: semantic

Summary

This PR does the card's first half: a flat screen can ask for a file, so a wizard collects the file before the record that will own it exists and writes everything after its last screen. The card's second half (closing a paused run's dialog cancels the run, or offers to) is left open as a product decision. It is not built here, and the section at the end says why.

A screen field declared type: 'file' now works like this:

{ id: 'ask', type: 'screen', config: { fields: [
  { name: 'contract_name', label: 'Name', type: 'text', required: true },
  { name: 'contract_file', label: 'Signed copy', type: 'file', required: true },
] } },
{ id: 'write', type: 'create_record', config: { objectName: 'contract',
  fields: { name: { dialect: 'cel', source: 'contract_name' }, doc: { dialect: 'cel', source: 'contract_file' } } } },
  • Console. The flow screen dialog renders the object form's own FileField for the field, reached through getLazyFieldWidget('file') as ActionParamDialog's file params reach it. It uploads through the console's UploadProvider, which is the file service. The upload lands as a sys_file that no record owns yet, and the widget's value is that file's id. The screen body renders inside an uploading scope (useUploadingScope), so while an upload is in flight Submit is disabled and reads "Uploading…". The resume posts screenResumeValues(...), which reduces each file field's value to its bare id with fileIdOf (a prefilled defaultValue may arrive in the expanded { id, name, url, … } form).
  • Engine. validateScreenInputs reads 'file' as the second closed value domain beside 'number'. A value that is not a file id is refused with 400 INVALID_SCREEN_INPUT and the field code invalid_type (Screen field "contract_file" must be the id of an uploaded file). The id is judged by the spec's isFileIdToken, the same predicate the claim path reads, so whatever the door admits is something a later write can claim. A URL, the expanded read form, a list or a number is refused and never reduced. The refusal writes nothing and keeps the pause, so a corrected resume still lands. An empty optional field and a hidden field are not judged.
  • Spec and docs. ScreenFieldConfigSchema.type and the ScreenFieldSpec contract document the new member, the reference docs are regenerated, and the flows guide gains an "A file field" section. The key stays an open string, so nothing an author already wrote is refused at publish.
  • Ownership. The create_record after the screen writes the id into the record's file field, and that write claims the file for the record (ADR-0104 D3: one file, one owner, met by unattached-then-claimed). Under the default runAs: 'user' the claim first makes the download door's read check as the run's user (files: an authorization gap in the file-reference lifecycle, within one organization (detail withheld) #25692). This PR does not touch service-storage: the claim is the lifecycle fix(storage)!: a record write claims or copies only a file its caller may read #25951 landed, unchanged.

Tests

Measured on this branch.

  • service-automation: screen-input-file-field.test.ts (new) covers minted ids accepted, six wrong shapes refused with the exact message, agreement with isFileIdToken on every shape, absence left to required, a hidden field not judged (with a positive control), and image/avatar/video/audio left open. builtin/screen-file-field-resume.test.ts (new) runs the engine: the pause carries type: 'file', a URL resume is refused with nothing inserted and the pause kept for the corrected resume, and an id resume reaches create_record unchanged.
  • app-shell: FlowRunner.fileField-25801.test.tsx (new, 7 tests) drives the real FileField under a held UploadProvider adapter. It covers the widget rendering instead of a text input; Submit disabled and reading "Uploading…" mid-upload, a click on it resuming nothing, and the resume after the upload settles posting { contract_file: MINTED_ID }; a required empty file field refused locally; a prefilled expanded value posted as its id; and screenResumeValues on its own. With its neighbours FlowRunner.bounds-9248 and ActionParamDialog.uploading: Test Files 3 passed (3), Tests 16 passed (16).
  • Dogfood: flow-screen-file-intake.dogfood.test.ts (new) boots a real stack with the local storage adapter and drives it as a plain member. The paused screen carries type: 'file'; an upload lands as a sys_file with no owner; a resume carrying the download URL or the expanded form is refused 400 INVALID_SCREEN_INPUT with no record written and no claim; the corrected resume lands on the same run; and a resume with the id writes the record with the id in its file field and claims the file (ref_object, ref_id, ref_field), read back through the data door.
    pnpm --filter @objectstack/dogfood exec vitest run test/flow-screen-file-intake.dogfood.test.ts: Test Files 1 passed (1), Tests 4 passed (4). The full battery at 1e9a122b: Test Files 297 passed, 3 skipped (300); Tests 2527 passed, 18 skipped (2545). The commits after it change only the changeset and the flows guide.
  • Type-check: pnpm --filter @object-ui/app-shell run type-check and pnpm --filter @objectstack/service-automation run typecheck, exit 0 each. pnpm --filter @objectstack/dogfood run typecheck, exit 0.

Ablation

Each mutation went through scripts/ablation-replace.mjs with its restore-to-HEAD check:

  • service-automation: deleting the 'file' row of the value-domain map turned 4 tests red. The file was restored to its HEAD blob 753e36a8.
  • FlowRunner.tsx: disabled={submitting || uploads.anyUploading} to disabled={submitting} turned 1 test red, the held-upload test, on toBeDisabled().
  • FlowRunner.tsx: resumeWith(screenResumeValues(screen, values)) to resumeWith(values) turned 1 test red, the prefilled expanded value test.
  • ScreenView.tsx: if (kind === 'file') { to if (false) { turned 4 tests red, every dialog test that waits for the widget's file input.

Each file was restored with git diff HEAD empty. A first attempt at the disabled mutation split its anchor on the || and wrote a file that did not parse ("no tests"). That reading was void and was re-run with the full anchor; the line above is the re-run.

Gates

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at 9c17572c (117 commands). Each ran with its exit code captured before any pipe, and --ran reconciled them: "117 derived famil(ies) accounted for — 116 run, 1 NOT-MEASURED". The 116 that ran exited 0. They include check-adr-0087-registration, check-changeset-no-major, check:api-surface, check:authorable-surface, check:generated, check:docs, check:liveness, check:nul-bytes and check:type-check-debt, which ran under the verify lock and reported "26 raw tsc error(s) total, none above its recorded number".

Not measured locally, left to CI: check:dual-build-cjs-loads, which needs a full workspace pnpm build. It stopped on that prerequisite with exit 3 before reading anything. This diff does not change any package's build shape.

Acceptance notes


Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_0121LS2jqNju6opaDpdcn7ht

…aded file's id

A flat screen field declaring `type: 'file'` is a value domain on resume,
beside `number`: a present value must be a `sys_file` id (the spec's
`isFileIdToken`, the predicate the record write's claim path reads), or the
resume is refused with `invalid_type` under `INVALID_SCREEN_INPUT`. The spec
describes the member on `ScreenFieldConfigSchema.type` and `ScreenFieldSpec`.

Claude-Session: https://claude.ai/code/session_0121LS2jqNju6opaDpdcn7ht
Co-authored-by: Claude <noreply@anthropic.com>
…et and resumes with its id

A `type: 'file'` flat-screen field now renders the object form's FileField
(through getLazyFieldWidget('file')), which uploads through the ambient
UploadProvider and hands back the minted sys_file id. The runner renders the
screen body inside an uploading scope and holds Submit, labelled "Uploading…",
until every upload settles, then posts screenResumeValues, which reduces a
file field's value to its bare id with fileIdOf.

Claude-Session: https://claude.ai/code/session_0121LS2jqNju6opaDpdcn7ht
Co-authored-by: Claude <noreply@anthropic.com>
…ialog

Drives the real FileField through a held UploadProvider adapter: Submit is
disabled and reads "Uploading…" while the upload is in flight, and the resume
posts the minted id; a required empty file field is refused locally; a
prefilled expanded value goes out as its bare id. screenResumeValues is
pinned on its own.

Claude-Session: https://claude.ai/code/session_0121LS2jqNju6opaDpdcn7ht
Co-authored-by: Claude <noreply@anthropic.com>
…laims the file

A real boot with the local storage adapter, driven as a plain member: the
paused screen carries `type: 'file'`, an upload lands with no owner, a resume
carrying the download URL or the expanded read form is refused 400
INVALID_SCREEN_INPUT with nothing written and the pause kept, and a resume
with the id writes the record and claims the file for it.

The spec docblock now says the claim's read check is asked under the default
`runAs: 'user'`, and that a system run's write is not checked. The changeset
covers spec, service-automation and app-shell.

Claude-Session: https://claude.ai/code/session_0121LS2jqNju6opaDpdcn7ht
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 26, 2026
@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-automation, @objectstack/spec, touching 4 documentable anchor(s).

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via validateScreenInputs (symbol, a top-level function))
What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 145 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json a335f1defbea57ffe552d2e9efc5ad8d56a2e76a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from a5bdd4e245183be5559ab70e9ba25b8b251430ba — the merge of head 9c17572c2da999a01edf567e4a95bab87b771d1d into base a335f1defbea57ffe552d2e9efc5ad8d56a2e76a, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a5bdd4e245183be5559ab70e9ba25b8b251430ba && git checkout a5bdd4e245183be5559ab70e9ba25b8b251430ba
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a335f1defbea57ffe552d2e9efc5ad8d56a2e76a 9c17572c2da999a01edf567e4a95bab87b771d1d && git checkout -B drift-repro a335f1defbea57ffe552d2e9efc5ad8d56a2e76a && git merge --no-ff 9c17572c2da999a01edf567e4a95bab87b771d1d

node scripts/docs-audit/affected-docs.mjs --json a335f1defbea57ffe552d2e9efc5ad8d56a2e76a

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a335f1defbea57ffe552d2e9efc5ad8d56a2e76a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

A short "A file field" section under the flat screen: the console uploads and
the run resumes with the file's id, the write after the screen claims the
file (read-checked under runAs 'user'), anything but an id is refused on
resume with invalid_type, and a field takes one file.

Claude-Session: https://claude.ai/code/session_0121LS2jqNju6opaDpdcn7ht
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl and removed size/l labels Oct 26, 2026
…ptance narrowing

The seat amended the claim to `Clause-②: yes (narrowing)` and
`Metadata-protocol: semantic`, as measured: a screen field already declared
`type: 'file'` now renders the upload widget and its resume refuses any value
that is not a file id. The changeset carries both lines, a BREAKING note with
the FROM/TO fix for a caller that resumes the run itself, and the ADR-0087
disposition `not-required (no-migration-prescription)`: no metadata key, stored
row or export moves, so there is nothing for `objectstack migrate meta` to
rewrite.

Claude-Session: https://claude.ai/code/session_0121LS2jqNju6opaDpdcn7ht
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot changed the title feat(spec,automation,app-shell): a flow screen field typed file collects an upload, and the run resumes with the uploaded file's id feat(spec,automation,app-shell)!: a flow screen field typed file collects an upload, and the run resumes with the uploaded file's id Oct 26, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 9c17572c2da999a01edf567e4a95bab87b771d1d
Local-runs: none
Scope: packages/spec/src/automation/builtin-node-config.zod.ts, packages/spec/src/contracts/automation-service.ts + packages/services/service-automation/src/screen-input-contract.ts, packages/services/service-automation/src/screen-input-contract.test.ts, packages/services/service-automation/src/screen-input-file-field.test.ts, packages/services/service-automation/src/builtin/screen-file-field-resume.test.ts, ui/packages/app-shell/src/views/ScreenView.tsx, ui/packages/app-shell/src/views/FlowRunner.tsx, ui/packages/app-shell/src/views/tests/FlowRunner.fileField-25801.test.tsx, packages/qa/dogfood/test/flow-screen-file-intake.dogfood.test.ts, packages/qa/dogfood/test/fixtures/flow-screen-file-fixture.ts, .changeset/25801-flow-screen-file-field.md, content/docs/references/automation/builtin-node-config.mdx, content/docs/automation/flows.mdx; read unchanged for the claims those hunks make: packages/spec/src/data/field-value.zod.ts (isFileIdToken), packages/services/service-storage/src/file-reference-lifecycle.ts (the claim path's read check), ui/packages/fields/src/widgets/file-value.ts (fileIdOf), ui/packages/fields/src/widgets/uploadingScope.tsx, docs/adr/0104-field-runtime-value-shape-contract.md (D3).

Inputs: card #25801, its body and all eight comments (claim 6415695572, amendment 6417676103, os-dev-reports 6417535261 and 6418524489; 6417697490 is a declared duplicate); PR #26134's body and file list as live at this read, its net diff against main at the head (14 files, +959/-48), and the check-runs on the head. The relay's queued title and body update had already landed when I read the PR: the title carries ! (rename event by objectstack-fleet[bot]) and the body is byte-equal to the seat's intended file issue-25801/pr-26134-body-9c17572c.md bar a trailing newline, so this record judges the live PR, which is the intended one. Check-runs: every gate-carrying run on the head concluded success, including the second Check Changeset run (121632617419) that followed the rename; the skipped runs are opt-in and matrix placeholders. No governed surface is in the file list, the head repo is the base repo, the PR is a draft, and 1007 changed lines sit under the 3,000-line threshold.

① Derived judgments

  1. The resume door's accept set narrows for type: 'file' — RIGHT. validateScreenInputs now keys a closed module-level map SCREEN_FIELD_VALUE_DOMAINS (number, file) in place of the field.type !== 'number' guard; a present value on a file field must satisfy isFileIdToken, else invalid_type with "must be the id of an uploaded file". The pass keeps the number pass's conditioning: an absent or empty value is required's question, a field hidden by visibleWhen is not judged. screen-input-file-field.test.ts pins two minted ids admitted, six wrong shapes refused with the exact message (download URL, absolute URL, expanded read form, legacy inline blob, list, number), door/predicate agreement on seven values, absence, a hidden field with its positive control, and image/avatar/video/audio left open. screen-file-field-resume.test.ts pins the pause carrying type: 'file', a URL resume refused before any insert with the pause surviving, and an id reaching create_record unchanged. The dogfood test pins the same at the REST doors as a plain member: 400 INVALID_SCREEN_INPUT for a URL and for the expanded form, no record and no claim, then the corrected resume landing on the same run.
  2. One arbiter — RIGHT. isFileIdToken (field-value.zod.ts, uuid/nanoid-shaped, letters, digits, _, -, 1 to 64 characters) is ADR-0104 D3 wave 2's single predicate, read at the head by objectql's expand path, service-storage's claim path and backfill, plugin-approvals, and @object-ui/fields' fileIdOf. The door admits exactly what the claimer can claim; the test asserts that agreement. A URL can never match (it carries :, / or .).
  3. Spec surface — RIGHT, nothing authorable moves. ScreenFieldConfigSchema.type stays z.string().optional(); only .describe() and the TSDoc change, and content/docs/references/automation/builtin-node-config.mdx is regenerated for both screen variants. ScreenFieldSpec.type in contracts/automation-service.ts stays type?: string, docblock only. No export is added, removed or re-typed, so the api-surface and authorable-surface gates (inside the green TypeScript Type Check job) are the right readers of this.
  4. The docblock's claim-path sentences — RIGHT, read in the cited code. file-reference-lifecycle.ts asks the download door's authorizeRead verdict as the write's caller (files: an authorization gap in the file-reference lifecycle, within one organization (detail withheld) #25692) and returns null for isSystem and context-less writes, so "under the default runAs: 'user' the claim first makes the read check; a runAs: 'system' write is not judged" is what the code does. This PR does not touch service-storage. The dogfood fixture's flow runs runAs: 'user', so the claim is proven on that path: status: 'committed', ref_object, ref_id, ref_field on the sys_file row after the write, read back through the data door.
  5. Console — RIGHT. ScreenView resolves getLazyFieldWidget('file') through the registry ActionParamDialog uses; screenResumeValues reduces each file field through fileIdOf and passes a no-id value through so the engine names it; FlowRunner wraps the screen body in UploadingScopeProvider, gates Submit on uploads.anyUploading, short-circuits submit() on it, and reads the fields.file.uploading key FileField already owns. screenResumeValues is exported from ScreenView.tsx only; the package index does not re-export that module, so @object-ui/app-shell gains no public surface. Seven app-shell tests; Type Check UI, Lint UI, Build & E2E UI and all sixteen Test UI shards green.
  6. The value-domain set stays closed — RIGHT. The other media hints remain widget hints (tested), and the docblock says a member joins by ruling.

② Semver level

  • .changeset/25801-flow-screen-file-field.md: minor on @objectstack/spec, @objectstack/service-automation and @object-ui/app-shell, the three published packages the diff moves (private unset, all in the fixed group). A semantic kind takes at least minor on a moved package: RIGHT. check-changeset-no-major is inside the green Lint job; both Check Changeset runs are green.
  • Clause-②: yes (narrowing) and Metadata-protocol: semantic, line-initial in the changeset and the PR body, equal to the amended claim 6417676103: RIGHT. Narrowing, because the resume accept set shrinks for a field already declared type: 'file'. Semantic, because type stays an open string so nothing is refused at publish, while the already-accepted value 'file' changes meaning from a text input to an upload whose resume admits an id only; nothing is added (the value was accepted before) and nothing is narrowed at authoring.
  • BREAKING banner present with Before, Now and Fix, a FROM → TO for a caller that resumes without the console, and the title carries !. The dev measured zero producers of a type: 'file' screen field in examples or packages.
  • ADR-0087 disposition: exactly one marker, not-required (no-migration-prescription): RIGHT on the gate's own definition. No spec key, export, stored row or stored shape is added, removed, renamed or re-shaped, so objectstack migrate meta has nothing to rewrite; the FROM → TO names the request body a caller posts, not a consumer source rewrite. check-adr-0087-registration accepted it on the head, as report 6418524489 quotes and the green Lint job confirms.

③ Boundary flags

  • Declaration-lines deviation (report 6417535261): answered. The seat amended the claim (6417676103); changeset, PR body and title now match it.
  • File-surface deviation (no service-storage edit; FlowRunner.tsx edited for part 1 only; ui/packages/app-shell/src/utils/flowResponse.ts of feat(spec,console): a type: 'flow' action may declare onSuccess, and the console navigates after a run that completes #26011 untouched): answered. The fix(storage)!: a record write claims or copies only a file its caller may read #25951 claim lifecycle is used unchanged, read in file-reference-lifecycle.ts; the file list confirms, and the single-writer guards on the head are green.
  • Part of #25801 keyword and the pre-push trailer reword without override: answered; the Part-of guards on the head are green.
  • Open question, part 2 (closing a paused run's dialog): escalated to [Decision] Closing a paused flow screen dialog: should abandoning the run be possible, and who may do it #26149 (needs-user-decision, open), and report 6418524489 carries open_questions: []. Not in this PR; nothing here pre-empts it (closing still calls onClose only).
  • PR title and body handed to the seat (report 6418524489): answered; the live PR carries both.
  • flows.mdx outside the claim's surface: answered; it closes the docs-drift row (validateScreenInputs) and is docs only.
  • Three noted-not-filed findings (console lowercases type while the engine matches exactly; one file per field with no multiple, accept or maxSize; a runAs: 'system' write unchecked): accepted as acceptance notes under Prime Directive 10. Each is pre-existing for number and text respectively, none is a defect this diff introduces, the system-write posture is fix(storage)!: a record write claims or copies only a file its caller may read #25951's and is stated in the docblock and the changeset, and the case asymmetry is not an authoring trap today because the widget posts an id whatever the case. If file ever grows widget-side limits, the case asymmetry wants its own card first.
  • CONTRACT_REVIEW_TIER review owed before the queue (report 6417535261): this record.
  • Landing stays the owning seat's: draft PR, no governed surface, no fork, under 3,000 lines.

Implemented-by: claude/project-thread-h2ifka
Reviewed-by: contract-review-26134-fable (a subagent of seat session_01HbmCEPMBmMLU3VZCEzfec7)

VERDICT: PASS

Rendered 2026-10-26T14:36Z from the card, the PR and the head's check-runs; nothing built, run or re-run.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Seat ACCEPT — PR #26134 at 9c17572c2da999a01edf567e4a95bab87b771d1d

Seat domain:spec#4, session session_01HbmCEPMBmMLU3VZCEzfec7, 2026-10-26T14:37Z. Lane review at TIER_DEFAULT; the contract face carries its own record (6418718438, PASS, CONTRACT_REVIEW_TIER).

  • Shape: draft, base main, first line Part of #25801 with no closing keyword beside any open card; the card's second half is on [Decision] Closing a paused flow screen dialog: should abandoning the run be possible, and who may do it #26149.
  • Declarations: Clause-②: yes (narrowing) / Metadata-protocol: semantic in the changeset and the PR body, matching amendment 6417676103; title carries !; level minor on the three moved packages; one ADR-0087 marker.
  • Prose checked sentence by sentence against the diff: the changeset's three package bullets, the BREAKING Before/Now/Fix lines, and the new content/docs/automation/flows.mdx "A file field" section (claim on write, runAs split, refusal shapes, one file per field) each state what the code does.
  • Evidence: dispatch-gates union at 9c17572c, 116 of 117 run and green, dual-build-cjs-loads left to CI. Check-runs at this head: 63 success, 7 skipped, none red. dogfood_battery full at 1e9a122b (297 files passed); git diff 1e9a122b 9c17572c touches only the changeset and flows.mdx, so it stands for this head.
  • Not governed (check-governed-merges.mjs --pr 26134: 0 of 14 paths), 1007 changed lines, under the Tier H threshold.

Next: ready and auto-merge through the relay. pm:dispatched stays on #25801 until it is closed or re-queued; the card stays open for #26149's ruling.

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

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants