From 9ae79a00bd258cb9426e660f27f977f177b1710e Mon Sep 17 00:00:00 2001 From: Aditya Garud <153842990+yashranaway@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:15:08 +0000 Subject: [PATCH] docs: add repository governance and cross-link the backlog to its issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings the repository up to the standard the product claims. Nothing here changes shipped behaviour. Governance and legal: - LICENSE — MIT, preserving the upstream Antiwork copyright from the chromeless project this was forked from. The repository was public with no license file at all, which left redistribution rights undefined and did not satisfy the upstream notice requirement. - SECURITY.md — private reporting route plus the boundary table a report is measured against, and the documented limitations that are not vulnerabilities. - CODE_OF_CONDUCT.md — Contributor Covenant 2.1. - CONTRIBUTING.md — setup, the bar for a PR, style, and the rule that contract changes need an architecture-decision entry first. Tracker plumbing: - Issue forms for bugs and feature proposals, a config that routes security reports to private advisories, and a pull-request template whose checklist is the host-enforced contract list. - CODEOWNERS covering the security-critical sources and the roadmap docs. - Dependabot for the web app, root tooling, Actions, and the Linux image. The Swift package has no third-party dependencies by design, so it is excluded. - CHANGELOG.md, including an Unreleased section — context pruning, capture formats, the docs set, and CI have all landed since v1.0.2. Backlog sync: - All 42 open backlog items now link to their GitHub issue inline, and §G links to its tracking issue. AGENTS.md and README explain how to pick work up from either surface and to keep both in sync. - Corrected §F1: the site is in fact deployed on Vercel. What is missing is repo-visible config, deploy documentation, and a real domain. --- .github/CODEOWNERS | 21 ++++ .github/ISSUE_TEMPLATE/bug_report.yml | 66 ++++++++++ .github/ISSUE_TEMPLATE/config.yml | 11 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 56 +++++++++ .github/PULL_REQUEST_TEMPLATE.md | 48 ++++++++ .github/dependabot.yml | 47 +++++++ AGENTS.md | 17 +++ CHANGELOG.md | 104 ++++++++++++++++ CODE_OF_CONDUCT.md | 135 +++++++++++++++++++++ CONTRIBUTING.md | 102 ++++++++++++++++ LICENSE | 22 ++++ README.md | 18 ++- SECURITY.md | 78 ++++++++++++ docs/roadmap/improvements-backlog.md | 101 ++++++++------- 14 files changed, 779 insertions(+), 47 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 SECURITY.md diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..3edc385 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,21 @@ +# Code owners are requested for review automatically. +# Order matters: the last matching pattern wins. + +* @SarthakWade + +# Security-critical surfaces — the host-enforced contracts live here. +/apps/headless/Sources/HeadlessProtocol/Protocol.swift @SarthakWade +/apps/headless/Sources/HeadlessProtocol/Transport.swift @SarthakWade +/apps/headless/Sources/HeadlessProtocol/Artifacts.swift @SarthakWade +/apps/headless/Sources/HeadlessProtocol/AgentRuntime.swift @SarthakWade +/apps/headless/LinuxHost/ @SarthakWade +/apps/headless/Host/ @SarthakWade + +# Product direction. Changes here are decisions, not refactors. +/docs/ROADMAP.md @SarthakWade +/docs/roadmap/ @SarthakWade +/SECURITY.md @SarthakWade +/AGENTS.md @SarthakWade + +# Release and CI plumbing. +/.github/workflows/ @SarthakWade diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..bcb47e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,66 @@ +name: Bug report +description: Something in Headless behaves incorrectly +labels: ["type:bug"] +body: + - type: markdown + attributes: + value: | + If this is a **security** issue — a way past navigation rules, the + artifact store, the socket boundary, or redaction — do not file it + here. Use [private vulnerability reporting](https://github.com/LockInTime/headless/security/advisories/new). + + - type: textarea + id: what-happened + attributes: + label: What happened + description: What did you expect, and what did you get instead? + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: Commands to reproduce + description: The exact `headless` commands, in order. Include the page or fixture if it matters. + render: shell + validations: + required: true + + - type: dropdown + id: engine + attributes: + label: Engine + options: + - macOS (WKWebView) + - Linux (Chromium) + - Linux via Docker image + - Both / not sure + validations: + required: true + + - type: input + id: version + attributes: + label: Version + description: Output of `headless capabilities` (protocol version) plus the release tag or commit you built from. + validations: + required: true + + - type: textarea + id: output + attributes: + label: Output and diagnostics + description: | + The failing JSON response, and anything relevant from `headless qa report`, + `console list`, or `network list`. Redact page content you cannot share. + render: shell + + - type: checkboxes + id: checks + attributes: + label: Checks + options: + - label: I checked the [improvements backlog](https://github.com/LockInTime/headless/blob/main/docs/roadmap/improvements-backlog.md) and open `backlog` issues for an existing entry + required: true + - label: This is not a documented limitation in the [phase contracts](https://github.com/LockInTime/headless/tree/main/apps/headless/docs) (P0/P1/P2) + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..4fbfa5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: Security vulnerability + url: https://github.com/LockInTime/headless/security/advisories/new + about: Report privately. Never open a public issue for a boundary bypass. + - name: Roadmap and backlog + url: https://github.com/LockInTime/headless/blob/main/docs/ROADMAP.md + about: Planned work, phases, and the definition of done. Most known gaps are already filed as `backlog` issues. + - name: Using Headless as an agent tool + url: https://github.com/LockInTime/headless/blob/main/.agents/skills/headless-computer-use/SKILL.md + about: The skill that teaches an agent how to drive Headless safely. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..012efd0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,56 @@ +name: Feature or command proposal +description: Propose a new capability, command, or parameter +labels: ["type:feature", "status:needs-design"] +body: + - type: markdown + attributes: + value: | + Headless has deliberate non-goals. These will be closed: + an arbitrary-JavaScript verb, a TCP listener or remote control without + authentication, page-download support, and desktop/OS automation. + See [what is excellent](https://github.com/LockInTime/headless/blob/main/docs/roadmap/what-is-excellent.md). + + - type: textarea + id: problem + attributes: + label: What can an agent not do today + description: Describe the task that fails or requires glue code, not the solution you have in mind. + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed surface + description: The command or parameter as an agent would type it, and the response shape you expect. + render: shell + validations: + required: true + + - type: dropdown + id: engines + attributes: + label: Which engines can support this + options: + - Both engines + - Chromium only (macOS returns UNSUPPORTED_CAPABILITY) + - WKWebView only (Linux returns UNSUPPORTED_CAPABILITY) + - Not sure + validations: + required: true + + - type: textarea + id: contracts + attributes: + label: Effect on the contracts + description: | + How does this interact with the host-enforced rules — bounded output, + untrusted content marking, fail-closed validation, no arbitrary + execution? If it needs an architecture-decision entry, say so. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e3676f6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,48 @@ + + +## What this changes + + + +Closes # + +## Why + + + +## How it was verified + + + +- [ ] `pnpm test` +- [ ] `pnpm test:runtime` +- [ ] `pnpm test:e2e:linux` — required if this touches a host, transport, agent runtime JS, or artifacts +- [ ] `pnpm test:e2e:mac` — or the `macos-e2e` label, required if this touches `main.swift`, `Host/`, or capture code +- [ ] New or updated tests cover the change + +## Contracts + + + +- [ ] No arbitrary-JS verb, no TCP listener, no debug port introduced +- [ ] Navigation stays HTTP/HTTPS only; downloads stay denied +- [ ] Artifacts stay bare-named, `O_EXCL`, `0600`, non-overwriting +- [ ] Validation still fails closed (unknown params rejected, capability errors explicit) +- [ ] Bounded output still reports truncation (`omitted`, `truncated`, `contextStats`) +- [ ] Page-derived text stays marked `untrustedContent`; no typed values recorded in flows +- [ ] Behaviour is identical on both engines, or the difference is an explicit `UNSUPPORTED_CAPABILITY` + +## Housekeeping + +- [ ] Backlog item checked off in `docs/roadmap/improvements-backlog.md` +- [ ] Architecture-decision entry added, if this changes a settled decision or the agent-facing contract +- [ ] A new protocol command was added to *both* hosts, the validator, the CLI, help text, and `capabilities` +- [ ] Docs updated — phase contract, README, and every duplicated copy on the site +- [ ] No committed media outside regenerated `docs/qa/evidence/` (checksums updated if so) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..43b8daf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,47 @@ +version: 2 + +updates: + # The Swift package has zero third-party dependencies by design, so there is + # nothing to update there — see docs/roadmap/what-is-excellent.md §11. + - package-ecosystem: npm + directory: /apps/web + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + labels: + - "type:chore" + - "area:web" + groups: + react: + patterns: ["react", "react-dom", "@types/react", "@types/react-dom"] + next: + patterns: ["next", "eslint-config-next"] + tailwind: + patterns: ["tailwindcss", "@tailwindcss/*"] + types: + patterns: ["@types/*"] + + - package-ecosystem: npm + directory: / + schedule: + interval: monthly + open-pull-requests-limit: 3 + labels: + - "type:chore" + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + labels: + - "type:ci" + + - package-ecosystem: docker + directory: /apps/headless + schedule: + interval: monthly + labels: + - "type:ci" + - "area:build-release" diff --git a/AGENTS.md b/AGENTS.md index ba92eda..6b34a93 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,8 @@ Read before making non-trivial changes: stays Swift; don't propose rewrites). - `docs/roadmap/improvements-backlog.md` — known defects with file/line refs; check items off when you fix them and add the named test. +- `CONTRIBUTING.md` — the same rules for humans, plus setup detail. +- `SECURITY.md` — the boundaries a bug report is measured against. - To *use* Headless as a browser tool (rather than develop it), follow the skill: `.agents/skills/headless-computer-use/SKILL.md`. @@ -62,6 +64,21 @@ Docker E2E. The macOS WKWebView E2E is heavier — it runs nightly, on `workflow_dispatch`, or when a PR carries the `macos-e2e` label. Run it that way before merging changes to `main.swift`, `Host/`, or capture code. +## Picking up work + +Every open backlog item is a GitHub issue labelled `backlog`, cross-linked +from the backlog file in both directions, carrying type/area/priority labels +and sitting in the milestone for its roadmap phase. Work from either surface — +but when you fix something, close the issue **and** check the item off in +`docs/roadmap/improvements-backlog.md` in the same PR, so the two never drift. + +Phase 1 (`Trust the build`) is the current milestone; `priority:critical` and +`priority:high` items there come first. `good first issue` marks the +self-contained ones. + +Anything that changes the agent-facing contract needs an entry in +`docs/roadmap/architecture-decisions.md` before the code lands. + ## Hard rules (host-enforced contracts — never weaken) 1. No arbitrary-JS execution verb; no TCP listener; no Chromium debug port. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b602525 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,104 @@ +# Changelog + +All notable changes to Headless are documented here. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +Releases are tagged `vX.Y.Z` and published by +[`.github/workflows/release.yml`](.github/workflows/release.yml). + +Two versions travel independently, on purpose: + +- **Product version** — the git tag, flowing into the macOS `Info.plist` via + `HEADLESS_VERSION` and into release assets. +- **Protocol version** — `headlessProtocolVersion` in `Protocol.swift`, + currently `0.4`. It changes only when the wire contract changes, and always + with an entry in + [`docs/roadmap/architecture-decisions.md`](docs/roadmap/architecture-decisions.md). + +## [Unreleased] + +Everything below has landed on `main` but is **not yet in a tagged release**. +Cutting that release is tracked in +[#45](https://github.com/LockInTime/headless/issues/45). + +### Added + +- Progressive context pruning: `inspect --context summary|outline|text|actions|full` + with `--task` ranking, `--within @rN` scoping, and `--limit` / `--budget` / + `--depth` bounds. Every focused response reports `contextStats` and `omitted`. + Measured 94.5 % estimated-token reduction on the 120-section fixture. +- Capture formats: JPG/JPEG and PDF screenshots, `--every-viewport` and + `--by-section` screenshot series, and MOV/WebM/GIF recording containers. +- Product documentation set — [`docs/ROADMAP.md`](docs/ROADMAP.md), + [what is excellent](docs/roadmap/what-is-excellent.md), + [architecture decisions](docs/roadmap/architecture-decisions.md), and the + [improvements backlog](docs/roadmap/improvements-backlog.md). +- Contributor rule files for every coding harness: [`AGENTS.md`](AGENTS.md) + and `CLAUDE.md`. +- Continuous integration on every pull request + ([`ci.yml`](.github/workflows/ci.yml)): static checks, agent-runtime suite, + protocol suite on Linux and macOS, web lint and build, and the Linux Docker + E2E. The macOS WKWebView E2E runs nightly or on the `macos-e2e` label. +- Repository governance: MIT `LICENSE` preserving the upstream chromeless + notice, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, issue forms, + a pull-request template, `CODEOWNERS`, and Dependabot. + +### Changed + +- QA evidence now includes a recorded progressive-pruning scenario, and the + committed bundle is checksum-verified in CI. +- Repository moved to the `LockInTime` organisation; site links updated. + +### Known gaps + +Tracked as [`backlog`](https://github.com/LockInTime/headless/labels/backlog) +issues, grouped by roadmap phase into milestones. Highlights: +a shutdown data race on the Linux host +([#12](https://github.com/LockInTime/headless/issues/12)), responses that can +exceed the 1 MiB frame ([#14](https://github.com/LockInTime/headless/issues/14)), +and unsigned macOS builds +([#39](https://github.com/LockInTime/headless/issues/39)). + +## [1.0.2] — 2026-07-19 + +### Fixed + +- macOS: recover when a restored page is unavailable at launch. + +## [1.0.1] — 2026-07-19 + +### Fixed + +- Release CI: harden the macOS E2E suite for GitHub runners. + +## [1.0.0] — 2026-07-19 + +First tagged release: the P0–P2 contract on macOS and Linux. + +### Added + +- Persistent host with a versioned JSON protocol over a private per-user Unix + socket, peer-UID checked, with no TCP listener and no arbitrary-JavaScript + verb. +- Two engines behind one CLI contract: macOS WKWebView windows, Linux + sandboxed Chromium driven over the inherited DevTools fd-3/4 pipe. +- Semantic interaction — `visit`, `inspect`, `click`, `fill`, `press`, + `scroll`, `wait`, `tour`, `back`, `reload` — targeted by role/name or by + element reference. +- Evidence capture: screenshots, built-in FFmpeg recording, visual comparison, + flows, and QA reports, written to a `0700` per-user artifact store with + `0600` non-overwriting files. +- Diagnostics with redaction: console, network, cookies, storage, computed + styles, performance, and animations. Sensitive values are double-gated. +- Linux-only network emulation and request mocking; macOS returns + `UNSUPPORTED_CAPABILITY` rather than partially emulating. +- `headless-mcp`, a stdio MCP server exposing the CLI as a single `argv` tool. +- Snap Chromium rejection before launch, after it proved unreliable over the + DevTools pipe. +- Tag-triggered release workflow publishing a macOS app zip and Linux + amd64/arm64 tarballs. + +[Unreleased]: https://github.com/LockInTime/headless/compare/v1.0.2...HEAD +[1.0.2]: https://github.com/LockInTime/headless/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/LockInTime/headless/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/LockInTime/headless/releases/tag/v1.0.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5a1aa81 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,135 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic +status, nationality, personal appearance, race, caste, color, religion, or +sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances + of any kind +* Trolling, insulting or derogatory comments, and personal or political + attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards +of acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for +moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement by opening a +[private security advisory](https://github.com/LockInTime/headless/security/advisories/new) +— which is visible only to maintainers — or by contacting an owner of the +LockInTime organization directly. + +All complaints will be reviewed and investigated promptly and fairly. All +community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7cf0be5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,102 @@ +# Contributing to Headless + +Thanks for working on this. Headless is a security-boundary product: agents +drive real browsers through it, and the host — not the prompt — is what stops +them doing something harmful. That shapes how we review changes, so please +read this before your first PR. + +Contributors are humans *and* coding agents. If you are an agent, your rules +live in [`AGENTS.md`](AGENTS.md); everything below applies to you too. + +## Before you start + +1. **Find or open an issue.** Every known defect and gap is already filed — + see the [backlog](docs/roadmap/improvements-backlog.md) and the matching + GitHub issues, labelled `backlog` and grouped into milestones by roadmap + phase. Good entry points carry `good first issue`. +2. **Read the contracts.** [`docs/roadmap/what-is-excellent.md`](docs/roadmap/what-is-excellent.md) + lists twelve properties that define the product. A PR that weakens one is + rejected on principle, not on style — even if the code is good. +3. **Check the decisions.** [`docs/roadmap/architecture-decisions.md`](docs/roadmap/architecture-decisions.md) + records what is already settled (the core stays Swift; no hosted service; + Windows is a stretch goal). If your change contradicts one, propose a new + decision entry in the same PR rather than quietly diverging. + +Anything that changes the agent-facing contract — a new verb, a new +parameter, a changed response shape, a relaxed safety rule — needs an +architecture-decision entry **before** the implementation lands. + +## Development setup + +```sh +pnpm install # workspace tooling (Node 20+, pnpm 9) +pnpm test # protocol/security suite — needs a Swift toolchain +pnpm test:runtime # jsdom context-pruning suite — Node only +pnpm test:e2e:linux # Docker + sandboxed Chromium E2E +pnpm test:e2e:mac # real WKWebView E2E (macOS GUI session) +``` + +Platform notes: + +- **macOS** builds need Xcode Command Line Tools; `pnpm build` produces + `Headless.app`. FFmpeg is required for recording and visual diffs. +- **Linux** builds need only Docker: `pnpm build:linux` compiles inside + `swift:6.1-bookworm` and emits a tarball. Chromium must be non-Snap — + the runtime rejects Snap launchers before starting, by design. +- `pnpm test:e2e:mac` opens real windows and mutates `com.headless.app` user + defaults. Don't run it in a background session or on a machine where that + matters. + +## The bar for a pull request + +- `pnpm test` and `pnpm test:runtime` pass locally. +- Run `pnpm test:e2e:linux` if you touched a host, the transport, the agent + runtime JS, or artifact code. Add the `macos-e2e` label to the PR if you + touched `main.swift`, `Host/`, or capture code — that triggers the heavier + WKWebView suite in CI. +- New behaviour ships with a test. New *safety* behaviour ships with a test + that fails without the fix. +- A protocol command still has to be added in both hosts, the validator, the + CLI, the help text, and `capabilities` until the HostCore refactor + ([#21](https://github.com/LockInTime/headless/issues/21)) lands. Keep the + copies in sync. +- If you fixed a backlog item, check it off in + `docs/roadmap/improvements-backlog.md` in the same PR and close the issue. + +CI runs on every PR: static checks, the agent-runtime suite, the protocol +suite on Linux and macOS, the web lint/build, and the Linux Docker E2E. All of +it uses the same scripts you ran locally, so a green laptop should mean a +green PR. + +## Style + +- **Swift:** Foundation and system frameworks only. No new SwiftPM + dependencies without an architecture-decision entry — the zero-dependency + supply chain is a feature. Explicit validation, small structs, and no + force-unwraps in host paths (the existing ones are tracked defects, not + precedent). +- **Shell:** POSIX `sh` unless you need bash; scripts are syntax-checked in + CI. +- **Web:** the site currently duplicates content by hand in three places. + If you change CLI behaviour, grep `apps/web` and `README.md` and update + every copy until [#48](https://github.com/LockInTime/headless/issues/48) + makes that unnecessary. +- **Commits:** conventional-ish prefixes — `feat:`, `fix:`, `docs:`, `ci:`, + `refactor:`, `test:`, `chore:`, with an optional scope such as + `fix(macos):`. + +## Media and evidence + +Never commit images or video except regenerated QA evidence under +`docs/qa/evidence/`, which is checksum-verified in CI. Local artifacts belong +in `build/qa-evidence/` (gitignored). If your change alters what a recording +or screenshot looks like, regenerate the affected evidence and update +`SHA256SUMS`. + +## Reporting security issues + +Do not open a public issue. See [`SECURITY.md`](SECURITY.md). + +## Code of conduct + +Participation is governed by [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68385f9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2026 LockInTime +Copyright (c) 2026 Antiwork, Inc. (original chromeless foundation) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index fc5be28..2947bb4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # headless +[![CI](https://github.com/LockInTime/headless/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/LockInTime/headless/actions/workflows/ci.yml) +[![Release](https://img.shields.io/github/v/release/LockInTime/headless?sort=semver)](https://github.com/LockInTime/headless/releases) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Protocol](https://img.shields.io/badge/protocol-0.4-informational)](apps/headless/Sources/HeadlessProtocol/Protocol.swift) +[![Platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20Linux-lightgrey)](#build-and-install) + Persistent browser control for agents, without Playwright scripts or screen coordinates. @@ -12,8 +18,16 @@ distros used in CI/Docker). Windows is not supported natively (planned as a stretch goal; Docker/WSL2 is the interim path). Product direction, architectural decisions, and the full improvements backlog -live in [docs/ROADMAP.md](docs/ROADMAP.md). Contributors (human or agent): -start with [AGENTS.md](AGENTS.md). +live in [docs/ROADMAP.md](docs/ROADMAP.md). Every known gap is also filed as a +[`backlog` issue](https://github.com/LockInTime/headless/labels/backlog), +grouped into [milestones](https://github.com/LockInTime/headless/milestones) +by roadmap phase. + +Contributing: [AGENTS.md](AGENTS.md) for agents and the short version for +humans, [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide, +[SECURITY.md](SECURITY.md) to report a boundary bypass privately, and +[CHANGELOG.md](CHANGELOG.md) for what shipped when. New here? Start with a +[good first issue](https://github.com/LockInTime/headless/labels/good%20first%20issue). P2 uses the same CLI everywhere Headless runs: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1214ff6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,78 @@ +# Security policy + +Headless exists to give an AI agent a browser it *cannot* misuse. The safety +rules are enforced by the host process, not by prompting, so a vulnerability +here is a vulnerability in the product's core promise. We take reports +seriously. + +## Reporting a vulnerability + +**Do not open a public issue.** + +Use GitHub's private vulnerability reporting: +[**Report a vulnerability**](https://github.com/LockInTime/headless/security/advisories/new). +That opens a private advisory visible only to maintainers. + +Please include: + +- affected version or commit, and platform (macOS engine or Linux Chromium engine) +- what boundary you crossed, and the commands or page that crossed it +- a minimal reproduction — a fixture page is ideal +- what an attacker gains + +We aim to acknowledge within 3 working days and to ship a fix or a documented +mitigation before any public disclosure. Tell us if you intend to publish, and +we will agree a timeline with you. + +## What counts as a vulnerability + +These are host-enforced contracts. Anything that defeats one is in scope: + +| Boundary | Expected behaviour | +| --- | --- | +| **No arbitrary code execution** | There is no JavaScript-evaluation verb and no shell verb. Reaching arbitrary in-page or host execution through the protocol is a vulnerability. | +| **Navigation** | HTTP/HTTPS only. `file:`, `javascript:`, `data:`, credential-bearing URLs, and external application schemes must be refused at every layer. | +| **Downloads** | Page-initiated downloads are denied. Executables, installers, scripts, libraries, and disk images are blocked by extension. | +| **Control plane** | A `0600` Unix socket inside a `0700` per-user directory, with a peer-UID check. There is no TCP listener and no Chromium debug port. Any remote reachability is a vulnerability. | +| **Artifacts** | Bare validated names, `O_EXCL` creation at `0600` inside a `0700` root, never overwritten. Path traversal or reading outside the store is a vulnerability. | +| **Secrets** | Cookie and storage *values* require both `--values` and `HEADLESS_ALLOW_SENSITIVE_DIAGNOSTICS=1`. Authorization, cookie, token, and secret headers, plus URL credentials, are always redacted. Flow recordings never contain typed values. | +| **Untrusted content** | Everything derived from a page is marked `untrustedContent` and is never executed as a command. A page that induces the host to act on its own text is a vulnerability. | +| **Sandbox** | The Linux host refuses to run as root and never passes `--no-sandbox`. Snap Chromium is rejected before launch. | + +Prompt injection that merely *persuades an agent* to do something within these +boundaries is not a host vulnerability — but if page content can escape the +`untrustedContent` marking or reach a privileged path, that is. + +## Known limitations (not vulnerabilities) + +These are documented design boundaries, not defects: + +- **Same-user access.** Any process running as your OS user can reach that + user's socket, browser profile, and artifacts. Run untrusted agents as + separate OS users. +- **Shared session state.** Sessions are windows (macOS) or tabs (Linux) over + one browser profile, so cookies and storage are shared between sessions. + Per-session isolation is tracked in the roadmap, not implied today. +- **macOS diagnostics are best-effort.** WebKit does not expose Chromium's + event stream; the macOS QA bridge runs in the page world and is therefore + observable by the page. Hardening it is tracked as + [#28](https://github.com/LockInTime/headless/issues/28). +- **Recording scope.** The recorder captures browser frames only — never OS + chrome, other applications, or audio. +- **Network mocking is Linux-only.** macOS returns `UNSUPPORTED_CAPABILITY` + rather than partially emulating traffic control. + +## Supported versions + +Headless is pre-1.x in practice: fixes land on `main` and ship in the next +tagged release. There is no long-term support branch yet. + +## Hardening guidance for operators + +- Run agents as a dedicated OS user, not your own account. +- Leave `HEADLESS_ALLOW_SENSITIVE_DIAGNOSTICS` unset unless you are actively + debugging, and never in a shared session. +- Keep the stdio MCP server local, or tunnel it over SSH. Do not bridge it to + a network listener. +- Treat every artifact, report, and console line as potentially sensitive + page content. diff --git a/docs/roadmap/improvements-backlog.md b/docs/roadmap/improvements-backlog.md index df6cd1e..9e3747a 100644 --- a/docs/roadmap/improvements-backlog.md +++ b/docs/roadmap/improvements-backlog.md @@ -10,11 +10,17 @@ Conventions: paths are relative to repo root; `HP/` = `apps/headless/Sources/HeadlessProtocol/`. Check items off here in the same PR that fixes them, and add the named missing test. +**Every open item below is also a GitHub issue**, linked inline and labelled +[`backlog`](https://github.com/LockInTime/headless/labels/backlog). Issues carry +type/area/priority labels and sit in the milestone for their roadmap phase, so +you can pick work up from either the tracker or this file. Keep both in sync: +when you fix an item, check it off here and close the issue in the same PR. + --- ## §A — Correctness & robustness (Phase 1: fix first) -**A1. Shutdown data race on the Linux host.** `LinuxBrowserHost` is +**A1. Shutdown data race on the Linux host.** ([#12](https://github.com/LockInTime/headless/issues/12)) `LinuxBrowserHost` is `@unchecked Sendable` with unlocked `sessions`/`trace`/`recordings`/ `activeFlows` (`apps/headless/LinuxHost/main.swift:9-12`). Normal commands are serialized by the transport's `requestQueue`, but `shutdown` deliberately @@ -27,14 +33,14 @@ bypass-the-queue property for shutdown. Test: none exists — add a concurrent-shutdown stress test beside the existing semaphore-based transport test (`ProtocolTests.swift:676-746`). -**A2. Force-unwraps in a long-lived host.** `visual compare` handlers do +**A2. Force-unwraps in a long-lived host.** ([#13](https://github.com/LockInTime/headless/issues/13)) `visual compare` handlers do `request.parameters["before"]!.stringValue!` on both hosts (`LinuxHost/main.swift:239-240`, `main.swift:1175-1176`). Safe only while `validate()` runs first; any future path that skips validation crashes the host and kills every session. Replace with guarded extraction returning `MISSING_PARAMETER`. -**A3. Oversized responses break the 1 MiB frame.** `qa report` can hold 500 +**A3. Oversized responses break the 1 MiB frame.** ([#14](https://github.com/LockInTime/headless/issues/14)) `qa report` can hold 500 events × ~4 KiB ≈ 2 MB; `artifact.list` is unbounded. `encodeLine` throws inside `handleClient` and the client receives a misleading `INVALID_REQUEST` (`HP/Transport.swift:219-227`). Fix: response-side bounding — @@ -42,11 +48,11 @@ pagination (`--limit/--cursor`) or truncation with `truncated: true` — per architecture decision §4. Test: generate >1 MiB of events, assert a bounded, well-formed response. -**A4. Accept-loop error spin.** All `accept()` errors are swallowed with +**A4. Accept-loop error spin.** ([#15](https://github.com/LockInTime/headless/issues/15)) All `accept()` errors are swallowed with `continue` (`HP/Transport.swift:180-184`); persistent EMFILE becomes a hot loop. Add backoff + a fatal threshold. -**A5. `@eN` refs silently invalidated by every snapshot.** The `current` ref +**A5. `@eN` refs silently invalidated by every snapshot.** ([#16](https://github.com/LockInTime/headless/issues/16)) The `current` ref map is reset on each `snapshot()` (`HP/AgentRuntime.swift:376`), so a `--context summary` (max 8 elements) invalidates all refs from a prior `full`; the agent later gets a bare `ELEMENT_NOT_FOUND`. Meanwhile @@ -56,7 +62,7 @@ teaching), then (a) make the error say *why* ("ref expired; re-inspect"), (b) reset regions consistently on navigation, (c) document in P1.md. Test: inspect-full → inspect-summary → click stale `@eN` asserts the new error. -**A6. `--json`/`--session` stripped from anywhere in argv.** Global-option +**A6. `--json`/`--session` stripped from anywhere in argv.** ([#17](https://github.com/LockInTime/headless/issues/17)) Global-option stripping (`HP/CLI.swift:46-49`) happens before subcommand parsing, so `headless fill @e1 pass --json to API` silently drops `--json` from typed text; `fill` also joins args with single spaces destroying whitespace @@ -64,17 +70,17 @@ text; `fill` also joins args with single spaces destroying whitespace before it, and pass the fill value as one argument. Test: fill value containing `--json`, tabs, double spaces. -**A7. Client never verifies response `id`.** Failure paths return +**A7. Client never verifies response `id`.** ([#18](https://github.com/LockInTime/headless/issues/18)) Failure paths return `id:"unknown"` (`HP/Transport.swift:201,222`); `LocalSocketClient.send` doesn't check correlation. Echo the request id everywhere and assert client-side. -**A8. CDP O(n²) buffering.** `receiveText` rescans the whole buffer and +**A8. CDP O(n²) buffering.** ([#19](https://github.com/LockInTime/headless/issues/19)) `receiveText` rescans the whole buffer and `removeFirst`s per 8 KiB read (`LinuxHost/CDP.swift:229-256`); a 30 MB base64 screenshot triggers thousands of full scans under a 128 MiB cap. Track a scan offset / use a ring buffer. -**A9. Misc hardening (smaller, same phase).** +**A9. Misc hardening (smaller, same phase).** ([#20](https://github.com/LockInTime/headless/issues/20)) - `ChromiumChildProcess.stop()` can busy-wait forever post-SIGKILL (`LinuxHost/BrowserProcess.swift:38`); bound it. - `SO_PEERCRED` hard-coded as `17` + hand-rolled `ucred` @@ -99,7 +105,7 @@ Track a scan offset / use a ring buffer. ## §B — Structure & contract (Phase 2) -**B1. HostCore extraction** — the headline refactor; full spec in +**B1. HostCore extraction** ([#21](https://github.com/LockInTime/headless/issues/21)) — the headline refactor; full spec in [architecture-decisions §3](architecture-decisions.md). Duplicated pairs to collapse (verified line ranges): dispatch switches (`main.swift:949-1291` / `LinuxHost/main.swift:65-343`), screenshot-series loops (`main.swift:911-945` @@ -111,11 +117,11 @@ collapse (verified line ranges): dispatch switches (`main.swift:949-1291` / gate, target validation (3 copies + JS), screenshot bounds, syscall shims (`Transport.swift:387-407` / `CDP.swift:278-288`). -**B2. Typed errors end-to-end.** Replace `message.contains("ELEMENT_NOT_FOUND")` +**B2. Typed errors end-to-end.** ([#22](https://github.com/LockInTime/headless/issues/22)) Replace `message.contains("ELEMENT_NOT_FOUND")` string matching (both hosts; `Host/AgentBridge.swift:416-418`) with an error enum carrying the protocol code. -**B3. Single-source constants + drift tests.** Blocked/caution extensions +**B3. Single-source constants + drift tests.** ([#23](https://github.com/LockInTime/headless/issues/23)) Blocked/caution extensions exist in Swift (`HP/Protocol.swift:576-591`) and JS (`HP/AgentRuntime.swift:17-21`) with no cross-check; artifact charset written 4×; local-address list 3×; CLI vs validator bounds disagree (scroll amount @@ -123,7 +129,7 @@ exist in Swift (`HP/Protocol.swift:576-591`) and JS unbounded in CLI, `CLI.swift:449` / `Protocol.swift:396-398`). One definition each + a test asserting the JS copy contains the Swift set. -**B4. Dead code removal.** `screenshotSeriesPoints(from:)` +**B4. Dead code removal.** ([#24](https://github.com/LockInTime/headless/issues/24)) `screenshotSeriesPoints(from:)` (`HP/ScreenshotSeries.swift:74-76`), `JSONValue.foundationObject` (`HP/Protocol.swift:670-679`), discarded `timeout` param (`LinuxHost/BrowserProcess.swift:777-778`) — either honor it (tour expects @@ -133,13 +139,13 @@ each + a test asserting the JS copy contains the Swift set. the flag from help, `jsonValue(from:)` duplicate (`AgentBridge.swift:428-441`). -**B5. `pruneToBudget` quality.** Hand-rolled 2-pass fixed point +**B5. `pruneToBudget` quality.** ([#25](https://github.com/LockInTime/headless/issues/25)) Hand-rolled 2-pass fixed point (`HP/AgentRuntime.swift:348-352`), O(n²) re-encoding per trim, pop-largest- *last*-element heuristic misses large mid-array items (`AgentRuntime.swift:367-369`), text-chop fallback untested. Rework with a size-estimating single pass; add unit tests in the jsdom suite. -**B6. Declared capability matrix.** Silent per-platform divergences to either +**B6. Declared capability matrix.** ([#26](https://github.com/LockInTime/headless/issues/26)) Silent per-platform divergences to either fix or promote to declared differences asserted in tests: PDF raster (macOS, `Host/AgentBridge.swift:236-250`) vs vector (`Page.printToPDF`, `BrowserProcess.swift:471-496`); element-screenshot coordinate space viewport @@ -156,13 +162,13 @@ through it; `press` length enforced in bridge only on macOS (`BrowserProcess.swift:521`). Generate `capabilities` from code (`CLI.swift:648-682` is a hand-written literal today) and assert it. -**B7. Runtime injection cost** — cache the isolated world / install runtime +**B7. Runtime injection cost** ([#27](https://github.com/LockInTime/headless/issues/27)) — cache the isolated world / install runtime per-navigation instead of per-call on both engines; extract the JS to a compiled resource. Spec in [architecture-decisions §10](architecture-decisions.md). (`BrowserProcess.swift:777-834`, `AgentBridge.swift:382-420`, `Tests/agent-runtime.test.mjs` regex extraction.) -**B8. QA diagnostics bridge isolation (macOS).** Page-world injection is +**B8. QA diagnostics bridge isolation (macOS).** ([#28](https://github.com/LockInTime/headless/issues/28)) Page-world injection is detectable/forgeable/spammable by a hostile page (`Host/QADiagnosticsBridge.swift:5-93`, `main.swift:233-236`) while P0 claims isolated-world helpers. Move what's possible; mark the rest untrusted. See @@ -170,35 +176,35 @@ architecture decision §7. ## §C — MCP & agent surface (Phases 1/4) -**C1. Timeout parity.** MCP uses flat 30 s except tour/series +**C1. Timeout parity.** ([#29](https://github.com/LockInTime/headless/issues/29)) MCP uses flat 30 s except tour/series (`apps/headless/MCP/main.swift:70-72`); CLI derives from `--timeout` (`HeadlessCLI/main.swift:104-114`). `wait --timeout 90000` works in CLI, dies via MCP. Derive identically. -**C2. Destructive verbs over MCP.** `stop` (shutdown) and `session close` are +**C2. Destructive verbs over MCP.** ([#30](https://github.com/LockInTime/headless/issues/30)) `stop` (shutdown) and `session close` are callable though the tool description says "safe"; decide policy (deny, or annotate) and test it. -**C3. Zero MCP tests** — the only coverage is inside `qa-videos.sh`. Add a +**C3. Zero MCP tests** ([#31](https://github.com/LockInTime/headless/issues/31)) — the only coverage is inside `qa-videos.sh`. Add a stdio harness test: initialize / tools/list / tools/call / malformed line / oversized line / local-command rejection (`MCP/main.swift:64-66`). -**C4. Machine-accurate `capabilities`** — generate from `CommandName.allCases` +**C4. Machine-accurate `capabilities`** ([#32](https://github.com/LockInTime/headless/issues/32)) — generate from `CommandName.allCases` + engine matrix (see B6) so agents can trust it. -**C5. Harness onboarding [exists: skill content].** Root `AGENTS.md` + +**C5. Harness onboarding [exists: skill content].** ([#33](https://github.com/LockInTime/headless/issues/33)) Root `AGENTS.md` + `CLAUDE.md` (added with this doc set); mirror the skill into `.claude/skills/` or symlink so Claude Code auto-discovers; ship `.mcp.json` example + per- client snippets (Claude Code, Cursor, Codex TOML — replacing the site's hardcoded `ssh hermes-vm` config, `apps/web/components/docs-markdown.ts:57-64`); flesh out `agents/openai.yaml` beyond its 4-line stub or remove it. -**C6. Input fidelity (Phase 4).** Real CDP input on Linux +**C6. Input fidelity (Phase 4).** ([#34](https://github.com/LockInTime/headless/issues/34)) Real CDP input on Linux (`Input.dispatchKeyEvent`/`dispatchMouseEvent`) behind the same verbs; today both engines dispatch synthetic DOM events and `press` special-cases only Enter/Space (`HP/AgentRuntime.swift:492-537`). Declared divergence per B6. -**C7. Considered-and-worth-designing (not committed):** hover/drag verbs; +**C7. Considered-and-worth-designing (not committed):** ([#35](https://github.com/LockInTime/headless/issues/35)) hover/drag verbs; `select` for dropdowns; scoped `evaluate` never (see what-is-excellent §3); per-session isolated profiles (`session create --isolated`, architecture §11); response-body inspection stays denied (P1.md:134) unless a gated design lands. @@ -220,7 +226,7 @@ Follow-ups: mark the jobs required in branch protection (repo setting, not code); consider arm64 Linux E2E on PRs (release covers it on tags); revisit whether the macOS E2E can become a per-PR gate once runtime is measured. -**D2. Known test gaps (from code audit).** No tests for: peer-UID rejection +**D2. Known test gaps (from code audit).** ([#36](https://github.com/LockInTime/headless/issues/36)) No tests for: peer-UID rejection (A9), MCP (C3), `Recording.swift` (ffmpeg args per format/quality, discovery rejection, drop-frame abort at `consecutiveFailures`, stop timeout), `VisualComparison`, `Flows.swift` — including the security property that @@ -239,10 +245,10 @@ which currently passes for the wrong reason. **Done** — the `web` job in `ci.yml` runs `pnpm --filter @headless/web lint` and `build`. -**D4. Cross-engine conformance runner** (architecture §14) replacing drifted +**D4. Cross-engine conformance runner** ([#37](https://github.com/LockInTime/headless/issues/37)) (architecture §14) replacing drifted hand-mirrored E2E assertions (e.g. `macos-e2e.sh:231` vs `linux-e2e.sh:157`). -**D5. Benchmark refresh discipline [exists: benchmark.sh].** Emit JSON +**D5. Benchmark refresh discipline [exists: benchmark.sh].** ([#38](https://github.com/LockInTime/headless/issues/38)) Emit JSON results artifact; re-run with the task-aware flow (BENCHMARK.md:29-31 says current numbers predate `--task`); repeat-count medians instead of single samples. @@ -251,50 +257,55 @@ samples. Owner-decided scope: package managers, no hosted service. -- **E1.** macOS Developer ID signing + notarization + stapling (today ad-hoc, +- **E1.** ([#39](https://github.com/LockInTime/headless/issues/39)) macOS Developer ID signing + notarization + stapling (today ad-hoc, `build.sh:96-114`); universal binary (today `uname -m` on an arm64 runner); Homebrew tap/cask. Resolve the `com.headless.app` provisioning question for the passkey entitlement (`.hermes` plan flagged it; `build.sh:97-109`). -- **E2.** Linux `curl | sh` installer wrapping the existing tarball + +- **E2.** ([#40](https://github.com/LockInTime/headless/issues/40)) Linux `curl | sh` installer wrapping the existing tarball + preflight (`install-linux.sh` already does runtime checks — reuse); align its ffmpeg policy with the runtime's allow-list (installer accepts any PATH ffmpeg, `install-linux.sh:118`, runtime doesn't, `HP/Recording.swift:225-227`); single-source the Snap-detection logic currently duplicated in shell (`install-linux.sh:40-67` vs `HP/ChromiumRuntime.swift:167-184`). -- **E3.** Publish the Docker `production` image to GHCR on tag (release notes +- **E3.** ([#41](https://github.com/LockInTime/headless/issues/41)) Publish the Docker `production` image to GHCR on tag (release notes currently say "not published"); this is also the interim Windows story. -- **E4.** `SHA256SUMS` (+ consider cosign) for all release assets — the QA +- **E4.** ([#42](https://github.com/LockInTime/headless/issues/42)) `SHA256SUMS` (+ consider cosign) for all release assets — the QA evidence bundle already ships sums; releases don't. -- **E5.** npm wrapper package (binary download shim) for `npx` reach. -- **E6.** Version unification + `headless --version` + CHANGELOG + release +- **E5.** ([#43](https://github.com/LockInTime/headless/issues/43)) npm wrapper package (binary download shim) for `npx` reach. +- **E6.** ([#44](https://github.com/LockInTime/headless/issues/44)) Version unification + `headless --version` + CHANGELOG + release automation (architecture §12). `package.json` says 0.0.0, tags say 1.0.x, default `HEADLESS_VERSION` is 1.0.0. -- **E7.** Cut a release: everything since v1.0.2 (capture formats, context +- **E7.** ([#45](https://github.com/LockInTime/headless/issues/45)) Cut a release: everything since v1.0.2 (capture formats, context pruning) is unreleased. -- **E8.** `NSAllowsArbitraryLoads` is blanket-true (`build.sh:86-90`); scope +- **E8.** ([#46](https://github.com/LockInTime/headless/issues/46)) `NSAllowsArbitraryLoads` is blanket-true (`build.sh:86-90`); scope it (localhost exception) if WKWebView allows. ## §F — Website & docs (Phase 5) -- **F1. Deploy pipeline** — none exists (no vercel/netlify/Dockerfile/export - config; only good headers/CSP in `next.config.ts` — keep those, consider a - nonce to drop `unsafe-inline`). -- **F2. Content provenance** — benchmark numbers hand-copied in +- **F1. Deploy pipeline is invisible to the repo** ([#47](https://github.com/LockInTime/headless/issues/47)) — the site *is* live at + `https://headless-web-pi.vercel.app` (set as the repo homepage) via Vercel's + GitHub integration, but nothing in the tree records that: no `vercel.json`, + no deploy docs, no preview-URL comment on PRs, and the temporary + `*-pi.vercel.app` hostname suggests no custom domain. Make the deployment + reproducible and reviewable — check in the project config, document the + hosting in `AGENTS.md`, and decide on a domain. Keep the existing headers/CSP + in `next.config.ts`; consider a nonce so `unsafe-inline` can be dropped. +- **F2. Content provenance** ([#48](https://github.com/LockInTime/headless/issues/48)) — benchmark numbers hand-copied in `app/page.tsx:26-38`, `components/efficiency-chart.tsx:26-31`, `components/benchmark-chart.tsx:21-26` (+ date in two places); docs prose triplicated across `app/docs/page.tsx`, `components/docs-markdown.ts`, and `README.md`, already diverging. Import from generated artifacts (D5, B6). -- **F3. Missing pages:** install (README's build/install section is absent +- **F3. Missing pages:** ([#49](https://github.com/LockInTime/headless/issues/49)) install (README's build/install section is absent from the site entirely), security model, MCP setup, full command reference (~30 commands; site lists 4 groups), changelog/version indicator, platform matrix, the README's comparison table (strongest positioning content, not on site). Plus `robots.txt`, `sitemap`, OG metadata, per-page `metadata`, 404 page. -- **F4. Stale-benchmark honesty:** site quotes pre-`--task` numbers while +- **F4. Stale-benchmark honesty:** ([#50](https://github.com/LockInTime/headless/issues/50)) site quotes pre-`--task` numbers while marketing `--task`, and drops BENCHMARK.md's re-run warning; headline says "Measured, not claimed." Fix by refresh (D5) or by carrying the caveat. -- **F5. Dead weight:** `components/ui/side-rays.tsx` + `ogl` dep (unused), +- **F5. Dead weight:** ([#51](https://github.com/LockInTime/headless/issues/51)) `components/ui/side-rays.tsx` + `ogl` dep (unused), `public/scan-dashboard.png` (unreferenced), leftover shadcn `.dark` block in `globals.css`, 8 unused button variants; two WebGL stacks (~700 KB) for decoration — `PixelBlast` still creates GL contexts under @@ -302,14 +313,14 @@ Owner-decided scope: package managers, no hosted service. reduced-motion rule; Recharts (~150 KB) for 4 static bars. Also `scan-frame.tsx` hardcodes pixel bounds tied to a committed github.com screenshot (drift + trademark question). -- **F6. Fix the Cursor config snippet** — hardcodes `ssh hermes-vm` +- **F6. Fix the Cursor config snippet** ([#52](https://github.com/LockInTime/headless/issues/52)) — hardcodes `ssh hermes-vm` (`components/docs-markdown.ts:57-64`), unusable by anyone else. -- **F7. Docs debt in-repo:** README states P1/P2 features but there is no +- **F7. Docs debt in-repo:** ([#53](https://github.com/LockInTime/headless/issues/53)) README states P1/P2 features but there is no single command reference doc; P1.md should document the `@eN` invalidation contract (A5) and the shared-profile session model (architecture §11); `.gitignore:18-23` still references `apps/chromeless/`. -## §G — Feature ideas (informed dump; schedule via roadmap phases) +## §G — Feature ideas (informed dump; schedule via roadmap phases) ([#54](https://github.com/LockInTime/headless/issues/54)) Gathered from the audit and product thinking; none are committed until they get an architecture-decision entry: