Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ jobs:
- name: Rust tests
run: cargo test --manifest-path rust/Cargo.toml

- name: Rust legacy parity tests
run: cargo test --manifest-path rust/Cargo.toml --test legacy_parity

- name: Rust security regression tests
run: cargo test --manifest-path rust/Cargo.toml --test security_regressions

Expand Down
1 change: 0 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ jobs:
cargo fmt --manifest-path rust/Cargo.toml -- --check
cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
cargo test --manifest-path rust/Cargo.toml
cargo test --manifest-path rust/Cargo.toml --test legacy_parity
cargo test --manifest-path rust/Cargo.toml --test security_regressions

- name: Build release binary
Expand Down
25 changes: 14 additions & 11 deletions docs/ARCHIVE_POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

## Canonical archive path

The full archived, non-maintained implementation is at:
The archived, non-maintained implementations are at:

- `legacy/deno/` (repo path)

The browser bridge is also archived in place:

- `browser-bridge/` (repo path)
- `legacy/browser-bridge/` (repo path)
- `legacy/native-host/` (repo path)
- `legacy/rust-src/` (repo path, old daemon/browser/native-host internals)
- `legacy/scripts/` (repo path, old browser/native-host helper scripts)

## Purpose of archive

Expand All @@ -19,11 +19,13 @@ The browser bridge is also archived in place:
## Maintenance rules

- `legacy/deno/` is read-only by default.
- `browser-bridge/` is read-only by default and carries an in-directory
- `legacy/browser-bridge/` is read-only by default and carries an in-directory
`ARCHIVED` tombstone.
- `legacy/native-host/`, `legacy/rust-src/`, and `legacy/scripts/` are read-only
by default.
- No feature work or new behavior should be introduced there.
- CI, lint, build, and packaging should target `rust/` only.
- Use `legacy/deno/` or `browser-bridge/` only for:
- Use `legacy/` only for:
- behavior audits,
- historical diffing,
- explicit, manual compatibility re-runs.
Expand All @@ -33,13 +35,14 @@ The browser bridge is also archived in place:
- Never treat archive behavior as a source of truth for releases.
- Do not apply dependency or security hardening changes only in the archive.
- All release gates, changelog updates, and bug fixes must land in Rust.
- `CODEOWNERS` explicitly owns `browser-bridge/` so archive changes require
code-owner review even though the bridge has no active product CI.
- `CODEOWNERS` explicitly owns archive paths so changes require code-owner
review even though they have no active product CI.

## First-run policy (for maintainers)

- If you are running this project as an active codebase, use `rust/` CLI and daemon paths.
- Ignore `legacy/deno/` unless you are explicitly performing a compatibility audit.
- If you are running this project as an active codebase, use `rust/` CLI and
`native-app/` broker paths.
- Ignore `legacy/` unless you are explicitly performing a compatibility audit.
- On first run of a new checkout, execute the normal Rust workflow first:
- `cargo fmt --manifest-path rust/Cargo.toml -- --check`
- `cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings`
Expand Down
11 changes: 3 additions & 8 deletions docs/INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,9 @@ Important v2 fields:
- `app.service.running`
- `app.service.transport`
- `app.service.live`
- `session`
- `daemon`
- `host`
- `bridge`

The legacy daemon/host/bridge sections remain in the payload for migration and
diagnostics, but the new primary health model is app-first.
Legacy daemon/host/bridge diagnostics were archived for v2.1.0; current
`status --json` reports the native app broker surface directly.

## Development and release checks

Expand All @@ -237,9 +233,8 @@ cargo build --manifest-path rust/Cargo.toml --release
./scripts/build-native-app.sh
```

Optional parity and release helpers:
Optional release helper:

```bash
cargo test --manifest-path rust/Cargo.toml --test legacy_parity
./scripts/release-bootstrap.sh
```
20 changes: 8 additions & 12 deletions docs/MIGRATION_AND_PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ Release reference version: `v2.0.0`

- Supported v2 implementation: [`rust/`](../rust/) + `native-app/`
- Archived implementation: [`legacy/deno/`](../legacy/deno/)
- Archived browser/helper implementation:
[`legacy/browser-bridge/`](../legacy/browser-bridge/),
[`legacy/native-host/`](../legacy/native-host/), and
[`legacy/rust-src/`](../legacy/rust-src/)
- Packaging, release, fixes, and hardening land in the Rust CLI and native app
- Legacy daemon/browser-helper code that remains in-tree is migration-only and
is no longer exposed through the active CLI contract
- Legacy daemon/browser-helper code is no longer in the active Rust module tree
and is preserved only for historical audit work

## Removed commands

Expand Down Expand Up @@ -76,22 +80,14 @@ cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
cargo test --manifest-path rust/Cargo.toml --all-targets
```

Legacy parity harness for retained status-shape compatibility and removed-command
regression coverage:

```bash
cargo test --manifest-path rust/Cargo.toml --test legacy_parity
```

## Release expectations

Before tagging a public release:

1. Keep versioned surfaces in sync
2. Run the Rust gates
3. Run the parity harness as a migration safeguard
4. Run the security regression matrix
5. Build the app bundle with `./scripts/build-native-app.sh`
3. Run the security regression matrix
4. Build the app bundle with `./scripts/build-native-app.sh`

Related docs:

Expand Down
2 changes: 1 addition & 1 deletion docs/NATIVE_MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ browser-helper vault reader flow.
| `apw otp list` | removed in v2.1.0 | no replacement in v2 |
| `apw otp get <url>` | removed in v2.1.0 | no replacement in v2 |
| `apw status` | supported | `apw status --json` now reports app/broker readiness |
| `apw host doctor` | legacy-only | `apw doctor` |
| `apw host doctor` | archived in v2.1.0 | `apw doctor` |
| `apw start` | removed in v2.1.0 | `apw app launch` |

## Bootstrap Flow
Expand Down
14 changes: 6 additions & 8 deletions docs/NATIVE_ONLY_REDESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,19 +271,18 @@ Deliverables:
### Rust changes

- add an app/XPC client module
- deprecate daemon/helper-specific runtime modes in CLI help
- introduce a new command family:
- `apw app install`
- `apw app launch`
- `apw doctor`
- `apw login <url>`

### Code to archive after cutover
### Archived after cutover

- `browser-bridge/`
- native-host private-helper bridge code
- helper manifest install scripts
- launchd/direct helper runtime modes
- `legacy/browser-bridge/`
- `legacy/native-host/`
- `legacy/scripts/`
- `legacy/rust-src/` for the former daemon/browser/native-host internals

## Security requirements

Expand Down Expand Up @@ -337,8 +336,7 @@ These are product decisions, not implementation bugs.

1. Create `docs/NATIVE_MIGRATION.md` with a command-by-command migration matrix.
2. Create `native-app/` with the minimal signed app skeleton.
3. Prototype `apw status` against a local app presence check instead of the
current native-host helper flow.
3. Keep `apw status` focused on the local app broker presence check.
4. Spike one supported credential request flow for a single associated domain.
5. Decide whether `v2.0.0` is a hard product break or ships with a temporary
compatibility shim for `pw get`.
Expand Down
11 changes: 6 additions & 5 deletions docs/SECURITY_POSTURE_AND_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ Release reference version: `v2.0.0`
### Runtime broker hardening

- the v2 app broker uses a same-user local UNIX socket under `~/.apw/native-app/`
- `status --json` exposes app/broker readiness while retaining legacy daemon diagnostics
- `status --json` exposes native app broker readiness; legacy
daemon/host/bridge diagnostics are archived under `legacy/`
- requests and responses use typed JSON envelopes with bounded payload sizes
- bootstrap credentials are read from a local runtime file for the supported
demo domain only; the app does not create that plaintext file on default
Expand Down Expand Up @@ -58,7 +59,6 @@ Run these before publishing:
cargo fmt --manifest-path rust/Cargo.toml -- --check
cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings
cargo test --manifest-path rust/Cargo.toml --all-targets
cargo test --manifest-path rust/Cargo.toml --test legacy_parity
cargo test --manifest-path rust/Cargo.toml --test native_app_e2e
cargo build --manifest-path rust/Cargo.toml --release
./scripts/build-native-app.sh
Expand All @@ -70,7 +70,7 @@ The Rust test suite covers:

- invalid PIN rejection before transport use
- invalid URL rejection before auth dependency
- stable JSON status shape
- stable native app JSON status shape
- launch failure precedence over session errors
- malformed or oversized payload rejection
- native app socket timeout handling
Expand Down Expand Up @@ -111,7 +111,8 @@ but the APW surfaces around it remain in scope.

## Archive policy

The Deno implementation is archived and not part of the supported security
surface. Use it only for compatibility audit work.
The historical Deno, browser bridge, native host, and legacy Rust helper
implementations are archived and not part of the supported security surface.
Use them only for compatibility audit work.

Archive rules: [ARCHIVE_POLICY.md](ARCHIVE_POLICY.md)
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
96 changes: 7 additions & 89 deletions rust/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::client::ApplePasswordManager;
use crate::error::APWError;
use crate::host::{native_host_doctor, native_host_install, native_host_uninstall};
use crate::logging::{self, LogLevel};
use crate::native_app::{
native_app_doctor, native_app_fill, native_app_install, native_app_launch, native_app_login,
native_app_status,
};
use crate::types::{Status, BUILD_DATE, BUILD_TARGET, GIT_SHA, RUST_VERSION, VERSION};
use clap::{Args, Parser, Subcommand};
Expand Down Expand Up @@ -97,7 +96,6 @@ pub enum Commands {
App(AppCommand),
Doctor(DoctorCommand),
Fill(FillCommand),
Host(HostCommand),
Login(LoginCommand),
Status(StatusCommand),
Version(VersionCommand),
Expand Down Expand Up @@ -139,25 +137,6 @@ pub struct FillCommand {
pub url: String,
}

#[derive(Args)]
pub struct HostCommand {
#[command(subcommand)]
pub command: HostSubcommand,
}

#[derive(Subcommand)]
pub enum HostSubcommand {
Install,
Doctor(HostDoctorArgs),
Uninstall,
}

#[derive(Args)]
pub struct HostDoctorArgs {
#[arg(long)]
pub json: bool,
}

#[derive(Args)]
pub struct StatusCommand {
#[arg(long)]
Expand All @@ -167,14 +146,13 @@ pub struct StatusCommand {
#[derive(Args, Default)]
pub struct VersionCommand {}

pub async fn run(mut manager: ApplePasswordManager, cli: Cli) -> Result<(), APWError> {
pub async fn run(cli: Cli) -> Result<(), APWError> {
match cli.command {
Commands::App(args) => run_app(args, cli.json),
Commands::Doctor(args) => run_doctor(args, cli.json),
Commands::Fill(args) => run_fill(args, cli.json),
Commands::Host(args) => run_host(args, cli.json),
Commands::Login(args) => run_login(args, cli.json),
Commands::Status(args) => run_status(&mut manager, args, cli.json),
Commands::Status(args) => run_status(args, cli.json),
Commands::Version(args) => run_version(args, cli.json),
}
}
Expand Down Expand Up @@ -243,13 +221,9 @@ fn run_login(args: LoginCommand, cli_json: bool) -> Result<(), APWError> {
Ok(())
}

fn run_status(
manager: &mut ApplePasswordManager,
args: StatusCommand,
cli_json: bool,
) -> Result<(), APWError> {
logging::debug("status", "collecting runtime status");
let payload = manager.status();
fn run_status(args: StatusCommand, cli_json: bool) -> Result<(), APWError> {
logging::debug("status", "collecting native app status");
let payload = native_app_status();
print_status(payload, args.json || cli_json);
Ok(())
}
Expand All @@ -268,25 +242,6 @@ fn run_version(_args: VersionCommand, cli_json: bool) -> Result<(), APWError> {
Ok(())
}

fn run_host(args: HostCommand, cli_json: bool) -> Result<(), APWError> {
match args.command {
HostSubcommand::Install => {
let payload = native_host_install()?;
print_output(&payload, Status::Success, cli_json);
}
HostSubcommand::Doctor(options) => {
let payload = native_host_doctor()?;
print_output(&payload, Status::Success, options.json || cli_json);
}
HostSubcommand::Uninstall => {
let payload = native_host_uninstall()?;
print_output(&payload, Status::Success, cli_json);
}
}

Ok(())
}

fn version_payload() -> Result<serde_json::Value, APWError> {
Ok(json!({
"version": VERSION,
Expand Down Expand Up @@ -526,6 +481,7 @@ mod tests {
fn legacy_daemon_commands_are_rejected() {
for args in [
&["apw", "auth"][..],
&["apw", "host", "install"][..],
&["apw", "pw", "list", "example.com"][..],
&["apw", "otp", "list", "example.com"][..],
&["apw", "start"][..],
Expand All @@ -537,44 +493,6 @@ mod tests {
}
}

#[test]
fn host_install_command_parses() {
let parsed = Cli::try_parse_from(["apw", "host", "install"]).unwrap();
match parsed.command {
Commands::Host(host) => match host.command {
HostSubcommand::Install => {}
_ => panic!("expected host install command"),
},
_ => panic!("expected host command"),
}
}

#[test]
fn host_doctor_command_accepts_json_flag() {
let parsed = Cli::try_parse_from(["apw", "host", "doctor", "--json"]).unwrap();
match parsed.command {
Commands::Host(host) => match host.command {
HostSubcommand::Doctor(options) => {
assert!(options.json);
}
_ => panic!("expected host doctor command"),
},
_ => panic!("expected host command"),
}
}

#[test]
fn host_uninstall_command_parses() {
let parsed = Cli::try_parse_from(["apw", "host", "uninstall"]).unwrap();
match parsed.command {
Commands::Host(host) => match host.command {
HostSubcommand::Uninstall => {}
_ => panic!("expected host uninstall command"),
},
_ => panic!("expected host command"),
}
}

#[test]
fn app_install_command_parses() {
let parsed = Cli::try_parse_from(["apw", "app", "install"]).unwrap();
Expand Down
Loading
Loading