Repository navigation
chore(release): prepare 0.5.0 platform qualification groundwork - #157
Conversation
Add scripts/deployment-harness.mjs, a repository-only validation harness for the remote deployment ADR. It runs the destination checks (dedicated prefix, root and .receipts trust, staging, probe, mkdir lock, no-replace activation, verification, receipts, rollback, retention, lock breaking) and an operator-side hash-chained run journal as separately invoked commands. It never opens SSH, never installs remotely by itself, never runs during list/send/doctor and leaves the exact-version preflight unchanged. Mutations are dry runs unless --execute; destructive steps also need --confirm-destructive. POSIX only. The harness enforces the step 8 order: destination verification, then the operator doctor and dry-run, then the receipt, then unlock, then the caller switch. It also covers fresh-lock recovery without a receipt, journal-confirmed rollback, corrupt/mismatched/orphan receipts, .receipts trust and unowned or unconfirmable lock overrides. Fix ADR drift for 0.4.0 (version range, handoff preparation after the same preflight, the smol-toml runtime dependency), state the operator check order explicitly, refuse an orphan receipt before activation, and document the harness, its Darwin-local evidence and the unrun Linux, Windows non-admin and Node 22 cells. Record the Darwin-local results in VALIDATION.md; they qualify no destination cell. Refs #23
Record one user-authorized, isolated harness run on a Linux arm64 test host (glibc 2.39, Node 24.18.0, published 0.4.0 installed offline into a new /tmp prefix). It covers the exact operator-check, receipt, unlock and caller-switch order, fresh-lock recovery without a receipt, corrupt/mismatched/orphan receipts, .receipts trust, unknown and unverifiable lock-owner overrides, journal-confirmed rollback and retention, and the activation-table rows. All 89 checked expectations matched; existing user executables stayed byte-identical and the test directory was removed. Scope the ADR and VALIDATION wording to exactly that cell. Linux x64 under the current rules, Node 22 (no binary on either host), Windows (the harness refuses; the only host is administrator-only), races and F6 remain unqualified. Refs #23
Address review findings in the #23 validation harness: - dest stage validates the identity file against a closed schema (exact keys and types, version syntax, SHA-256/SHA-512 forms, absolute Node path) and requires the staging name to resolve to a direct child of the root before any write, in dry runs too. Markers use the same schema. - op record accepts an operator dry-run only for the intended no_reachable_target refusal or a normal validated result; transport, host-key, preflight and version refusals no longer pass. - Operator results are bound to what the CLI JSON shows: host equal to op init --dest-host and doctor version equal to the verified install. The --remote-bin used is recorded as remoteBinAttested operator attestation and only checked to name the verified install. - Launcher and native-lock probes run only with --execute: dest probe and dest verify are execute-gated, activate/receipt dry runs classify statically, and unexecuted verify results cannot be recorded. - already_provisioned keeps this run's staging (stagingRetained) for the explicit discard-staging --confirm-destructive step. Add regression tests (traversal/absolute/type/schema identities in dry run and execute, refused transport and preflight dry-runs, host/version /install binding, no launch or probe file in dry runs, retained staging) and record a minimal Linux arm64 rerun with the fixed harness. Cells not rerun keep the earlier harness revision as their evidence. Refs #23
Address the second review round of the #23 validation harness: - dest verify --run <run> --execute binds its result to the run, the prefix sentinel ID and the creation time of the run's current lock; op record accepts a verified result only from the recording run's current lock on the same prefix, so an earlier lock's or another run's verification cannot satisfy the operator-check order. - dest activate writes and syncs an activation intent (target and staging) inside the lock before the rename and renames nothing if that write fails; dest unlock checks the intent against the actual target and refuses while it exists without a matching receipt or the intent is unreadable. - dest discard-staging requires --run and a valid staging marker naming that run; markerless leftovers of a failed stage are removed only by the separate dest discard-leftover --declare-abandoned with the same execute and destructive gates. - The ADR scopes the sentinel rule to commands that inspect or change a prefix; dest identity and dest path-inventory take none. Add regression tests that failed before these changes (stale and foreign verify results recorded, unlock released after a rename whose record failed, another run's staging deleted) and record a second minimal Linux arm64 rerun with the fixed harness. Refs #23
Address the third review round of the #23 validation harness: - Every dest lock acquisition gets a random lockId. Every lock-bound result (activate, verify --run, receipt, unlock, rollback, receipt cleanup, retention), journal record and statement carries the prefix sentinel ID and that lockId, and op record refuses results of another run, another prefix or an earlier lock, including an earlier lock of the same run acquired at the same timestamp. callers_switched requires the publisher's unlock under the lock that published the receipt. - The activation intent's presence and validity are separate: a present intent that is not exactly this lock's closed record (false, 0, empty, null, array, malformed, extra or missing keys, another run, lock, target or staging, or not a regular file) refuses unlock, activation and receipts as activation_intent_unreadable and is never treated as absent. - Split the interrupted-activation test into a deterministic intent write failure before the rename and the persisted post-rename state; no crash between files is physically injected, and the docs say so. - Scope the ADR and VALIDATION wording: the full Linux arm64 cells L3-L8 are historical evidence of harness revision 210759a1 (cc7fe23); the final revision was rerun on that host only for the stated subset. Add regressions that failed before (an invalid intent let unlock release an unreceipted install; an earlier lock's receipt was recorded under a new lock of the same run) plus same-timestamp and another-prefix negatives. Refs #23
…he journal Close two operator-journal freshness gaps in the #23 validation harness: - op record accepts a lock acquisition (prefix ID and lockId) only once per journal: re-recording it, whether replayed after it closed or repeated while current and from any run, is refused as acquisition_replayed and never deduplicated, so an earlier acquisition cannot become current again and revive its old unlock or receipt results. - An acquisition stays current until its own run's unlock of the same pair or a recorded break that closed exactly that pair. A break result closes it only when its prefix ID and broken lockId name an open acquisition recorded in this journal; unowned overrides, other prefixes' or journals' locks and already closed locks are kept as audit records that close nothing, and recording the same break twice is refused as break_replayed. Add regressions that failed before (a replayed earlier acquisition was accepted; a break on another prefix ended the current holder's lock) plus stale-break, duplicate-break and legitimate-recovery cases. The Linux host was not rerun for this journal-only change. Refs #23
Close the out-of-order journal gap in the #23 validation harness: a break recorded before its acquisition (break first, acquisition result recorded later) no longer lets that acquisition become current. op record refuses a lock_acquired whose prefix ID and lockId already appear anywhere in the journal as an acquisition or as a recorded break's broken lockId (acquisition_replayed). A break naming the same lockId on another prefix is an unrelated generation and blocks nothing; existing recovery is unchanged. The ADR now limits "closes nothing" to overrides whose owner pair cannot be identified: a break of a lock with a readable owner whose exact pair is an open acquisition here does close it. Add a regression that failed before (a late-recorded acquisition was accepted after its break) plus the other-prefix case. Verified with the focused harness tests only. Refs #23
The op record comment still listed an unowned override as always closing nothing. Limit it to overrides whose owner pair cannot be identified, matching the ADR: a break with a readable owner whose exact pair is an open acquisition recorded here does close it. Comment-only change; no code line changed. Refs #23
…recordable results Address three code-review findings on the #23 validation harness: - dest discard-leftover removes only a staging tree with no marker entry at all. Any marker entry, whether this workflow's, another manager's, unparsable, invalid, non-regular or symlinked, is refused as staging_has_marker and left byte-for-byte unchanged; --declare-abandoned and the execute/destructive gates are not a permission to delete what something else may own. - op record accepts an operator dry-run only after a doctor recorded since the latest verify of the same target under the same lock, and the operator-checks statement requires the recorded order verify, doctor, dry-run. A re-verify makes an earlier doctor stale. This is the order of journal records, not proof of when the operator commands ran. - dest receipt-cleanup and dest retire results are recordable as receipt_cleaned and retired, with the same executed-result, run, prefix and lock binding as other results; receipt_cleaned keeps mode, receipt state and ownership basis. Other event names stay usage errors. The ADR lists the recordable results. Add regressions that failed before (foreign and invalid markers deleted by discard-leftover; a dry-run recorded before any doctor; cleanup and retention results not recordable) plus reverse-order, stale-doctor, stale-lock, dry-run and other-run negatives. Verified with the focused harness tests only. Refs #23
Distinguish destination results from operator events and state that cleanup and retirement records are audit entries, without changing switch or statement eligibility. No executable code changes. Refs #23
Carry forward the four exact skill profiles, preserve historical evidence and update current multilingual version examples. Linux observation, Windows observation/storage and Windows jump remain unsupported. Preparation only; no staging or publication.
…tory Preserve historical evidence text, keep the supported handoff scope explicit, and pin previous-version SSH refusal plus plausible unreviewed skill profiles in focused fixtures.
…cation replayable Address seven inline review findings on the #23 validation harness: - Caller state is keyed by prefix and install name. callers_switched and callers_unswitched take an explicit --prefix-id that must be a prefix the journal has locked; switches no longer cancel across prefixes, and no-caller/receipt-owned statements and the receipt lookups are scoped to the current lock's prefix. - verify runs launcher and native probes only after every static check (real owned directory, consistent marker, manifest, staging gone) passes. - The explicit lock override removes only the known lock metadata entries, each a file, an unfollowed symlink or an empty directory; unknown entries and non-empty directories are refused before any removal. - dest receipt syncs a publication intent with the exact receipt bytes before writing the receipt. A retry after a lost response under the same lock and proof replays the committed receipt (bytes and verifiedAt unchanged) or completes a lost write with those bytes; changed, corrupt or orphan receipts, stale proofs, other targets and later locks fail closed, and op record accepts one receipt_published per target under a lock. - After run_ended only a run-ended statement is issued, even while the run's lock remains on disk. - callers_switched accepts a publishing acquisition closed by its run's unlock or by a recorded break that closed exactly that prefix and lockId; other-prefix breaks, stale breaks and unknown closures do not count. - path-inventory treats empty PATH components as the current directory and inventories ./session-peer there. Add regressions for each finding; they failed before (two of them, and two existing tests, only on the new --prefix-id argument) and pass after. Verified with the focused harness tests only. Refs #23
…intents Address the root review of the #23 harness publication path: - dest receipt --adopt-existing is an explicit, non-destructive recovery: after a run wrote a receipt, lost the response, ended and had its lock broken, a new run under a fresh lock with fresh verify, doctor and dry-run publishes the still-matching receipt under that lock. Receipt bytes, the original runId and verifiedAt are kept; receipts not matching the marker and manifest, invalid receipts and earlier-lock proofs are refused. - Completing a lost receipt write from a publication intent now requires every static check (and the probes when executed) and the current marker runId, identityHash and recomputed manifest to equal the committed receipt; a rolled-back or changed target is refused as publication_target_changed, and the result is rechecked after the write. Replays also recheck the install. - Publication intents must embed a receipt of the closed schema (exact keys, receiptVersion 1, 32-hex runId, 64-hex hashes, ISO verifiedAt); other valid JSON is refused before any write. - Lock metadata entries of other file types (FIFO, socket, device) are refused before removal. - Caller records without a prefix make the caller count unusable (caller_history_unusable) instead of counting as zero; older journal formats are not supported for caller decisions. - The test helper for a second prefix runs the operator CLI against that prefix's own wrapper. The ADR states statement issuance scope and the one-publication-per-lock versus one-record-per-target distinction. Regressions: the completion, schema, file-type and legacy-record tests failed on 839f2f6 and pass now; the adoption tests failed there on the new --adopt-existing flag. Verified with the focused harness tests only. Refs #23
…ations Keep the 10-second readiness deadline and inspect the persisted synthetic queue record with bounded polling. Report complete child-close output when send exits before queue evidence. Add notification-suppression and early-refusal regressions. This hardens a reproduced fixture weakness; it does not establish the cause of earlier CI failures or retry a CLI submission.
Use one closed receipt schema and one static-check predicate for initial publication, replay and adoption. Reject activation after publication scope is committed so rollback cannot revive an old proof under the same acquisition. Add reuse, adoption, unlock, staging and reactivation regressions; full CLI execution remains required in CI.
Keep release PR draft until dependency #155 merges and final current-main checks complete. No staging or publication.
…ge version Keep the explicit wrong-version refusal while allowing the intended wrong-target regression to reach its own guard after a release version change.
… manifests Address three review findings and one fixture issue on the #23 validation harness: - already_provisioned records the staging tree it keeps in the lock (retained.json, bound to run, lock and target). Until that tree is explicitly discarded, the final verify (stagingGone false, no probe), receipts and adoption depending on it, unlock and further activations are refused; a present but invalid record is refused, never treated as absent. - The manifest's file and link maps have no prototype, so a root entry named __proto__ (file or symlink) is recorded and later changes are detected before verify or receipt. - Receipt publication is synced before success: intent and lock directory, then the receipt file, .receipts and the root, then a publication.synced.json marker, for first writes, completed writes, replays and adoption (existing bytes are never rewritten). Unlock keeps the evidence and refuses while a receipt or its install exists without the marker. Journal records write every byte (short writes continue), are synced, and are truncated back on failure. Test-only environment hooks trace fsync order, inject one fsync failure or force short writes; they add failures only and do not qualify power-loss behavior. - The operator-doctor positive fixture uses the package version instead of a hard-coded 0.4.0; the wrong-version negative stays 0.3.9. Regressions for each failed before (retained staging released, a __proto__ change undetected, no sync guard or journal fault handling) and pass now. Verified with the focused harness tests only. Refs #23
…lback honestly Close two bounded gaps in the #23 validation harness: - Every executed receipt write, completion, replay and adoption now syncs the existing publication intent and the lock directory before the receipt, .receipts and the root, and then writes or resyncs the synced marker and the lock directory before reporting success. A failed intent sync stops before the receipt sync; a failed marker sync happens after the receipt and its directories were synced and fails the step without implying the receipt is lost. - A failed journal append attempts a rollback (truncate to the previous length and sync). journal_append_failed with rollback restored is reported only when that is confirmed; otherwise journal_append_unknown with rollback unconfirmed keeps the original and rollback errors, and the operator must inspect the journal first. A failure never promises the record is absent and the append is never retried. Test-only hooks can now fail only the first N matching fsyncs and fail the truncate. The ADR also states that retained staging only blocks while it is pending; after the explicit discard the lock's statements remain usable. Journal regressions failed on 61f37f8 (harness_error) and pass now, run in a private directory under the external volume's tmp root. The replay sync-order test needs a dedicated prefix, which that root's ancestors correctly refuse; it is unrun locally and left to CI. Refs #23
Preserve the publication ordering contract when an earlier intent write failed its sync. Require the intent and lock directory sync before creating the missing receipt. Add a regression for repeated intent-sync failure; full CLI execution remains a current-head CI gate. Read-only paired Claude/Codex review found no mandatory defect. Refs #23
Backport the independently reviewed release-preparation test corrections: persisted queue and initialize readiness with unchanged deadlines, early-close diagnostics, and a retained synthetic warm WAL connection. Include the discriminating notification and keeper regressions. Runtime guards, delivery assertions and platform support are unchanged; the original CI causes remain unproven. Refs #23
# Conflicts: # test/deployment-receipt-harness.test.ts
… the harness Address six review findings on the #23 validation harness: - The attested --remote-bin must be an absolute POSIX path that, after lexical normalization (never resolved remotely), equals exactly the wrapper path reported by the verified destination result (a new harness-only verify field); detours, other wrappers and other prefixes are refused. It remains operator attestation. - Manifest links must resolve inside the install, also through chains; absolute, escaping, dangling or looping links are refused as unsafe_link during staging before anything reads through the tree, and before every probe. Bounded internal links stay allowed. - Executed op begin and op record hold an exclusive <journal>.lock (mkdir) across read, validation, append and rollback; contention is refused as journal_busy without waiting, stealing or killing, and dry runs and op statement refuse while it is held without writing. A lock left by a crash is removed by the operator after checking. No claim covers hostile same-user writers or power loss. - callers_switched and callers_unswitched are refused while any recorded acquisition on the prefix is open (acquisition_open), so a statement issued under an open lock stays true. - A publication is superseded by a later rollback, retirement, receipt cleanup or new activation of the same prefix and name (publication_superseded); a newer completed publication is required. The ADR replaces the earlier audit-only description. - npm receives tarballs as absolute paths of regular files, so none can be read as an npm option; the hashed path is the one npm receives. Journal-only regressions (journal lock contention and rollback isolation, caller fences and remote-bin binding on shaped destination results) failed on e9f99e0 and pass now in a private directory under the external volume's tmp root. Dedicated-prefix fixtures (manifest links, npm argv, CLI caller and supersede flows) are unrun locally and left to CI. Refs #23
…n the harness Apply root's design refinements to the six E9 fixes: - The attested --remote-bin must already be a canonical absolute POSIX path (no ., .., repeated or trailing /) equal to the verified wrapper; a path that only normalizes to it is refused, because a shell resolves link/.. through the link. Nothing is resolved locally or remotely. - op statement and journal dry runs now hold the ephemeral journal lock across their read, so a statement never snapshots a line that a concurrent failed append later rolls back. The ADR states the lock directory as their only effect, that a crash can leave it, and that it is removed by the operator; no stale takeover. - The ADR records that absolute links are always refused (they go stale or outside after the activation rename) while relative internal links survive it with referents covered, and that activated records without an outcome from earlier revisions fence as new activations. Tests: the shaped remote-bin case now requires the exact wrapper (it fails on 8c6f224's normalizing check), the rollback-isolation test also checks that a statement is refused during a held append, and the internal-link fixture checks that the link survives activation and that referent changes are detected. Refs #23
Add a test-only pause point after a failed journal append and before its rollback. A deterministic regression holds a complete appended lock_acquired line on disk while its rollback waits: a statement and a second record are refused as journal_busy, and after the confirmed rollback no statement can be issued from that acquisition (lock_not_recorded). The retire test notes that releasing, unswitching and locking again is the same as unswitching before the lock, with the guard unchanged. No production behavior changes. Refs #23
…he OS does Address five review findings on the #23 validation harness: - Manifest links are followed one component and one link at a time as the OS would; every intermediate location must stay inside the install, so a hop out through another location or a .. above the install root is refused even when the chain comes back. Absolute, dangling and looping links stay refused, and nothing is probed before the check. Relative internal links survive the activation rename and their referents stay covered. - Tarball paths are interpreted by the OS (lstat and realpath of the given path), never lexically, so a symlinked ancestor followed by .. selects the file the OS reads; the final component must be a regular file and npm gets its absolute real path. - A journal must have exactly one link (journal_aliased), and the open file is checked to be that same single-link file, unchanged since the read, before an append and before its rollback. - A cleanup, retirement or rollback recorded since an acquisition closes its publication: a later receipt result under the same lock is refused (publication_closed); recovery needs a new lock and checks or the explicit adoption. - op init holds the journal lock from the exclusive create until the header line and newline are written and synced; its dry run holds it only to check for an existing journal. The harness can now be imported by its tests (pure helpers exported; the command runs only when executed), and the test file accepts a test-only DEPLOYMENT_HARNESS_SCRIPT to compare against another revision's copy without replacing the worktree file. Refs #23
Use native realpath for the raw tarball path so symlink ancestors followed by .. select the OS file. Require directories for every remaining link component, including a trailing slash or dot. Refuse a journal lacking its final LF before any append, without repair or migration. Add discriminating filesystem and journal regressions and align init coordination comments. Two focused regressions, syntax, types and repository checks passed locally; dedicated-prefix stage fixtures remain a current-head CI gate. Fresh paired read-only Claude and Codex supplemental review found no mandatory issue. Refs #23
Exclude the #155 harness, its dedicated fixtures and new deployment qualification claims. Restore the existing main-branch ADR and retain independently reviewed handoff fixture readiness fixes. Preserve all preparation history; #23 remains open and minimum-contract redesign is tracked separately. Refs #158
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Merged-source release preparation — 2026-10-20 KSTPR #157 is merged at
Proposed publication plan — not yet approved or dispatched
No upload/stage, publication dispatch, tag, GitHub release or operations change has occurred. Separate approval of the exact source/publication plan is the next gate. |
Public 0.5.0 verification — 2026-10-21 KSTPublication completed with the user's explicit exact-source/version/mode and tag/release approval; the user also completed the protected GitHub npm environment and final npm stage approvals.
#155 remains Draft/deferred/excluded; #158 is design-only with no committed version. This release does not complete unsupported platform qualification, publish/repin the companion skill, update user-global installations, or perform native/model/SSH/ACK experiments. No repeated stage upload occurred. |
Summary
Prepare
session-peer@0.5.0from the merged platform groundwork in #153, #156 and #154. This is release preparation; no staging or publication is performed or authorized by this PR.User-approved scope reduction
#155 is excluded and is no longer a dependency. The deployment harness expanded beyond its qualification purpose before its overall authority/state contract was fixed. It is now Draft, removed from the 0.5.0 milestone, and preserved for redesign in #158 under still-open #23.
docs/design/remote-deployment.mdbyte-for-byte from mainba44171ed5d2c7882006d6bc40d6a300448d0b0b.Included scope and preserved limitations
Refs #130, #131 and #20 remain partial; do not close their unsupported qualification cells or declare the whole milestone complete. #23 and #158 have no committed release version. Experimental #82/#142 are excluded.
Candidate and validation
Current source:
d824d17372b3eef2d9c343b89a09eeeee16362dd. Main basis:ba44171ed5d2c7882006d6bc40d6a300448d0b0b.5f6546arelease-branch checkpoint.claude-opus-5-5, actual model confirmed) and Codex CLI 0.163.0 (isolated default; actual model not exposed) reviewed the original release-preparation changes and separate fixture corrections on their recorded immutable inputs. These are historical scoped reviews, not a claim that every later source was re-reviewed by those models.Publication gates
A fresh separate read-only registry validation confirmed the public baseline and absence of 0.5.0: previous stable 0.4.0, preview 0.1.0-preview.1. No npm upload/stage or publish workflow is dispatched. Human merge of this preparation PR and later separate exact merged-main SHA/version/mode/latest-promotion approval, fresh authenticated release checks, protected npm environment approval and final npm 2FA remain required by RELEASING.md. Historical exceptions or authentication approvals do not carry forward. No tag, GitHub release, global install or operations/protection change.
Current artifact checkpoint
Source
d824d17372b3eef2d9c343b89a09eeeee16362dd; local tarball SHA-25650f603d9e5b6db8a99b4bcab464767ad30ff38ecac3fbebc22fc03fdd4c66b1d. This is a reviewed preparation candidate, not an approved publication artifact or an Actions/staged package. Current-head CI passes 12/12; no publication approval is requested here.Final source reconciliation
An independent read-only static audit of
d824d17372b3eef2d9c343b89a09eeeee16362ddfound no remaining material mismatch after correcting the release-procedure scope sentence. It confirmed removal of the harness/dedicated fixtures, exact main ADR restoration, the retained reviewed handoff fixture checkpoint, finite profile/version alignment and unchanged unsupported boundaries. This was source reconciliation, not another external CLI run or native qualification. Current-head CI and both automatically triggered reviews pass as recorded below.Final current-head checks
d824d17372b3eef2d9c343b89a09eeeee16362dd: all 12 GitHub checks pass, including macOS/Ubuntu Node 22/24, Windows-native Node 22/24, release gate, repository policy, CodeQL and GitGuardian. CLI run 39413751652.Ready for human review/merge. No merge, release, tag, workflow publication, npm stage/publish, global installation or operations change was performed. After a human merge, verify the new exact main source and obtain its separate publication approvals under RELEASING.md.