Skip to content

sync: converge nightly into dev (share-flow removal + accumulated 0.7/0.8 commits) - #1249

Open
Koh0920 wants to merge 183 commits into
devfrom
sync/nightly-into-dev-20260804
Open

sync: converge nightly into dev (share-flow removal + accumulated 0.7/0.8 commits)#1249
Koh0920 wants to merge 183 commits into
devfrom
sync/nightly-into-dev-20260804

Conversation

@Koh0920

@Koh0920 Koh0920 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Full sync of nightly into dev. Primary purpose: bring the web-share-flow removal (ato#1241 — remove encap/decap + /v1/shares web upload/fetch, keep local-file sharing) forward toward release. Also carries the 180 other accumulated 0.7/0.8 commits on nightly (authoring builder, ready-state/snapshot, desktop auth, fix/feat line — see below).

nightly was 182 commits ahead of dev (and dev had 3 commits not on nightly: pocketllm-demo /open URL fix + retrigger + docs). The merge was clean — 0 conflicts; no manual resolutions required.

Contents of the 182 nightly commits (by scope)

  • 61 fix / 59 feat across cli, capsule, snapshot, snapshot-builder, desktop, netd (authoring build-diagnostics, ready-state acceptance, v1 execution-contract, hardware-binding, native-inference, runner leases, desktop shell/Home, and more)
  • 10 test, 10 docs, 5 auth, 2 refactor, 2 ci, 1 desktop, 1 style

Verification

  • git merge origin/nightly into a branch based on origin/dev: 0 conflicts.
  • The share-removal commit d3695ade (ato#1241) is an ancestor of the merge head.
  • cargo check -p cli at the merge head: clean (0 errors).
  • Real CI (rust-ci.yml + cli-ci.yml + the contract/architecture gates on dev) is the authoritative gate for this PR — please let it run before merging.

This is a mechanical branch sync; the individual feature PRs were each reviewed on nightly before landing there.

Koh0920 and others added 30 commits July 15, 2026 02:29
…ort pixel_rfb_port

The session-surface arc (#1062) shipped the restore/gateway side of
ato.pixel-stream.v1 but no build lane could produce a pixel artifact:
every import lane sealed surface_requirement=None and no lane emitted the
explicit pixel_rfb restore endpoint the runner's restore gate requires
(selected_pixel_rfb_port fails a pixel descriptor without exactly one).

- dockerfile_import params gain pixel_rfb_port (strict u16, fail-closed
  parse mirroring port_override). Presence opts the import into
  SessionSurface(kind=pixel_stream, ato.pixel-stream.v1).
- The producer derives + validates the sealed contract pair fail-closed
  (pixel_import_contract): host_internal app_http readiness endpoint +
  guest_private first_frame pixel_rfb endpoint, re-validated through
  EndpointContract::validate; the app-port collision is refused at plan
  derivation and again at produce time.
- process_job seals produced.endpoints into the manifest restore
  contract; Web artifacts seal an empty list so every pre-existing
  manifest stays byte-identical.
- DockerImportOptions/DockerfileImportRequest carry pixel_rfb_port as an
  identity input, skipped when None so pre-existing import descriptors
  keep their identity digests (host_bind_relay discipline).
- The recipe lane now fails closed on a pixel_stream capsule.toml surface
  requirement instead of sealing an artifact that can never restore.
- docker_import_build dev CLI gains --pixel-rfb-port.
A terminal-class GUI workload allocates ptys (xterm opens /dev/ptmx) and
the minimal guest init never mounts devpts — the live KVM build failed
with 'get_pty: not enough ptys'. Mount it ONLY for a pixel build
(pixel_rfb_port set), fail-closed like the managed mounts, so every Web
import keeps a byte-identical init (same rootfs digest / identity).
…g named for a webview

login_with_store_device_flow_desktop (used when ato-desktop spawns `ato
login` as a child process) now calls try_open_browser on the computed
login_url, the same helper the plain interactive `ato login` flow already
uses. Previously it only emitted the URL over NDJSON and relied on the
desktop app to render it inside an embedded WebView, which is what this
change (and the paired desktop-side commit) removes.

Renamed the CLI flag from --desktop-webview to --desktop since there is no
more webview involved, and renamed the internal desktop_webview bool in
run_publisher_onboarding_flow to non_interactive, which is what it actually
distinguishes (no TTY available to prompt).
…op instead

auth_login_window.rs opened the auth_bridge login URL inside an
app-embedded Wry WebView, which departs from RFC 8252 (OAuth for native
apps must use the system browser). The CLI's login_with_store_device_flow_desktop
now opens the OS default browser itself, so the Dock's Login command no
longer needs to open any window at all - it just spawns `ato login
--desktop`, watches its NDJSON stdout for a terminal event, and refreshes
the Dock identity or re-shows the Dock on failure, exactly like the old
window's completion handling did minus the WebView.

- Deleted window/auth_login_window.rs and its module declaration.
- Moved the child-process spawn/watch logic into ato_dock as a private
  trigger_login helper (DockCommand::Login's only job now).
- Removed the AuthLoginWindowSlot global from app.rs.
- Removed ContentWindowKind::Auth and its three match arms
  (card_switcher's glyph_for/kind_tag, ato_start's snapshot builder) - no
  window of that kind is ever opened anymore.
- DockCommand::Login no longer creates a WebView, so its required
  capability changed from WebviewCreate to LaunchSystemCapsule (matching
  the analogous StoreCommand::Login), and WebviewCreate was dropped from
  AtoDock's capability allowlist since Login was its only consumer.
- Updated the stale ato-store comments that still described the old
  embedded-WebView design.
…s/URL to Dock UI

Round-1 review fixes for ato#1077 (Desktop external-browser login):

- Add a release gate (EXTERNAL_BROWSER_LOGIN_HARDENING_LANDED, default
  false) in login_with_store_device_flow_desktop: this path widens
  exposure to auth_bridge.ts's current auto-approval / session-token-copy
  weaknesses by routing through the OS browser instead of an app-isolated
  WebView. ato#1077 and RFC ato-api#261 rev.3 §6 both require this to stay
  off until ato-api#275 (Phase 1b hardening) has actually landed and
  deployed, not merely been authored (verified via `gh pr view` that
  ato-api#279/#281 are still open drafts). Fails closed with a clear NDJSON
  error naming the dependency; regression-tested.

- Fix the fail-silent Desktop login UX: `ato login --desktop` now emits a
  structured `desktop_browser_launch_failed` NDJSON event (carrying
  login_url) when the automatic browser launch fails, instead of only an
  eprintln! that ato-desktop discarded via Stdio::null(). ato_dock now
  captures login_url, forwards this event and the terminal failure message
  into the Dock's existing (previously unused) event_queue/toast channel
  via a new dock_event_queue() accessor, so denial/cancellation/timeout/
  HTTP errors are no longer silently invisible to the user.

- Add regression tests that would have caught both classes of defect:
  CLI flag-parsing tests for `--desktop` wiring, a pure classify_ndjson_line
  unit (extracted from watch_login_completion) covering success/failure/
  forward/ignore classification, and an async test asserting the bridge-
  hardening gate trips before any network/filesystem work.

Verified: cargo check clean for both crates; full cli test suite (2897
tests) and ato_dock tests (7/7) green; cargo clippy clean on touched files;
cargo fmt clean on touched files. Confirmed the 25-27 pre-existing desktop
test failures (env-lock poisoning in webview/settings/materializer tests)
reproduce identically on the pre-fix commit — unrelated to this change.
… copy, actionable link toast

Addresses five round-2 findings on ato#1077's external-browser Desktop
login:

- trigger_login had no single-flight guard against overlapping Login
  invocations (regression from the deleted embedded-WebView flow's
  window-reuse check). Add LOGIN_IN_FLIGHT plus try_begin_login/end_login,
  wired through a guarded trigger_login wrapper around the existing spawn
  logic (renamed to trigger_login_inner); released on every exit path,
  including early errors before a child process is ever spawned.

- The fail-closed gate's message (EXTERNAL_BROWSER_LOGIN_HARDENING_LANDED
  pending ato-api#275) was forwarded verbatim into the Dock's user-facing
  toast, showing real users a multi-sentence paragraph naming internal
  GitHub issue numbers with no next step. Split the gate's messaging into
  a developer-facing detail (stderr/bail!, still names ato-api#275 for
  logs) and a clean user-facing message that points at the one login path
  that still works today: `ato login` from a terminal (not gated by this
  constant). The security posture itself (fail closed before any
  network/filesystem work) is unchanged — that's a deliberate, tested
  decision from round 1 and outside this lane's scope to relax.

- desktop_browser_launch_failed baked login_url into the message string,
  so the Dock could only show it as unclickable plain text in a toast
  that auto-dismisses in ~2.8s — not enough time to read or copy a URL.
  login_url now travels as its own JSON field; App.jsx renders it as a
  clickable link plus a "Copy link" button and keeps the toast open until
  manually dismissed instead of auto-dismissing.

- JS-side single-flight guard: handleLogin now tracks a `signingIn` state,
  ignores repeat clicks, disables/relabels the "Sign in with Ato" button
  while a login is in flight, and stays in sync with the Rust-side guard
  via the desktop_login_failed / desktop_login_in_progress events.

New/updated tests: login_guard_rejects_concurrent_acquire_until_released,
desktop_login_gate_user_message_has_no_internal_jargon,
forwards_browser_launch_failure_with_login_url_as_a_separate_field (+
without_login_url variant). Existing
desktop_device_flow_fails_closed_pending_bridge_hardening still passes
unchanged since the security-relevant bail! text is untouched.
…e substitute

The fail-closed gate's user-facing fallback recommended `ato login` in a
terminal "instead," implying it was a safe alternative to the very
OS-browser exposure the Desktop gate blocks. It isn't: plain `ato login`'s
existing non-headless branch already opens a real OS browser against the
same unhardened auth_bridge.ts endpoints today on main. Reworded the
fallback copy to drop the "instead" framing, and made the doc comments
explicit that this is a pre-existing, already-shipped, separately-tracked
gap rather than something silently walked back by the message — gating the
CLI path too is out of scope for this Desktop-only lane (ato#1077).

Added a regression test pinning the absence of the "safe substitute"
framing while still requiring the message to name a working next step.
…erage, sanitized failures, persistent failure toasts

Addresses five Major findings from round-4 review of ato#1077:

- store.rs: EXTERNAL_BROWSER_LOGIN_HARDENING_LANDED flipping is no longer a
  bare, discipline-based boolean — a companion EXTERNAL_BROWSER_LOGIN_HARDENING_EVIDENCE
  constant must be updated in the same commit, and
  hardening_flag_requires_recorded_evidence_when_enabled fails CI if it isn't.

- store.rs: extracted compute_poll_timing, browser_launch_failed_event, and
  sanitize_bridge_failure as pure, directly unit-tested functions covering the
  previously-untested browser-open/NDJSON logic that sits behind the fail-closed
  gate, and deduplicated poll-timing arithmetic between the plain-CLI and
  Desktop device-code flows so the two paths cannot silently diverge.

- store.rs + mod.rs: the desktop flow's poll/exchange/init failure branches no
  longer forward raw ato-api HTTP status/response bodies verbatim into the
  Dock's user-facing toast. sanitize_bridge_failure splits each failure into a
  generic user message and a `detail` string that only reaches tracing::warn!
  logs, mirroring the dev/user split the fail-closed gate message already had.

- App.jsx: desktop_login_failed toasts (including the fail-closed gate's
  explanation) now persist until dismissed instead of auto-vanishing in
  ~2.8s — persistence was previously keyed only on a URL being present, which
  this event never carries.

- store.rs: added an explicit RELEASE-SEQUENCING NOTE flagging that this PR
  removes the only working in-app Desktop login until ato-api#275 lands,
  deploys, and the gate is flipped — requires an explicit human sign-off
  before promoting this branch past nightly/dev, which this commit cannot
  itself provide.

Verified: cargo test -p cli --lib application::auth:: (44 passed) and
cargo test --bin ato-desktop ato_dock (10 passed, both crates their own
workspace), cargo clippy -p cli --lib --tests -D warnings (clean), npm run
build in ato-dock (clean).
cargo fmt formatting for the new LoginCompletion::Failure variant and the
new test assertions added in the previous commit.
- imported_pack_script gained pixel_rfb_port on nightly; dev's seed-files
  test call passes None (web import, no pixel endpoint)
- ProducedBuild gained endpoints on nightly; compose_import produces a Web
  artifact, so it seals no restore endpoints (legacy ports projection stays
  authoritative)

cargo test -p snapshot -p snapshot-builder -p guest-agent: all green (349+54+…).
merge: forward-port dev into nightly (compose_import joins pixel-surface lane)
…n review)

Integration review, DESKTOP-LOGIN-GAP (Major): this branch removes the embedded
WebView login while the new external-browser login is gated behind
EXTERNAL_BROWSER_LOGIN_HARDENING_LANDED (false until ato-api #275 is deployed).
A standalone desktop release cut while the flag is false would ship with NO
working in-app login.

The existing CI test already prevents flipping the flag true without recorded
evidence; this closes the other direction — add a publish-release gate to
desktop-release.yml that FAILS the release if the flag is still false, so a
desktop release cannot be published until #275 is live and the flag is flipped.
The gate only runs on release tags / workflow_dispatch, so it doesn't affect
normal branch CI; it activates exactly when the WebView removal would otherwise
ship a login-less build.
desktop: open OS browser for login instead of embedded WebView (#1077)
…y restore readiness

P0 — Seal point
* Add `[snapshot] warmup_paths / stable_successes / stable_interval_ms /
  content_ready_path` to the capsule manifest's SnapshotConfig (additive — an
  empty `[snapshot]` parses byte-identically to v1) and thread them through
  `snapshot::RestoreContract` so they ride the sealed artifact to the runner.
  `effective_*` apply the v1 fallback (1 success / 250ms), so a manifest sealed
  before this change restores unchanged.
* `FirecrackerBackend::build_ready_state` hits every `warmup_path` until it is
  stable for `stable_successes` rounds AFTER the healthcheck answers and BEFORE
  `Paused` + `CreateSnapshot`. The sealed memory therefore already carries
  template generation, JIT, DB init and First-Frame prep — the user's first
  request hits warm pages instead of redoing that work after resume. Skipped
  for the required-binding supervisor carve-out (its workload is stop+revoke'd
  before the seal, so warming it would be wasted I/O).
* `boot_timeout` is the only budget for that wait. A shorter private round cap
  would fail the build on exactly the ~3s post-health first-screen work this
  feature exists to absorb; a broken path still fails closed, it just takes the
  full boot budget to prove it.
* Recipe lane takes the recipe from `[snapshot]`; the import lanes (no
  capsule.toml — dockerfile / OCI / compose) from operator env
  (`ATO_SNAPSHOT_BUILDER_WARMUP_PATHS` / `_STABLE_SUCCESSES` /
  `_STABLE_INTERVAL_MS` / `_CONTENT_READY_PATH`). Both go through one
  `WarmupRecipe`, so every lane freezes the same fields by the same rule.
* Probe paths are validated (origin-form, no spaces/control chars) wherever a
  recipe is built. A typo now fails with a pointed error instead of an opaque
  warmup timeout or a full boot-timeout restore hang, and a CR/LF can never be
  smuggled into the guest probe's request line.

P2 — Restore readiness = user first-screen
* `restore()` judges readiness with `content_ready_path || healthcheck || /`,
  not only `/health`, so a runner's "ready" means the actual first screen is
  serveable. Build-time warmup and restore-time readiness share ONE probe
  (`probe_ready` + nightly's `http_status_ready`), so a path cannot be accepted
  at build and then judged by a different rule at restore.

P1 — UFFD lazy restore canary behind an operator flag
* `ATO_RUNNER_UFFD_PREVIEW=1` opts a Connected Runner into UFFD demand-paging
  on `restore_snapshot` leases (the 512 MB eager rehydrate moves off the
  restore critical path; only faulted pages stream from CAS). Off by default.
* The flag is gated on host capability (`userfaultfd(2)` usable + local CAS
  openable) and DEGRADES to the eager File path, printing why. Without the
  gate, opting in on a host that cannot serve page faults does not keep the
  File path — it fails every restore on that runner, which is not a canary.
  The env gate (`ATO_FC_UFFD`) stays ungated and hard-fails: a smoke that
  silently fell back to File would assert nothing.
* Local `ato run` (`ATO_READY_STATE_UFFD_PREVIEW`) behaves as before.

P3 — Content-ready metric on the RESTORE_PROF line
* `RESTORE_PROF` carries `content_ready_ms`: how long after resume the user's
  first screen was serveable, so the PWA progress UI can interpolate p50 from
  real measurements rather than the proxy-only `proxy_ready_wait_ms`. It is the
  restore's own measurement, returned on `RestoreReceipt` — NOT a `snapshot::bench`
  span. `bench` is opt-in via `ATO_READY_STATE_BENCH=1`, which only the
  benchmark harness sets, so a bench-sourced metric would read `ungated` on
  every product runner and on every UFFD restore (the UFFD branch records no
  `restore.wait_health` span). `ungated` now means only what it says: a
  supervisor artifact, whose readiness is the agent probe, not a GET.

Tests
* Capsule SnapshotConfig parse / round-trip; probe-path validation.
* Warmup loop against a local guest stand-in: no-paths is a no-op; N stable
  rounds are required and a 302 counts as ready; a slow path keeps retrying to
  the boot timeout (regression: a 10-round cap would fail the ~3s case this
  feature targets); a never-ready path fails the build; an invalid path is
  rejected before any guest dial.
* WarmupRecipe carries `stable_*` only when warming (keeps the seal additive);
  `effective_stable_successes` clamps 0 → 1.
* Snapshot RestoreContract warmup round-trip + content_ready_path_or chain;
  CLI `restore_contract_from_manifest` copies warmup fields.
…t a new one

The first cut of the P1 gate hand-rolled a `userfaultfd(2)` syscall probe. That
duplicated `crate::uffd::evaluate` — the pure, unit-tested rule this backend
already reports as `BackendCapabilities::supports_uffd_mem_backend` — and was
weaker: it checked only the kernel, missing the arch / KVM / Firecracker-version
conditions `evaluate` covers.

Gate on `self.probe()` instead. The preview flag can no longer disagree with
what the runner advertises, and a refusal now carries `uffd_reason`, the exact
string the probe already produces. U0 built that probe noting "no restore path
uses it yet" — this is that path, so the note is updated rather than bypassed.
…oken invariant (ato#1077) (#1081)

Finalizes the external-browser Desktop login test coverage on top of the
#1078 core (already on nightly). Extracts two pure helpers from
login_with_store_device_flow_desktop so the security-relevant properties are
unit-testable from a single host build:

- browser_open_command(os, url): per-OS launcher argv (macOS `open`, Linux
  `xdg-open`, Windows `cmd /C start "" <url>`) with the login URL always a
  single discrete argument, never spliced into a shell string. try_open_browser
  now dispatches on std::env::consts::OS through it; runtime behavior unchanged.
- desktop_login_completed_event(handle, storage): the stdout success signal,
  guarded by a test asserting it can never carry a session/device token.

Adds 8 tests: macOS/Linux/Windows argv, a shell-metacharacter URL kept as one
argument on every OS, and completion-event shape / absent-handle / no-token.

Ships inert: EXTERNAL_BROWSER_LOGIN_HARDENING_LANDED stays false, so the desktop
login path still fails closed. The feature stays off until ato-api#275 is merged
AND deployed to production and the flag is flipped with recorded evidence in the
same commit (CI-enforced by hardening_flag_requires_recorded_evidence_when_enabled
and the desktop-release.yml publish gate) — a later, human-gated step. PR_BODY.md
documents the go-live sequence and the required human browser-open verification.

Co-authored-by: koh0920 <koh0920@users.noreply.github.com>
feat(snapshot): warm first-screen before seal + content-ready restore readiness (P0/P1/P2/P3)
…isplay flag

openbox has no --display option (it reads DISPLAY from the environment);
the flag makes it exit 1 immediately, wait -n reaps it, and the whole
fixture tears down before the health endpoint ever answers — the guest
never becomes healthy. Found by the first live dockerfile_import build of
the fixture; the static contract tests never execute the script. DISPLAY
is already exported earlier in the script.
…mpfs

The Ready-State guest mounts the rootfs read-only; mkdir -p /home/ato dies
(set -e) before Xvfb ever starts. Found via the live KVM build's console
diagnostics. /tmp is the guest's writable tmpfs — move HOME and
XDG_RUNTIME_DIR there.
The Ready-State seal is a memory resume: whatever is (not) listening at
seal time is (not) listening at restore. Gating /health on the mapped
window alone let the seal race ahead of x11vnc binding 5900, so the
restored guest refused the host pixel gateway's RFB connect. Require an
accepted TCP connect to 127.0.0.1:5900 before reporting ready.
…store

The Ready-State artifact is a memory snapshot; x11vnc's internal select()
timers can trip on the seal->restore wall-clock jump and make it exit once
right after resume. As a one-shot in the fatal wait set that tore the whole
fixture down, so the restored guest refused the host pixel gateway's RFB
connect. Run x11vnc under a restart supervisor and drop it from the fatal
wait set — it rebinds 5900 within ~1s, so the gateway readiness retry
connects on the restored session.
…veness

The resumed x11vnc can come back WEDGED after a memory-snapshot restore:
the process still exists — an exit-triggered supervisor never fires — but
its RFB listener is gone and the host gateway's connect is refused
(observed live: 8080 python survives the restore, 5900 refuses). Probe
the actual service (TCP connect to the RFB port) once a second; on
failure kill the leftover x11vnc and start a fresh one within the gateway
readiness probe's retry window.
x11vnc comes back WEDGED from a memory-snapshot restore (process alive,
RFB listener gone — observed live), so an exit-triggered supervisor never
fires and the restored guest refuses the gateway's connect. Use TigerVNC's
scraping server under a SERVICE-liveness watchdog (TCP-probe 5900 every
second, kill + restart on a dead listener). -SecurityTypes None keeps the
credential-free build contract; static tests updated to the TigerVNC
markers.
Koh0920 and others added 30 commits August 3, 2026 05:52
ato-web was retired and /s/<id> was never carried into ato-pwa, so the
web-upload/fetch share path (POST/GET /v1/shares, ato.run/s/<id> links)
has no working destination. Keep local-file sharing (share.spec.json /
share.lock.json) as the only mechanism.

- Remove the hidden top-level encap/decap aliases entirely.
- Drop EncapVisibility and the --internal/--private/--local flags;
  workspace share/setup are now the only names for the feature.
- Delete upload_share/fetch_share_url/rewrite_api_to_site_url/
  api_base_for_share_host and the ShareApiCreateRequest/
  ShareRevisionPayload structs.
- ato run <share-url> and ato workspace setup <share-url> no longer
  accept web share links; only local share files are valid inputs.
- Update docs and tests accordingly.
The web share flow is gone, so the desktop share-URL path has no working
destination. Remove is_share_url, resolve_and_start_from_share,
share_tmp_dir, and the pending-share-terminal machinery (plus the
corresponding navigate_to_url branches and tests). The ato:// deep-link
scheme and all other verbs (run, app/<ipk>, open?handle=, cli, launch,
runner/*, auth/callback/*) are untouched.
feat(authoring): execute and stream immutable build plans
Mirror the ato-api authoring asset contract (ato-api#459) on the Rust
side: an authoring metadata asset is image/png, image/jpeg, image/webp
or image/svg+xml, and SVG is only accepted when it survives the strict
passive profile.

inspect_svg_markup enforces:
- well-formed single-root XML via roxmltree (rejects DTD/entities/
  mismatched tags/multiple roots), with a fail-closed raw scan for
  CDATA, processing instructions and markup declarations
- root <svg> in the w3.org SVG namespace
- element allowlist (no script/foreignObject/style/a/iframe/animate*)
  judged by namespace + local name, so prefix aliasing cannot dodge it
- url(...) fragment-only across all attributes; href/src fragment-only;
  javascript:/data:/file:/protocol-relative/backslash rejected; on*
  handlers and xml:base banned
- dimensions from fixed width/height else viewBox, clamped to 16384
- element/depth/attribute count limits

Binary types are magic-byte checked; pixels are not decoded.
A URL asset descriptor may carry the materialized identity fields
(content_digest, artifact_ref, media_type) all together or not at all
(ato-api#459), artifact_ref must be derived from content_digest, and
media_type is restricted to the authoring allowlist
(image/png, image/jpeg, image/webp, image/svg+xml). origin_url, when
present, must be a credential-free HTTPS URL. Enforced in
CapsuleManifestV1::validate, which every from_toml parse already runs.
Before an Execution Identity is minted, validate authoring metadata
assets the manifest references by path: SVG bytes must survive the
passive inspection profile and binary formats must match their magic
signatures (ato-api#459 mirror). Missing files and unknown media types
are refused, so a manifest can no longer mint over hostile or corrupt
asset bytes.
A builder reporting materialized assets back to ato-api must first
validate them against the authoring contract (ato-api#459 mirror): the
bytes must match the declared media type (SVG through the passive
profile, binaries by magic bytes) and content_digest must be the sha256
of the bytes. The gate is exported for the setup-detected/ready payload
sites that currently report an empty materialized_assets list.
The builder now materializes the manifest's path-locator metadata assets
from the workspace at setup-detected time, validates each against the
authoring contract (SVG passive profile, binary magic bytes, sha256
content_digest), and reports them in the materialized_assets payload
ato-api's ingestBuilderPathAssets expects. A missing, unreadable,
unknown-type, or hostile asset is refused before setup is reported.
…d links

Fixes the clean-env regression where `ato run <share.spec.json>` invoked the
deleted `ato decap` subcommand via the capsule ShareExecutor.

- capsule share executor: materialize via `ato workspace setup --into <dir>
  --dev` (--dev is required so captured install_steps run; the old hidden
  decap alias passed dev:true). Pin the subprocess to the running binary via
  current_exe from the CLI caller.
- Add clean-ATO_HOME regression tests covering share.spec.json and
  share.lock.json inputs (install step runs, entry output), and a guard that
  decap is never invoked.
- Retired web share links (`ato.run/s/<id>`, incl. staging/www spellings) now
  fail with actionable migration guidance in `ato run`, `ato workspace setup`,
  and the desktop omnibar/deep-link paths, instead of a generic error or an
  external-browser navigation.
- Update active docs (root README, crates/cli README, both accepted
  ATO_CLI_SPEC copies, run.md, publishing guide, TESTING.md, demo runbook,
  ADR-005, security schema README, samples) and the manual suites
  (06-share-url rewritten for the local-file flow, 15-dogfooding, README) so
  removed encap/decap commands and share URLs are no longer presented as the
  current contract.
No user-facing behavior change — cleans up comments/strings that still
referenced the deleted encap/decap aliases or the web share flow in dead
desktop helpers and capsule doc comments.
Address review blockers/majors on PR #1248:

- Blocker: url(...) targets must now begin with '#' (a same-document
  fragment). url(evil), url(data:image), url(javascript:evil) and mixed
  'url(#safe) url(evil)' are rejected. ':' is dropped from the fragment
  NCName charset, matching the ato-api contract.
- Major 1: validate_asset_bytes now enforces the 5 MiB bound (1..=5 MiB)
  shared with ato-api, and snapshot-builder checks file size via
  metadata() before reading a path asset.
- Major 2: viewBox requires exactly four tokens and every token must
  parse; a 5-token '0 junk 0 100 100' is now refused instead of being
  silently filtered to four.
- Major 3: materialized asset failures are reported at the
  'metadata_assets' stage (setup_metadata_assets_failed) instead of
  'detect'.

Golden-vector tests mirror the ato-api cases.
… mismatch

Blocker 1 — the ShareExecutor cached workspaces keyed on the raw input string
(`./share.spec.json`), so two different projects run from different directories
(or an edited file at the same path) could alias each other and execute the
wrong code. The workspace dir is now derived from a SHA-256 content identity
over the canonical spec+lock bytes plus a materialization-contract version,
and a cached workspace is reused only when the stored identity matches AND
sources are all ok AND verification.result == "ok" AND no install step failed.

Blocker 2 — `ato run` executed a spec even when its digest did not match the
lock. It now fails closed before any entry execution with an explicit
"refusing to execute an unverified share" error.

Also in this commit:
- `ato run` pins the ato subprocess to current_exe fail-closed (no silent PATH
  fallback to a different binary version).
- Regression tests now compute the real spec digest (share_run_local_e2e) and
  cover: digest-mismatch rejection (spec+lock inputs), cross-directory
  ./share.spec.json non-aliasing, same-path spec update freshness, install
  step + entry execution on a clean ATO_HOME, and the retired-link error.
- cli-ci.yml now gates share_run_local_e2e, capsule share lib tests, the
  interactive capture e2e, and run_install_semantics, and triggers on
  crates/capsule/src/engine/share/** changes.
- Desktop ato://open?handle=<retired share link> shows guidance without
  leaving an empty tab.
- Manual suites: 06-share-url keeps its fixture for the whole suite (run/setup
  now PASS instead of SKIP); 04-group4 4c is now a local-file share roundtrip.
feat(ato): mirror authoring asset SVG contract from ato-api#459
Remove web-based encap/decap share flow (CLI + desktop)
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