Skip to content

feat(observation): select native profiles by exact platform tuple and record a Linux arm64 candidate - #153

Merged
abruption merged 6 commits into
mainfrom
abruption/sts050-130-linux
Oct 20, 2026
Merged

abruption merged 6 commits into
mainfrom
abruption/sts050-130-linux

Conversation

@abruption

Copy link
Copy Markdown
Owner

Summary

Refs #130. Preparation PR. Native profile selection becomes platform-aware (exact tuple table), the Linux procfs machinery is implemented and tested, and a linux-arm64 / Codex 0.162.1 candidate is recorded with real artifact, schema and injection evidence, but it is not activated. Every Linux tuple remains unsupported (locally and over SSH) until the frozen follow-up design below is implemented and reviewed.

What is implemented

  • src/native-profiles.ts: frozen, validated exact (platform-arch, version, commit, platformOs, storageBinding, ownerInspection, sshDestination, artifact) rows; no SemVer range, libc/arch alias or cross-platform inheritance; new rows must pin the executable SHA-256/target; Linux rows can never be selected by SSH destination requests. Selectable rows: darwin-arm64 0.160.1/0.162.1 (unchanged). CANDIDATE_PROFILES holds the non-selectable linux-arm64 0.162.1 entry.
  • app-server.ts: exact (version, platform) queue capability and schema evidence (linux-arm64 0.162.1 schema hashes recorded as evidence only); initialize.platformOs matches the row; pinned artifact digest and Linux procfs binding run in an owned worker; at the original remaining budget the supervisor requests group termination and the caller refuses, while actual reaping of a worker blocked in kernel I/O may occur later. Caller-side realpath/stat/lstat stay synchronous with deadline checks only, as on Darwin.
  • Linux machinery: owned-child /proc/<pid> storage binding (direct child, own group, all UIDs, exe inode, stable start time, exact canonical paths, deleted descriptors refused; shims refused); writer probe --procfs-owner (/proc/locks sole exclusive flock holder = lsof owner, stable start time/UIDs) without changing the generation.
  • metadata-observation.ts / handoff-operations.ts: exact (version, platform) qualification and retained resume; foreign-platform retained records terminate unsupported before any probe. Fix: the structural capability check no longer becomes ambiguous when one version has rows on two platforms (found by the live run as a pre-effect refusal; regression test).
  • cli.ts (one token): destination wire requests pass remote so SSH requests cannot select Linux rows.
  • Docs: all locales, API, PARITY (including the frozen activation design) and VALIDATION; 0.4.0 scope statements unchanged.

Recorded Linux arm64 evidence (candidate, not activated)

  • Artifact: the host's standalone binary SHA-256 071cbe8d…cabe5 equals the executable extracted from upstream rust-v0.162.1 codex-aarch64-unknown-linux-musl (.tar.gz bf3ca271…, .zst).
  • Schema: Linux-generated queue/add, item/started, turn/completed and 84-method notification hashes equal darwin 0.162.1.
  • Live (separately user-approved, with the candidate temporarily selectable): one dedicated TUI thread; "Update available" menu dismissed with Esc; one required bootstrap turn (a new thread is unsaved until its first turn); the first send was refused before effect by the capability bug; one approved resend observed exact client-ID injection with unchanged generation; handoff status and an independent wait succeeded; ACK not requested; 2 model turns total. Fixture: test/fixtures/linux-observation-native-evidence.json (identifiers omitted). Whether the Darwin dedicated threads had a prior turn is not recorded.

Why the candidate is not activated (review round 2)

  1. Only the owned queue child's executable is digest-pinned; the original TUI writer is checked by lsof command/pid/uid and ps lstart, so a different-version or gnu writer beside a pinned musl queue binary is not distinguished.
  2. The writer's kernel start time is compared only inside one probe and is not retained across send and independent wait (the generation binds ps lstart at 1-second resolution).

Closing (2) safely needs an optional private ledger field, so the design is frozen in PARITY.md ("Linux row activation follow-up design": writer /proc/<pid>/exe binding; optional observationOwner field with start time/boot ID/exe identity; fail-closed for missing/old records; an end-to-end CLI check for a missing destination remote flag; procfs lock/device matching verified only on ext4; negative test list) instead of pushing an unreviewed schema change.

What remains unqualified

All Linux observation cells (including the recorded candidate), SSH-remote observation, Windows (#131), automatic receipt/ACK (#132). External review inputs received summaries of the native send/status/wait JSON and generation values, not raw records; the root review audited the raw records separately.

Validation checkpoints

  • Full suites at the dd21302805ae215273ae7c5e59ef514c47e8eb68 source checkpoint: macOS arm64 / Node 24.16.0 / pinned Python reference 8ca77cf, npm test: 408 total / 400 pass / 0 fail / 8 skip; Linux arm64 / Node 24.18.0: 404 total / 376 pass / 0 fail / 28 skip. The Linux run used fixtures only, without credentials, a model turn or an installation change. These are executor-recorded results; the raw Linux log does not contain a HEAD marker. It is associated with the checkpoint by the executor's run record, rather than an embedded commit attestation.
  • At ffe8afc75538a8283df4eb3e2dae9a65d8125002 and current head 104e3dcb0296ab6b11a7733a2a4b6be480e09f68: npm run build, npm run test:types, repository and diff checks passed; focused checks: 80 pass / 0 fail / 3 skip at each. These heads change documentation, comments and an evidence-description string; no runtime code statement changed. No new full-suite, Linux-host or live-model run was performed at these heads.
  • Root independently ran npm run test:package at the exact ffe8afc75538a8283df4eb3e2dae9a65d8125002 source export: PASS (isolated install/uninstall and package allowlist). node scripts/release.mjs pack initially stopped because a Git archive has no Git metadata; after supplying the helper's supported GITHUB_SHA equal to that exported commit, pack and artifact checks passed. The initial failure and corrected run are retained. No publication command ran. The two source files changed by 104e3dc have identical comment-stripped compiler output to ffe8afc; these checks are not claimed as new full-suite or packaging runs at 104e3dc.
  • Historical regressions: at 2f3b35d (the candidate was selectable) the Linux SSH regression failed with the wire flag removed and passed with it; at b804115 the two-platform capability regression failed before its fix and passed after. Those are checkpoint-specific observations. The current candidate remains disabled, and activation requires a discriminating end-to-end remote-flag test.
  • The repeated Linux total 404 / 376 / 28 in two recorded runs is consistent with the test-name changes: the selectable-row SSH case was replaced by the Linux refusal case. Matching counts are not claimed as a fresh run or independent runtime validation.

Out of scope

Ledger schema changes, version bump/release, Python/skill pins, relay/device (#29-32), SideSession/Steer, automatic receipt, MCP and wake. PR #82/#142 are excluded.

Replace version-only native selection with one exact table keyed by host
platform-arch and native version, carrying source commit, initialize
platformOs, storage-binding and owner-inspection methods and a pinned
executable artifact. Production keeps only the historical darwin-arm64
Codex 0.160.1/0.162.1 rows; there is no version range, libc/arch fallback
or cross-platform inheritance, and every Linux tuple stays unsupported.

Add Linux machinery for a future evidence-backed row without qualifying
one: owned app-server storage binding from that child's /proc entries
(direct child, own group, all UIDs, exe inode, stable start time, exact
canonical SQLite paths, deleted descriptors refused), a writer-probe
--procfs-owner mode that requires /proc/locks to name the lsof owner as
the sole exclusive flock holder without changing the generation, and
ELF/Mach-O artifact digests within the original deadline. Retained
qualifications keep their original platform and a foreign-platform record
terminates unsupported before any probe.

Tests use injected procfs fakes on every host, a compiled hermetic
stand-in for exact artifact pinning, and Linux-only native cases for real
procfs/flock, CLI refusal/downgrade and fake-SSH complete/partial results.

Refs #130
Add the exact linux-arm64 / Codex 0.162.1 / 092d3acd row pinned to the
upstream aarch64-unknown-linux-musl executable digest, with Linux-generated
app-server schema evidence and procfs storage/owner inspection. Other Linux
architectures, versions and gnu builds remain unsupported.

Fix the structural metadata capability check, which used a version-only
lookup that became ambiguous once one version had rows on two platforms and
refused delivery before effect. A regression test reproduces it.

Record the Linux arm64 live evidence: matching artifact and schema hashes,
a required bootstrap turn for the new dedicated thread, the pre-effect
refusal found by the live run, and one observed client-ID injection with
unchanged writer generation and a satisfied independent wait.

Refs #130
…tive I/O

SSH destination requests could select the qualified linux-arm64 row and
enter the app-server path, although SSH-remote observation has no Linux
evidence. Profiles now carry sshDestination; Linux rows must refuse it, so
a wire request on a Linux row behaves as unsupported: required delivery
refuses before effect with zero queue calls and best-effort uses the
ordinary queue. Darwin keeps its 0.4.0 destination behaviour.

Move the executable digest and the procfs storage binding into an owned
worker run under the original remaining budget, refusing at that deadline
even if the worker is blocked in kernel I/O. Document the retained-identity
boundary (generation binds ps lstart at 1-second resolution; kernel start
time and the writer executable are not retained) and fix stale comments.

Refs #130
…binds

Review found two gaps in the qualified linux-arm64 / Codex 0.162.1 row: only
the owned queue child's executable is digest-pinned while the original TUI
writer is checked by lsof command/pid/uid and ps lstart, and the writer's
kernel start time is not retained across send and independent wait. Closing
the second safely needs a reviewed optional ledger field, so the row moves
to a non-selectable CANDIDATE_PROFILES entry: every Linux tuple is
unsupported again, locally and over SSH. The recorded artifact, schema and
injection evidence stay, marked not activated with both blockers.

Freeze the activation design in PARITY.md (writer /proc/<pid>/exe binding,
optional private observationOwner field, required negatives). Keep the
capability and SSH-destination regressions on hermetic tables. Limit the
worker comment's hard bound to its supervisor refusal; caller-side
realpath/stat/lstat stay synchronous with deadline checks only.

Refs #130
The candidate evidence limit now says it does not qualify the row,
matching activated: false and its blockers. The linux-arm64 queue schema
entry is described as candidate data consumed only by profile selection
and tests. The frozen activation design also requires an end-to-end check
for a missing destination remote flag and records that procfs lock and
device matching was exercised only on ext4.

Refs #130
…rding

State that the full-suite counts belong to the dd21302 source checkpoint
and that later documentation-only heads ran light gates only, and update
the validation record date. Describe the worker deadline as a requested
group termination with caller refusal; reaping a worker blocked in kernel
I/O may happen later.

Refs #130
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T06:10:23.460295Z 104e3dc PR opened
🔒 Security Review ✅ Completed 2026-10-20T06:10:53.018973Z 104e3dc PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@abruption
abruption merged commit f7afe34 into main Oct 20, 2026
12 checks passed
abruption added a commit that referenced this pull request Oct 20, 2026
Align the release procedure with the user-approved scope: #153, #156 and #154 only; defer #155 to checklist-first contract redesign #158.
abruption added a commit that referenced this pull request Oct 20, 2026
* test(ssh): add operator-invoked deployment receipt and recovery harness

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

* docs(ssh): record Linux arm64 deployment harness requalification

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

* fix(ssh): harden deployment harness input, operator binding and dry runs

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

* fix(ssh): bind harness verification to its lock and guard activation

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

* fix(ssh): bind harness results to a per-lock id and validate the intent

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

* fix(ssh): refuse replayed lock acquisitions and unrelated breaks in the 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

* fix(ssh): treat a recorded lock break as a tombstone for its acquisition

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

* docs(ssh): align the harness lock-break comment with the ADR

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

* fix(ssh): tighten harness leftover removal, operator-check order and 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

* docs(ssh): clarify deployment journal event and audit scope

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

* chore(release): prepare 0.5.0 platform qualification groundwork

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.

* docs(release): align API examples and retain immutable validation history

Preserve historical evidence text, keep the supported handoff scope explicit, and pin previous-version SSH refusal plus plausible unreviewed skill profiles in focused fixtures.

* fix(ssh): scope harness caller state by prefix and make receipt publication 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

* fix(ssh): complete harness receipt recovery and validate publication 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

* test(handoff): observe queue evidence without relying on file notifications

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.

* fix(harness): share receipt validation across completion consumers

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.

* test(release): derive successful deployment doctor fixture from package version

Keep the explicit wrong-version refusal while allowing the intended wrong-target regression to reach its own guard after a release version change.

* fix(ssh): track retained staging, sync receipt publication and harden 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

* test(handoff): keep warm fixture WAL identities stable

* docs(release): distinguish current source from historical platform trials

* fix(ssh): resync existing publication metadata and report journal rollback 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

* fix(ssh): sync pending intent before completing a missing receipt

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

* test(handoff): observe persisted initialize readiness and early exits

* test(handoff): stabilize synthetic observation and readiness fixtures

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

* fix(ssh): bind remote-bin, links, journal writers and caller state in 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

* fix(ssh): require the canonical wrapper path and lock journal reads in 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

* test(ssh): pin the journal rollback window and the retire unswitch order

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

* fix(ssh): follow link chains, tarball paths and journal identity as the 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

* fix(ssh): preserve native paths and reject incomplete journal records

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

* chore(release): defer deployment harness and narrow 0.5.0 scope

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

* docs(release): remove the deferred harness from 0.5.0 procedure

Align the release procedure with the user-approved scope: #153, #156 and #154 only; defer #155 to checklist-first contract redesign #158.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant