Skip to content

refactor(daemon): session script publication behind one capability (#1478 P4a) - #1532

Merged
thymikee merged 3 commits into
mainfrom
p4a/session-script-publication
Aug 1, 2026
Merged

refactor(daemon): session script publication behind one capability (#1478 P4a)#1532
thymikee merged 3 commits into
mainfrom
p4a/session-script-publication

Conversation

@thymikee

Copy link
Copy Markdown
Member

#1478 P4a: four supported publication paths consume one capability and one tagged state machine.

What moved

Commit 1 (from the prior session) added the value module: the scriptPublication aggregate — none | authoring{armed,aborted,published} | repair{armed,complete,close-succeeded,committed,aborted} — with the shared target and per-target --force authorization (#1258) inside the target, plus pure transitions.

Commit 2 completes P4a atomically:

  • SessionState drops the eight co-resident fields (scriptRecordingState, saveScriptPath, saveScriptForce, saveScriptBoundary, saveScriptComplete, saveScriptCommitted, repairPlatformCloseReceipt, repairSourcePath) for one scriptPublication field. No shadow state, no old writer path.
  • session-replay-transaction.ts (ReplaySessionTransaction): repair arm (per-step, boundary stamps once, explicit <out> wins over the healed sibling), --from demotion, completion, abort, close receipts, and the reads idle-reap/tombstones/divergence-hold/record-exclusion key off.
  • session-script-publication-capability.ts (SessionScriptPublication): authoring arm on open, the recorded --save-script flag ingress (fix(daemon): reject unsupported raw --save-script flags at request ingress #1501 pins every other command closed at the router), active publication, published transitions, and the effective write-force decision. The writer keeps the commit transition so idempotence stays colocated with the atomic publish.
  • Failure/retry pins as briefed: platform-close failure leaves state unchanged; publication failure retains target+force+receipt and a same-identity retry skips close dispatch; committed/aborted are explicit and drop the receipt. No caller-computed platformCloseSucceeded anywhere.
  • R7 consolidated: one scriptPublication row with exactly the three writer modules, recordSession narrowed; R10 baseline lowered to 22 fields / 28 claims so the consolidation cannot regrow.

The two open design decisions

  • Force retention across default→explicit retarget: preserved byte-for-byte and still flagged in resolveScriptTarget's docblock as a probable Add --force/--overwrite flag for --save-script now that publish refuses any existing target #1258 gap. Tightening is a product change for its own PR.
  • Never-armed close --save-script: folded into the authoring lifecycle (armed at the recorded close action, published in the same request) rather than a fourth variant. Every close path that reaches the write deletes the session, so the transient armed state cannot reach session save-script eligibility; its "not armed before this journey began" rejection is unchanged (and covered by the existing eligibility tests).

A bug the migration caught

The aggregate's original resolveScriptTarget collapsed an already-materialized explicit target back to default on every bare (pathless) re-arm — the per-step repair armer would wipe the healed-sibling path after step 1 and defeat the persisted-force preflight bypass. The migrated repair tests caught it (5 failures tracing to one root cause); a bare re-arm now keeps the previous target and only adds a live force grant.

Verification

  • typecheck / lint / format / check:layering clean at head (R7 writes verified AST-level against the new owner set).
  • 624 test files / 5220 tests; two full-suite-only timeout flakes (request-save-script-transports, client-metro) pass in isolation and match the known contention class — failure set changes between runs, timeouts only.
  • The six repair/publication test files (104 tests) were migrated assertion-for-assertion, no weakening; where old boolean flags became statuses the new assertions are strictly more precise.

Per the brief's STOP list: no parallel repair state, no caller-computed close boolean, and no receipt clear before terminal cleanup.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.92 MB 1.92 MB +2.9 kB
JS gzip 614.6 kB 615.5 kB +899 B
npm tarball 732.6 kB 733.5 kB +947 B
npm unpacked 2.57 MB 2.57 MB +2.9 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 29.1 ms 28.9 ms -0.2 ms
CLI --help 59.9 ms 59.4 ms -0.4 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/screenshot-result.js +3.3 kB +910 B
dist/src/internal/daemon.js -41 B -16 B
dist/src/session.js -363 B +13 B
dist/src/script.js 0 B -5 B
dist/src/cli.js 0 B -4 B

@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head 7ff0af870. The P4a state-machine migration is semantically coherent: authoring/repair are structurally disjoint, writes stay confined to the two daemon-owned projections plus writer commit, and the migrated tests cover arm/resume/commit, close-retry receipts, tombstones, force/retarget behavior, and router-backed recording.

One blocking owner-action gate remains: Fallow Code Quality is red. Remove the unused exported scriptPublicationTarget helper (src/daemon/session-script-publication-state.ts:131) and reduce the new/changed complexity findings in validatePublicationEligibility (src/daemon/handlers/session-script-publication.ts:69), prepareSaveScriptSession (src/daemon/handlers/session-replay-runtime.ts:652), and the touched replay-repair provider scenario. Please refactor/extract the decisions rather than suppressing or regenerating baselines, then rerun pnpm check (including check:fallow). iOS/Android/Linux smoke checks are also still running. No ready-for-human until the failing gate is resolved.

@thymikee

Copy link
Copy Markdown
Member Author

Fixed in a145b8378 — all four findings refactored, none suppressed, no baselines touched:

  • scriptPublicationTarget: now module-private. Both public target reads (scriptTargetPath/scriptTargetForce) route through it; nothing outside the module ever did.
  • validatePublicationEligibility (12 cyclomatic → ~5): split into a pure publicationIneligibility classifier returning a discriminant and a PUBLICATION_INELIGIBILITY_ERRORS table, so each rejection is one decision and one message, and the classifier's order (repair > terminal statuses > not-armed) is explicit.
  • prepareSaveScriptSession (10 cyclomatic / 42 lines): the two arm-time rejections (authoring re-arm guard, EEXIST preflight) moved into rejectSaveScriptArming; the function that remains is the demote-and-arm flow only.
  • The repair-record-exclusion provider scenario (132-line arrow): its three phases are named helpers now — armRepairAndAssertHeld, assertRepairSegmentReadExclusion, assertHealedScriptContents — and the test body reads as the arm → diverge → resume → commit journey again.

Verified at the CI's own scope, not just the delta: fallow audit --base origin/main over the 34 changed files reports zero issues (which also clears the fourth, unnamed finding from the "3 shown, 4 total" list). pnpm check:fallow, typecheck, lint, format, and check:layering are clean; the touched publication/repair/provider test files pass (45 + the provider scenario).


Generated by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head a145b8378. The Fallow follow-up preserves the P4a semantics: eligibility and save-script preflight decision order are unchanged, target access is narrowed without losing callers, and the extracted provider scenario still exercises daemon → replay → divergence → resume → close/write. The aggregate keeps authoring/repair disjoint and receipt/demotion/terminal transitions coherent. Exact-head CI is fully green, including Fallow, Integration, Coverage, and iOS/Android/macOS/Linux smoke. No blocking code findings — ready for human review.

Residual: #1533 documents a real pre-existing aborted-authoring/bare-close quirk intentionally preserved by this refactor; it needs an explicit product decision after the stack. The generic device smoke jobs do not specifically prove a live --save-script publication artifact, so that remains evidence risk rather than a code finding.

claude and others added 3 commits August 1, 2026 07:56
First step of P4a. Nine co-resident optional SessionState fields encode two
lifecycles plus a shared output target, with nothing in the shape saying the
lifecycles are disjoint — so readers re-derived that from field combinations
and writers had to remember which siblings to clear.

The aggregate makes both invariants structural: a session publishes nothing,
authors ordinarily, or is under repair; and force lives inside the target, so
retargeting replaces the authorization along with the path.

Three corrections after an adversarial review of the first draft:

- The target is a default|explicit union, not a mandatory path. A bare
  'open --save-script' arms with no path and lets the writer resolve a
  daemon-owned destination at write time, and force can be granted before any
  path exists. Eagerly materializing a default path would have silently changed
  retarget semantics, because today's check requires a previously persisted
  path — so 'open --save-script --force' then 'close --save-script=out.ad' is
  not currently a retarget and the grant survives. That behavior is preserved
  here and flagged in the docblock as a probable #1258 gap; tightening it is a
  product change and belongs in its own commit.

- The repair status relation is not linear. A failed commit followed by
  'replay --from' demotes complete back to armed, so demoteRepairToArmed exists
  and deliberately RETAINS the close receipt: the platform close already
  succeeded for that operation identity, and dropping it would re-dispatch a
  close on retry — which is also how a migrator ends up reaching for the
  caller-computed platformCloseSucceeded boolean the brief forbids.

- The receipt doc no longer claims it is set only at close-succeeded and later,
  since the demotion path makes {armed, receipt set} reachable.

Still to come in this PR: both projections, and the writer migration. Note the
brief's seven-file writer inventory omits session-open.ts, which holds the only
two writers of the authoring armed/aborted states.

Refs #1478

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXQLYV7etZx3gcXsUsrQJ8
#1478 P4a)

The eight co-resident SessionState fields (scriptRecordingState, saveScriptPath,
saveScriptForce, saveScriptBoundary, saveScriptComplete, saveScriptCommitted,
repairPlatformCloseReceipt, repairSourcePath) are gone; SessionState.scriptPublication
holds the aggregate, and every writer migrated in this commit — no shadow state.

Two daemon-private projections own the writes, enforced by the R7 ownership gate:

- session-replay-transaction.ts (ReplaySessionTransaction): repair arm/demote/
  complete/abort, close receipts, and the uncommitted/boundary/sourcePath reads
  that idle-reap, tombstones, divergence-hold, and the recorder's exclusion key off.
- session-script-publication-capability.ts (SessionScriptPublication): authoring
  arm on open, the recorded --save-script flag ingress, active publication, the
  published transitions, and the effective per-target force decision (#1258).
  The writer keeps the commit transition so idempotence stays colocated with the
  atomic publish.

Failure/retry transitions pinned as the brief requires: platform-close failure
leaves state unchanged (no receipt, retry re-dispatches); publication failure
retains target+force+receipt (same-identity retry skips close dispatch); committed
and aborted are explicit terminal states that drop the receipt.

Design decisions resolved:

- Force retention across a default->explicit retarget is preserved as-is and
  still flagged in resolveScriptTarget's docblock as a probable #1258 gap;
  tightening it stays a separate product change.
- The never-armed 'close --save-script' whole-log publication folds into the
  authoring lifecycle (armed at the recorded close, published in the same
  request) instead of a fourth variant: every close path that reaches the write
  deletes the session, so the transient armed state cannot leak into
  'session save-script' eligibility, whose not-armed-before-this-journey
  rejection is untouched.

One real bug caught by the migrated tests and fixed in resolveScriptTarget: a
bare (pathless) re-arm collapsed an already-materialized explicit target back to
the daemon default, wiping the healed-sibling path on every per-step repair
re-arm and defeating the persisted-force preflight bypass. A bare re-arm now
keeps the previous target and only adds a live force grant.

R7 rows consolidated to one scriptPublication entry (three owners) and the
recordSession row narrowed; the R10 baseline drops to 22 writer-owned fields /
28 owner claims so the consolidation cannot regrow.

Gates: typecheck, lint, format, layering clean; 624 files / 5220 tests pass
(two known contention-flake timeouts reproduce only under full-suite load and
pass in isolation).

Refs #1478

Co-Authored-By: Claude <noreply@anthropic.com>
…t suppressing

- scriptPublicationTarget is module-private; both public target reads
  (scriptTargetPath/scriptTargetForce) go through it and nothing else did.
- validatePublicationEligibility splits into a pure ineligibility classifier
  and an error table, so the four rejections read as one decision each.
- prepareSaveScriptSession hands its two arm-time rejections (authoring
  re-arm, EEXIST preflight) to rejectSaveScriptArming and keeps only the
  demote-and-arm flow.
- The repair-record-exclusion provider scenario extracts its three phases
  (arm-and-hold, exclusion contrast, healed-script contract) into named
  helpers; the test body is the journey again.

Refs #1478

Co-Authored-By: Claude <noreply@anthropic.com>
@thymikee
thymikee force-pushed the p4a/session-script-publication branch from a145b83 to e6508ec Compare August 1, 2026 05:56
@thymikee

thymikee commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Re-reviewed exact head e6508ecd after the main rebase. git range-diff shows all three P4a commits patch-equivalent to the previously reviewed clean series, with no conflict-resolution drift or production-route change. All exact-head checks are green, merge state is clean, and the existing ready-for-human label remains justified.

Residual product follow-up is unchanged: #1533 still needs explicit triage for the intentionally preserved aborted-authoring/bare-close behavior.

@thymikee
thymikee merged commit 67f3d09 into main Aug 1, 2026
30 checks passed
@thymikee
thymikee deleted the p4a/session-script-publication branch August 1, 2026 09:10
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-01 09:10 UTC

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

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants