From 50791c9bad5766aaee50e6aeca30437853782a22 Mon Sep 17 00:00:00 2001 From: dbarr5 Date: Mon, 20 Jul 2026 08:54:52 -0400 Subject: [PATCH 1/4] docs: spec for devops/production hardening loop branch --- ...7-20-devops-production-hardening-design.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 docs/specs/2026-07-20-devops-production-hardening-design.md diff --git a/docs/specs/2026-07-20-devops-production-hardening-design.md b/docs/specs/2026-07-20-devops-production-hardening-design.md new file mode 100644 index 0000000..cd05f2d --- /dev/null +++ b/docs/specs/2026-07-20-devops-production-hardening-design.md @@ -0,0 +1,39 @@ +# Spec: DevOps / Production Hardening Loop + +**Branch:** `loop/devops-production-hardening` +**Status:** Draft — assigned to employee, PR opened as starting point +**Date:** 2026-07-20 + +## Purpose + +Dedicated branch to run a stack of operator loops focused specifically on +production/infra readiness of aether-agent, separate from the auth/UX fix +branch (`fix/terminal-auth-401-ux`) and the TS7 upgrade branch +(`feat/typescript-7-terminal-upgrade`). + +## Loops to run (in order) + +Source: `C:\Users\lilbe\Documents\GitHub\agentic-loops\skills\` + +1. **LOOP-07-infra-devops.md** — infra/devops hardening pass (deploy config, + CI, env handling, observability). +2. **LOOP-17-sparring-partners.md** — adversarial pairing pass on findings + from step 1. +3. **LOOP-11-adversarial-review.md** — adversarial review of resulting + changes before they're trusted. +4. **LOOP-12-test-mutation-chaos.md** — mutation/chaos testing pass to + confirm the hardening actually holds under failure injection. +5. **LOOP-13-drift-debt.md** — drift/debt sweep to catch anything the above + loops leave inconsistent or half-migrated. + +## Scope + +- Production/devops surface only (build, deploy, CI, infra config, + resilience). Not the auth bug, not the TS7 migration — those are separate + branches by design so loops don't cross-contaminate unrelated diffs. +- Document findings from each loop stage in this spec as they land. + +## Notes + +Minimal spec intentionally — this is the starting point for the loop run, +not the full write-up. Expand with findings per loop stage. From 3aa36f02ff0488367eb673e15e9e624b946694df Mon Sep 17 00:00:00 2001 From: dbarrante Date: Mon, 20 Jul 2026 13:58:53 -0400 Subject: [PATCH 2/4] =?UTF-8?q?loop(LOOP-07):=20remediation=20=E2=80=94=20?= =?UTF-8?q?harden=20production=20delivery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confidence: 0.88 Adds least-privilege CI, supply-chain and release controls, safe installers, production verification, runbooks, regression tests, and LOOP-07 audit artifacts. External GitHub/npm admin controls remain operator-deferred. --- .env.example | 4 +- .github/dependabot.yml | 22 ++ .github/workflows/ci.yml | 58 +++- .github/workflows/codeql.yml | 42 +++ .github/workflows/release.yml | 103 +++++++ COMMANDS.md | 2 +- CONTRIBUTING.md | 3 + README.md | 6 +- SECURITY.md | 17 ++ .../AUDIT-ARTIFACT.md | 70 +++++ .../2026-07-20T13-53-25-0400/node-1.json | 12 + .../2026-07-20T13-53-25-0400/node-2.json | 13 + .../2026-07-20T13-53-25-0400/node-3.json | 12 + .../2026-07-20T13-53-25-0400/node-4.json | 12 + .../2026-07-20T13-53-25-0400/node-5.json | 13 + .../2026-07-20T13-53-25-0400/node-6.json | 12 + .../2026-07-20T13-53-25-0400/node-7.json | 12 + .../2026-07-20T13-53-25-0400/node-8.json | 17 ++ .../2026-07-20T13-53-25-0400/node-9.json | 12 + .../topology-drift.md | 12 + _loopstate/governance-ledger.md | 5 + docs/PRODUCTION_OPERATIONS.md | 114 ++++++++ install.ps1 | 20 +- install.sh | 40 +-- package.json | 1 + scripts/verify-production.ts | 274 ++++++++++++++++++ test/production_hardening.test.ts | 100 +++++++ tsconfig.json | 2 +- 28 files changed, 971 insertions(+), 39 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/release.yml create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/AUDIT-ARTIFACT.md create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-1.json create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-2.json create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-3.json create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-4.json create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-5.json create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-6.json create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-7.json create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-8.json create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-9.json create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/topology-drift.md create mode 100644 _loopstate/governance-ledger.md create mode 100644 docs/PRODUCTION_OPERATIONS.md create mode 100644 scripts/verify-production.ts create mode 100644 test/production_hardening.test.ts diff --git a/.env.example b/.env.example index ac9932c..7a1bfc7 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,8 @@ # Aether Agent configuration (all optional — sensible defaults ship in-app). # Copy to .env only if you need to override. NEVER commit a real token. -# Aether API base URL. Default: https://api.aethersystems.net -# AETHER_BASE_URL=https://api.aethersystems.net +# Aether API base URL. Default: https://api.aethersystems.net/cloud +# AETHER_BASE_URL=https://api.aethersystems.net/cloud # Page `aether login` opens. Default: https://aethersystems.net/platform # AETHER_LOGIN_URL=https://aethersystems.net/platform diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f8da9e5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + time: '06:00' + timezone: America/New_York + open-pull-requests-limit: 5 + groups: + development-toolchain: + dependency-type: development + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: '06:15' + timezone: America/New_York + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdf1966..f5f44c6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,24 +3,35 @@ name: CI on: push: branches: - - '**' + - main pull_request: branches: - main + workflow_dispatch: + +permissions: + contents: read -# Same-repo PR branches fire both on.push and on.pull_request for the same -# commit, running build-and-test twice. Cancel the superseded run instead of -# paying for (and status-checking) both. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build-and-test: - runs-on: ubuntu-latest + name: Test (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + runs-on: ${{ matrix.os }} + timeout-minutes: 20 steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false - name: Set up Node.js uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 @@ -31,7 +42,42 @@ jobs: cache: 'npm' - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - name: Build and test run: npm test + + supply-chain: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Set up Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + cache: 'npm' + + - name: Install dependencies without lifecycle scripts + run: npm ci --ignore-scripts + + - name: Audit dependencies + run: npm audit --audit-level=high + + - name: Verify production package and policy + run: npm run verify:production + + - name: Generate CycloneDX SBOM + run: npm sbom --sbom-format cyclonedx > aether-agents.cdx.json + + - name: Upload SBOM + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: aether-agents-sbom-${{ github.sha }} + path: aether-agents.cdx.json + if-no-files-found: error + retention-days: 90 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..aef8f0a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,42 @@ +name: CodeQL + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: '17 6 * * 1' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + packages: read + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 + with: + languages: javascript-typescript + build-mode: none + + - name: Analyze + uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..082332c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,103 @@ +name: Release npm package + +on: + release: + types: + - published + +permissions: + contents: read + +concurrency: + group: npm-production-${{ github.event.release.tag_name }} + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + timeout-minutes: 25 + environment: npm-production + permissions: + contents: read + id-token: write + attestations: write + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + steps: + - name: Checkout immutable release tag + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ env.RELEASE_TAG }} + fetch-depth: 0 + persist-credentials: false + + - name: Verify release commit belongs to main + shell: bash + run: | + git fetch --no-tags origin main + git merge-base --is-ancestor HEAD origin/main + + - name: Set up Node.js and npm registry + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + cache: 'npm' + registry-url: https://registry.npmjs.org + + - name: Install dependencies without lifecycle scripts + run: npm ci --ignore-scripts + + - name: Audit dependencies + run: npm audit --audit-level=high + + - name: Test release source + run: npm test + + - name: Verify tag, production policy, and package allowlist + run: npm run verify:production -- --tag "$RELEASE_TAG" + + - name: Generate CycloneDX SBOM + run: npm sbom --sbom-format cyclonedx > aether-agents.cdx.json + + - name: Build immutable npm tarball + id: pack + shell: bash + run: | + npm pack --json --ignore-scripts > pack-metadata.json + tarball="$(node -e "const fs=require('node:fs');const p=JSON.parse(fs.readFileSync('pack-metadata.json','utf8'));process.stdout.write(p[0].filename)")" + test -n "$tarball" + echo "tarball=$tarball" >> "$GITHUB_OUTPUT" + + - name: Smoke-test exact tarball + shell: bash + run: | + prefix="$RUNNER_TEMP/aether-install" + npm install --global --prefix "$prefix" "${{ steps.pack.outputs.tarball }}" --ignore-scripts + test "$("$prefix/bin/aether" --version)" = "${RELEASE_TAG#v}" + "$prefix/bin/aether" --help > /dev/null + + - name: Attest package provenance + uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 + with: + subject-path: ${{ steps.pack.outputs.tarball }} + + - name: Upload release evidence + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: npm-release-${{ env.RELEASE_TAG }} + path: | + ${{ steps.pack.outputs.tarball }} + aether-agents.cdx.json + pack-metadata.json + if-no-files-found: error + retention-days: 90 + + - name: Require npm publishing credential + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: test -n "$NODE_AUTH_TOKEN" + + - name: Publish verified tarball + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish "${{ steps.pack.outputs.tarball }}" --access public --provenance diff --git a/COMMANDS.md b/COMMANDS.md index 72ee6e2..181ef21 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -338,7 +338,7 @@ Requires an active orchestrator — switch with `/agent neo` or `/agent kronus` | Var | Default | Meaning | |---|---|---| -| `AETHER_BASE_URL` | `https://api.aethersystems.net` | Overrides the config `baseUrl`. | +| `AETHER_BASE_URL` | `https://api.aethersystems.net/cloud` | Overrides the config `baseUrl`. | | `AETHER_LOGIN_URL` | `https://aethersystems.net/platform` | Page `aether auth login` opens. | | `AETHER_TOKEN` | *(unset)* | Inject a session token (CI / headless / embedding). | | `AETHER_CONFIG_DIR` | `~/.config/aether` | Config + token + REPL-history directory. | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 65faedd..a4b2c27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,7 @@ cd aether-agent npm install npm run build npm test +npm run verify:production ``` Node ≥ 24. Zero runtime dependencies — the client uses only Node built-ins, and @@ -38,6 +39,8 @@ Most features touch a `core/` module + a `commands/` file. tested — match that bar). - **Types pass.** `npm run typecheck` uses the repository's pinned TypeScript 7 compiler and strict project settings. +- **Production policy passes.** `npm run verify:production` checks the npm + package allowlist, pinned CI actions, bounded jobs, and installer safety. - **Types are honest.** No `any` in application code; narrow `unknown`. - **Small files, one job each.** If a file grows past ~300 lines it's probably doing too much. diff --git a/README.md b/README.md index 61fd27f..9f5e2db 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ **Aether Agent is in beta.** Updates are shipping quickly. ```bash -npm i -g aether-agents # or run once: npx aether-agents +npm i -g aether-agents --ignore-scripts # or run once: npx --ignore-scripts aether-agents ``` [Install](#install-in-three-moves) · [Models & pricing](#models--pricing) · [Commands](#commands) · [Security](#security) · [Release notes](RELEASE_NOTES.md) @@ -30,7 +30,7 @@ It scans, plans, edits, and runs your tests — in your repo, on your terms. Ver ```bash -npm i -g aether-agents # 01 — drop it in (Node ≥ 24) +npm i -g aether-agents --ignore-scripts # 01 — drop it in (Node ≥ 24) aether auth login # 02 — sign in once aether agent # …terminal opens. Just start chatting. @@ -41,7 +41,7 @@ aether agent --local # …same terminal, nothing leaves the machi `aether agent` opens the REPL — chat with the model, slash-commands at hand, the agent edits files and runs your tests **in the same session**. Both brains run through the same host loop, render, tools, and commands — switching just swaps the transport. On the hosted path your code stays local and only the prompt + context you send leaves; on `--local`, nothing leaves at all. The local brain runs on **[Unlimited Context](https://github.com/DBarr3/Unlimited-Context-LLM)** — Aether's open-source (Apache-2.0) memory engine that gives any Ollama model a billion-token working memory. -> Prefer a script? `curl -fsSL https://aethersystems.net/install.sh | sh` (macOS / Linux / WSL) · `irm https://aethersystems.net/install.ps1 | iex` (Windows PowerShell). Both just verify Node and run the npm install — no native deps, no daemon. +> Prefer the installer UI? Download [`install.sh`](install.sh) or [`install.ps1`](install.ps1), inspect it, then run it locally. Set `AETHER_VERSION=0.1.0` (shell) or `-Version 0.1.0` (PowerShell) to pin an exact release. The canonical npm command above verifies registry integrity and disables lifecycle scripts; there are no native or runtime dependencies and no daemon. ## Models & pricing diff --git a/SECURITY.md b/SECURITY.md index 0fb7a16..1820eba 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -42,3 +42,20 @@ Out of scope here (report to Aether AI directly, same address): - Rotate your CLI token at [aethersystems.net/platform](https://aethersystems.net/platform) if you suspect it leaked, then `aether auth logout` and `aether auth login` again. - Prefer `aether login` (browser) over pasting long-lived tokens into scripts. + +## Release and supply-chain controls + +- GitHub Actions are pinned to immutable commit SHAs and run with explicit, + least-privilege permissions and bounded timeouts. +- CI installs dependencies with lifecycle scripts disabled, audits the complete + development graph, tests on Linux and Windows, verifies the npm package + allowlist, and emits a CycloneDX SBOM. +- npm publication accepts only a `v` tag, runs through the + protected `npm-production` environment, attests the tarball, and publishes + it with npm provenance. A release never rebuilds after the verified tarball + has been created. +- Installer scripts support exact-version pinning and never recommend piping a + network response directly into a shell. + +Operational setup, rollback, secret rotation, and evidence retention are +documented in [`docs/PRODUCTION_OPERATIONS.md`](docs/PRODUCTION_OPERATIONS.md). diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/AUDIT-ARTIFACT.md b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/AUDIT-ARTIFACT.md new file mode 100644 index 0000000..51acf70 --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/AUDIT-ARTIFACT.md @@ -0,0 +1,70 @@ +# LOOP-07 Infrastructure and DevOps Audit + +run_id: `2026-07-20T13-53-25-0400` +date: `2026-07-20` +target: `DBarr3/aether-agent` repository production path (`--no-ssh`) +branch: `loop/devops-production-hardening` +baseline SHA: `0f648f3741b7a08eae5ba2f505412b7d2075b51b` + +## Verdict: FAIL-WITH-ARTIFACT + +Repository code is production-ready for remote verification. One HIGH +operator-controlled gate remains unverified: the real `npm-production` +environment, npm token scope, required reviewers, and `main` protection. No live +infrastructure or repository settings were mutated. + +## Confidence block + +```yaml +confidence_block: + confidence: 0.88 + risk: high + evidence: [static-analysis, npm-test, npm-audit, package-dry-run, sbom, installer-syntax, manual-read] + unknown: [repository-admin settings, npm credential scope, organization backup mirror, remote workflow results] + missing_evidence: [successful PR checks, environment-policy export, branch-protection export, restore rehearsal] +``` + +## Findings + +| id | severity | domain | evidence | description | status | +|---|---|---|---|---|---| +| L07-001 | HIGH | Release | `.github/workflows/release.yml` | npm 0.1.0 had no automated verified, attested, SBOM-backed release path | RESOLVED-IN-BRANCH | +| L07-002 | HIGH | CI/IAM | `.github/workflows/ci.yml` | CI lacked explicit permissions/timeouts, ran lifecycle scripts, and tested only Linux | RESOLVED-IN-BRANCH | +| L07-003 | HIGH | Installer | `install.sh`, `install.ps1`, `README.md` | Pipe-to-shell guidance plus a POSIX pipeline hid npm failure and used bash-only `PIPESTATUS` | RESOLVED-IN-BRANCH | +| L07-004 | MEDIUM | Supply chain | `.github/dependabot.yml`, `.github/workflows/codeql.yml` | No SBOM retention, CodeQL, or dependency-update policy | RESOLVED-IN-BRANCH; remote enablement pending | +| L07-005 | MEDIUM | Operations | `docs/PRODUCTION_OPERATIONS.md` | Production topology, rollback, evidence, secret, and backup responsibilities were undocumented | RESOLVED-IN-BRANCH | +| L07-006 | MEDIUM | Config drift | `.env.example`, `COMMANDS.md`, `src/core/config.ts` | Operator docs omitted the required `/cloud` API base path | RESOLVED-IN-BRANCH | +| L07-007 | HIGH | External controls | GitHub/npm settings | Protected environment, token scope, branch protection, alert enablement, and backup mirror cannot be verified with current access | OPERATOR-DEFERRED | + +## Diffs applied + +- Hardened CI and added static policy regression tests. +- Added CodeQL, Dependabot, SBOM retention, a protected attested npm release, + immutable package verification, and tag-to-main ancestry validation. +- Reworked both installers to preserve errors, disable lifecycle scripts, and + permit exact release pinning without network-to-shell execution. +- Added the production operations runbook and corrected API configuration drift. + +## Tests generated + +- `test/production_hardening.test.ts`: manifest/tag, package allowlist, + workflow pin/timeout, installer policy, checked-in config, and API-doc drift. + +## Remediation plan requiring operator sign-off + +1. Merge only after Linux, Windows, supply-chain, and CodeQL checks succeed. +2. Configure `npm-production`, its required reviewer, and a package-scoped + `NPM_TOKEN`; protect `main` with the new required checks. +3. Enable Dependabot alerts/security updates and secret scanning. +4. Record an organization Git mirror and dated recovery rehearsal. + +## Governance row + +`| 2026-07-20 | LOOP-07 | 2026-07-20T13-53-25-0400 | FAIL-WITH-ARTIFACT | 0 | 2 | 96 | 0 | 0 | 0 | 0.88 | external admin controls unverified |` + +## Recommended next loops + +- LOOP-17 on the production policy and release workflow. +- LOOP-11 hostile security review of the final diff. +- LOOP-12 mutation pass on the production verification guard. +- LOOP-13 drift/debt sweep after all remediations settle. diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-1.json b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-1.json new file mode 100644 index 0000000..f5812c9 --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-1.json @@ -0,0 +1,12 @@ +{ + "node": 1, + "summary": "Repository production topology captured: GitHub source and runners, protected npm release environment, npm distribution, and end-user Node runtime. No repository-owned live nodes were supplied, so the run used the documented repo-static posture.", + "git_sha": "0f648f3741b7a08eae5ba2f505412b7d2075b51b", + "confidence_block": { + "confidence": 0.88, + "risk": "medium", + "evidence": ["repository inventory", "workflow inspection", "package metadata", "docs/PRODUCTION_OPERATIONS.md"], + "unknown": ["repository-admin settings could not be read", "no SSH or production-node target was provided"], + "missing_evidence": ["branch-protection export", "npm-production environment policy export"] + } +} diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-2.json b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-2.json new file mode 100644 index 0000000..783afba --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-2.json @@ -0,0 +1,13 @@ +{ + "node": 2, + "summary": "No Dockerfiles, compose manifests, container references, or running-container target exist in this repository. Container image controls are not applicable to the npm CLI delivery model.", + "git_sha": "0f648f3741b7a08eae5ba2f505412b7d2075b51b", + "images": [], + "confidence_block": { + "confidence": 0.97, + "risk": "low", + "evidence": ["git file inventory", "literal container-manifest search"], + "unknown": ["hosted API images are outside repository scope"], + "missing_evidence": [] + } +} diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-3.json b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-3.json new file mode 100644 index 0000000..46476da --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-3.json @@ -0,0 +1,12 @@ +{ + "node": 3, + "summary": "No compose or systemd service units exist. The real install surface was audited instead: the POSIX installer masked npm failures through a pipeline and used bash-only PIPESTATUS under /bin/sh; both installers enabled lifecycle scripts. The branch now runs npm directly, preserves its exit status, disables lifecycle scripts, and supports version pinning.", + "git_sha": "0f648f3741b7a08eae5ba2f505412b7d2075b51b", + "confidence_block": { + "confidence": 0.94, + "risk": "medium", + "evidence": ["manual read", "POSIX syntax check", "PowerShell parser", "production-policy regression tests"], + "unknown": ["website-hosted copies of installer scripts were not compared"], + "missing_evidence": ["post-merge website publication check"] + } +} diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-4.json b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-4.json new file mode 100644 index 0000000..3cdca85 --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-4.json @@ -0,0 +1,12 @@ +{ + "node": 4, + "summary": "No Terraform, Pulumi, CloudFormation, Kubernetes, or other infrastructure-as-code state exists. This is justified for the repository-owned npm CLI boundary; GitHub environment and branch controls remain external click-configuration and are documented as required admin actions.", + "git_sha": "0f648f3741b7a08eae5ba2f505412b7d2075b51b", + "confidence_block": { + "confidence": 0.91, + "risk": "medium", + "evidence": ["git file inventory", "production topology runbook"], + "unknown": ["current GitHub environment and branch-protection state"], + "missing_evidence": ["repository-admin settings export"] + } +} diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-5.json b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-5.json new file mode 100644 index 0000000..5e03760 --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-5.json @@ -0,0 +1,13 @@ +{ + "node": 5, + "summary": "Current tree and full Git history were scanned for common credential shapes without printing values. The only matches were two path fragments containing 'sk-' inside architecture document filenames, confirmed as false positives after redaction. Workflow secret references are symbolic only.", + "git_sha": "0f648f3741b7a08eae5ba2f505412b7d2075b51b", + "findings": [], + "confidence_block": { + "confidence": 0.86, + "risk": "medium", + "evidence": ["history-wide credential-shape scan", "redacted false-positive review", ".gitignore inspection"], + "unknown": ["GitHub secret-scanning alerts API was unavailable to the current credential"], + "missing_evidence": ["secret-scanning settings and alert export"] + } +} diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-6.json b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-6.json new file mode 100644 index 0000000..0f98dd3 --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-6.json @@ -0,0 +1,12 @@ +{ + "node": 6, + "summary": "Workflow IAM is least-privilege in source: read-only contents by default, CodeQL alone writes security events, release alone writes OIDC/attestations, and checkout credentials are not persisted. NPM_TOKEN is isolated to the npm-production environment in the workflow, but its real package scope and rotation state cannot be verified from repository contents.", + "git_sha": "0f648f3741b7a08eae5ba2f505412b7d2075b51b", + "confidence_block": { + "confidence": 0.87, + "risk": "high", + "evidence": ["workflow static policy", "immutable action pins", "release runbook"], + "unknown": ["npm token scope", "required reviewer configuration", "main branch protection"], + "missing_evidence": ["npm-production environment export", "branch-protection export"] + } +} diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-7.json b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-7.json new file mode 100644 index 0000000..c209c7f --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-7.json @@ -0,0 +1,12 @@ +{ + "node": 7, + "summary": "The CLI opens no inbound listener. Credentialed API transport is restricted to HTTPS or loopback HTTP by isCredentialSafeUrl and covered by transport-security tests. The public Aether API is an outbound dependency and its hosted ingress is outside this repository.", + "git_sha": "0f648f3741b7a08eae5ba2f505412b7d2075b51b", + "confidence_block": { + "confidence": 0.91, + "risk": "medium", + "evidence": ["network call-path review", "transport security tests", "repository listener search"], + "unknown": ["live hosted API firewall and ingress posture"], + "missing_evidence": ["hosted API infrastructure audit from its owning repository"] + } +} diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-8.json b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-8.json new file mode 100644 index 0000000..66f9e19 --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-8.json @@ -0,0 +1,17 @@ +{ + "node": 8, + "summary": "The package has zero runtime dependencies and npm audit reports zero vulnerabilities. All GitHub Actions are pinned to full commit SHAs. CI now blocks dependency lifecycle scripts, runs package-policy verification, emits a CycloneDX SBOM, and uploads it for 90 days. CodeQL and weekly Dependabot coverage were added; releases attest and publish the exact verified tarball with provenance.", + "git_sha": "0f648f3741b7a08eae5ba2f505412b7d2075b51b", + "sbom": { + "format": "CycloneDX", + "components_observed": 4, + "generation": "npm sbom --sbom-format cyclonedx" + }, + "confidence_block": { + "confidence": 0.94, + "risk": "low", + "evidence": ["npm audit", "npm SBOM", "package dry-run", "workflow policy tests"], + "unknown": ["remote CodeQL and Dependabot execution pending push"], + "missing_evidence": ["successful remote workflow runs"] + } +} diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-9.json b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-9.json new file mode 100644 index 0000000..948b542 --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/node-9.json @@ -0,0 +1,12 @@ +{ + "node": 9, + "summary": "No repository-owned database, object store, or persistent production volume exists. The runbook assigns source recovery to a Git mirror and release evidence to GitHub/npm plus 90-day workflow retention. The organization-controlled mirror and restore rehearsal remain operator-owned and unverified.", + "git_sha": "0f648f3741b7a08eae5ba2f505412b7d2075b51b", + "confidence_block": { + "confidence": 0.85, + "risk": "medium", + "evidence": ["state inventory", "package topology", "backup and recovery runbook"], + "unknown": ["external Git mirror freshness", "release-evidence export cadence"], + "missing_evidence": ["dated restore rehearsal"] + } +} diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/topology-drift.md b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/topology-drift.md new file mode 100644 index 0000000..dcc0b6e --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/topology-drift.md @@ -0,0 +1,12 @@ +# Topology drift + +| service | documented node | actual node | drift | severity | +|---|---|---|---|---| +| Source control | GitHub `DBarr3/aether-agent` | GitHub `DBarr3/aether-agent` | None | LOW | +| Verification | GitHub-hosted Ubuntu and Windows runners | Workflow definitions added; remote run pending push | Remote execution not yet observed | MEDIUM | +| Release | `npm-production` environment | Workflow definition added; admin policy unreadable | Environment controls unverified | HIGH | +| Distribution | npm `aether-agents` | npm `aether-agents@0.1.0` | Automated release/provenance absent before this branch | HIGH, remediated in code | +| Runtime | End-user Node 24+ process | Package requires Node 24+ | None | LOW | + +There are no repository-owned containers, daemons, public listeners, databases, +or SSH nodes. Hosted Aether API infrastructure is explicitly outside this repo. diff --git a/_loopstate/governance-ledger.md b/_loopstate/governance-ledger.md new file mode 100644 index 0000000..5236524 --- /dev/null +++ b/_loopstate/governance-ledger.md @@ -0,0 +1,5 @@ +# Loop governance ledger + +| date | loop | run_id | verdict | retries | debate_rounds | tool_success_pct | human_overrides | hallucinations_caught | false_positive_rate | rollbacks | confidence | note | +|---|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---| +| 2026-07-20 | LOOP-07 | 2026-07-20T13-53-25-0400 | FAIL-WITH-ARTIFACT | 0 | 2 | 96 | 0 | 0 | 0 | 0 | 0.88 | Code ready; repository-admin and npm controls unverified | diff --git a/docs/PRODUCTION_OPERATIONS.md b/docs/PRODUCTION_OPERATIONS.md new file mode 100644 index 0000000..637fea7 --- /dev/null +++ b/docs/PRODUCTION_OPERATIONS.md @@ -0,0 +1,114 @@ +# Production Operations + +This runbook defines the production boundary for the Aether Agent repository. +It covers the terminal client and its npm delivery path. The hosted Aether API, +account platform, billing, and model fleet are separate systems with separate +operations and incident response. + +## Deployment topology + +| Stage | Asset | Trust boundary | State owned here | +|---|---|---|---| +| Source | `DBarr3/aether-agent` on GitHub | Maintainer and pull-request controls | Git history, workflow definitions, release tags | +| Verification | GitHub-hosted Linux and Windows runners | Ephemeral CI identities with explicit permissions | Test logs, CodeQL results, 90-day SBOM artifacts | +| Release | Protected `npm-production` GitHub environment | Required reviewer plus release-scoped npm credential | Attested npm tarball, CycloneDX SBOM, provenance | +| Distribution | npm package `aether-agents` | Public npm registry | Immutable published versions and dist-tags | +| Runtime | End-user Node 24+ process | User workstation and workspace boundary | User-owned config, token, sessions, logs, and worktrees | + +There are no Docker images, long-running repository-owned services, systemd +units, Kubernetes resources, Terraform state, public listeners, or databases in +this repository. Do not add placeholder infrastructure for those surfaces. If a +hosted component moves into this repository, this topology and its backup, +network, resource-limit, and restore requirements must be expanded first. + +## Required GitHub configuration + +The workflow files enforce what can be expressed in source. A repository admin +must also configure these controls in GitHub: + +1. Create the `npm-production` environment. +2. Require at least one maintainer approval and restrict deployments to release + tags created from `main`. +3. Add `NPM_TOKEN` as an environment secret. Scope it only to publish + `aether-agents`; never use a personal all-packages token. +4. Protect `main`: require pull requests, the Linux and Windows CI jobs, the + supply-chain job, and CodeQL; dismiss stale approvals after new commits. +5. Enable Dependabot alerts/security updates and secret scanning. The checked-in + Dependabot configuration supplies weekly npm and GitHub Actions update PRs. + +If any control cannot be verified, treat release readiness as unknown and do not +publish. + +## Release procedure + +1. Update `package.json` and `src/version.ts` to the same semantic version. +2. Run `npm ci --ignore-scripts`, `npm test`, `npm audit --audit-level=high`, and + `npm run verify:production` from a clean checkout. +3. Merge only after all required CI and CodeQL checks pass. +4. Create and publish a GitHub Release whose existing tag is exactly + `v`. Publishing the release is the operator approval that + starts `.github/workflows/release.yml`. +5. Approve the `npm-production` environment deployment after reviewing the tag, + changelog, and workflow inputs. +6. Confirm the workflow produced one tarball, an SBOM, GitHub build provenance, + npm provenance, and a successful npm publish for the expected version. +7. Install the exact version in a clean temporary prefix and run + `aether --version`, `aether --help`, and `aether doctor` before moving the npm + `latest` tag when a staged dist-tag is used. + +The release job packs once, attests that tarball, uploads the same tarball as +evidence, and publishes that file. It does not publish a second rebuild. + +## Secrets and least privilege + +- `NPM_TOKEN` exists only in `npm-production`; it is not a repository secret, + command-line argument, artifact, log, or local env file. +- CI has read-only repository contents permission. CodeQL alone receives + `security-events: write`. The release job alone receives OIDC and attestation + permissions. +- Checkout credentials are not persisted. Dependency lifecycle scripts are + disabled in CI, release, README instructions, and both installers. +- Rotate the npm credential after suspected exposure, maintainer departure, or a + publishing incident. Re-run no release until the old credential is revoked. + +## Observability and release evidence + +- CI and CodeQL status are the release gate; a green local run is supporting + evidence, not a substitute. +- Each CI commit retains a CycloneDX SBOM for 90 days. Each release retains the + tarball, pack metadata, and SBOM for 90 days in addition to npm provenance. +- npm version metadata and the GitHub Release tag provide the distribution audit + trail. `npm run verify:production -- --tag vX.Y.Z` ties them together. +- At runtime, `aether doctor` provides bounded dependency and configuration + diagnostics. CLI exit codes and user-owned logs are the client-side signal; + hosted API service telemetry is outside this repository. + +## Rollback and incident response + +npm releases are immutable. Never overwrite or silently rebuild a published +version. + +1. Stop further releases and revoke the publishing credential if compromise is + possible. +2. Deprecate the affected npm version with a clear install warning and move any + affected dist-tag to the last known-good version. +3. Publish a fixed version through the normal protected workflow; do not reuse + the compromised version number. +4. Compare the retained tarball, SBOM, GitHub attestation, npm provenance, and + source tag. Preserve logs before changing credentials or tags. +5. Follow `SECURITY.md` for coordinated disclosure and document the incident in + release notes. + +## Backup and recovery + +The client owns no server-side database or persistent production volume. User +config, credentials, sessions, and worktrees belong to the user's workstation +and must never be uploaded as a repository backup. + +Repository source and signed release tags are retained in GitHub; published +tarballs and provenance are retained by npm; the workflow also retains release +evidence for 90 days. Maintainers should keep an organization-controlled mirror +of the Git repository and export release evidence on the normal backup cadence. +Recovery is proven by checking out a signed release tag, running the release +verification commands, and comparing the resulting metadata with the retained +artifact. A registry copy is distribution evidence, not the sole source backup. diff --git a/install.ps1 b/install.ps1 index 0c25f48..6716896 100644 --- a/install.ps1 +++ b/install.ps1 @@ -1,16 +1,21 @@ # Aether Agent installer — Windows (PowerShell). # -# irm https://aethersystems.net/install.ps1 | iex +# Download, inspect, then run this file with: .\install.ps1 # # Installs the `aether` CLI globally via npm, then shows next steps. # Requires Node.js >= 24 and npm on the PATH. param( - [switch]$SkipNodeCheck = $false + [switch]$SkipNodeCheck = $false, + [string]$Version = $(if ($env:AETHER_VERSION) { $env:AETHER_VERSION } else { "latest" }) ) $ErrorActionPreference = "Stop" +if ($Version -notmatch '^[0-9A-Za-z.+-]+$') { + throw "Invalid Version: use latest, a dist-tag, or a semver." +} + # ── Color helpers ── $cyan = 11 # BrightCyan (ANSI 44 ≈ #1aa6b7) $ice = 12 # BrightBlue (ANSI 117 ≈ #87d7ff) @@ -67,7 +72,7 @@ Write-Host "│" -NoNewline -ForegroundColor $cyan Write-Host " ☁ Aether Agent — Terminal Coding Agent Installer " -NoNewline Write-Host "│" -ForegroundColor $cyan Write-Host "│" -NoNewline -ForegroundColor $cyan -Write-Host " v0.1.0 · aethersystems.net " -NoNewline -ForegroundColor $dim +Write-Host " npm release $Version · aethersystems.net " -NoNewline -ForegroundColor $dim Write-Host "│" -ForegroundColor $cyan Write-BoxEmpty Write-BoxBottom @@ -124,10 +129,9 @@ if ($alreadyInstalled) { Write-Step "Installing aether-agent" } -npm install -g aether-agents 2>&1 | ForEach-Object { - Write-Host "." -NoNewline -} -Write-Host "" +# Run npm directly so its native exit status cannot be hidden by a pipeline. +# --ignore-scripts is safe because the package has no install-time lifecycle. +& npm install -g "aether-agents@$Version" --ignore-scripts if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne $null) { Write-ErrorMsg "Install failed. Check your network and npm permissions." @@ -139,7 +143,7 @@ if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne $null) { } $aetherPath = Get-Command aether -ErrorAction SilentlyContinue -$aetherVersion = "0.1.0" +$aetherVersion = "unknown" if ($aetherPath) { try { $aetherVersion = (aether --version 2>$null) } catch {} } diff --git a/install.sh b/install.sh index cb7ec9c..45c6b82 100644 --- a/install.sh +++ b/install.sh @@ -1,12 +1,22 @@ #!/bin/sh # Aether Agent installer — macOS / Linux / WSL. # -# curl -fsSL https://aethersystems.net/install.sh | sh +# Download, inspect, then run this file with: sh install.sh # # Installs the `aether` CLI globally via npm, then shows next steps. # Respects NO_COLOR (https://no-color.org) and non-TTY output. -set -e +set -eu + +# Pin a specific release with AETHER_VERSION=0.1.0. The default follows npm's +# latest dist-tag, while the quoted package spec prevents shell interpretation. +AETHER_VERSION="${AETHER_VERSION:-latest}" +case "$AETHER_VERSION" in + ""|*[!0-9A-Za-z.+-]*) + printf '%s\n' "Invalid AETHER_VERSION: use latest, a dist-tag, or a semver." + exit 1 + ;; +esac # ── ANSI color helpers ── if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then @@ -47,7 +57,7 @@ echo "" echo "${cyan}┌──────────────────────────────────────────────────────────────┐${n}" echo "${cyan}│${n} ${cyan}│${n}" echo "${cyan}│${n} ${ice}☁${n} ${bold}Aether Agent${n} — Terminal Coding Agent Installer ${cyan}│${n}" -echo "${cyan}│${n} ${dim}v0.1.0 · aethersystems.net${n} ${cyan}│${n}" +echo "${cyan}│${n} ${dim}npm release ${AETHER_VERSION} · aethersystems.net${n} ${cyan}│${n}" echo "${cyan}│${n} ${cyan}│${n}" echo "${cyan}└──────────────────────────────────────────────────────────────┘${n}" echo "" @@ -58,11 +68,9 @@ if ! command -v node >/dev/null 2>&1; then echo "" error "Node.js not found. Aether Agent needs Node.js >= 24." echo "" - info "Install it from https://nodejs.org (or your package manager):" + info "Install it from https://nodejs.org or your package manager." info " macOS: brew install node" - info " Ubuntu: curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -" - info " sudo apt-get install -y nodejs" - info " Other: https://nodejs.org/en/download" + info " Linux/other: https://nodejs.org/en/download" exit 1 fi @@ -102,23 +110,19 @@ else step "Installing aether-agent" fi -# Capture npm output, show spinner dots while installing -npm install -g aether-agents 2>&1 | while IFS= read -r line; do - # print dots for progress - printf '.' >&2 -done -echo "" - -if [ "${PIPESTATUS[0]}" -ne 0 ]; then +# Keep npm attached directly so POSIX sh receives its real exit status. The +# package has no runtime dependencies; --ignore-scripts makes that contract +# fail-safe if the registry graph ever drifts. +if ! npm install -g "aether-agents@${AETHER_VERSION}" --ignore-scripts; then error "Install failed. Check your network and npm permissions." echo "" info "If you see EACCES errors, try one of:" - info " npm install -g aether-agents --prefix ~/.local" - info " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash" + info " npm install -g aether-agents@${AETHER_VERSION} --ignore-scripts --prefix ~/.local" + info " Or install Node with a version manager from https://nodejs.org/en/download" exit 1 fi -AETH_V=$(aether --version 2>/dev/null || echo "0.1.0") +AETH_V=$(aether --version 2>/dev/null || echo "unknown") success "Aether Agent ${AETH_V} installed!" # ── Verify ── diff --git a/package.json b/package.json index ec357d6..9093bf3 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "start": "node dist/src/main.js", "test": "npm run build && node --test --test-isolation=none \"dist/test/**/*.test.js\"", "smoke": "npm run build && node -e \"import('./dist/src/core/smoke.js').then(m=>m.smokeMain()).then(c=>process.exit(c)).catch(e=>{console.error(e);process.exit(1)})\"", + "verify:production": "npm run build && node dist/scripts/verify-production.js", "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"", "prepack": "npm run build" }, diff --git a/scripts/verify-production.ts b/scripts/verify-production.ts new file mode 100644 index 0000000..4dc31a6 --- /dev/null +++ b/scripts/verify-production.ts @@ -0,0 +1,274 @@ +import { execFileSync } from "node:child_process"; +import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +interface PackageManifest { + name?: unknown; + version?: unknown; + main?: unknown; + types?: unknown; + bin?: unknown; + files?: unknown; + engines?: unknown; + dependencies?: unknown; + scripts?: unknown; +} + +export interface PackFile { + path: string; + size: number; +} + +export interface PackReport { + name: string; + version: string; + filename: string; + size: number; + unpackedSize: number; + entryCount: number; + files: PackFile[]; +} + +const REQUIRED_ROOT_FILES = new Set([ + "COMMANDS.md", + "LICENSE", + "NOTICE.md", + "README.md", + "package.json", +]); + +const MAX_UNPACKED_BYTES = 5_000_000; + +function record(value: unknown): Record | undefined { + return value !== null && typeof value === "object" && !Array.isArray(value) + ? (value as Record) + : undefined; +} + +export function validateManifest(manifest: PackageManifest, expectedTag?: string): string[] { + const errors: string[] = []; + if (manifest.name !== "aether-agents") errors.push("package name must remain aether-agents"); + if (typeof manifest.version !== "string" || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(manifest.version)) { + errors.push("package version must be valid semver"); + } + if (expectedTag && `v${String(manifest.version)}` !== expectedTag) { + errors.push(`release tag ${expectedTag} does not match package version v${String(manifest.version)}`); + } + + const engines = record(manifest.engines); + if (engines?.["node"] !== ">=24") errors.push("engines.node must be >=24"); + + const dependencies = record(manifest.dependencies); + if (dependencies && Object.keys(dependencies).length > 0) { + errors.push("runtime dependencies are forbidden by the zero-dependency production contract"); + } + + const files = Array.isArray(manifest.files) ? manifest.files : []; + if (!files.includes("dist/src")) errors.push("package files must include dist/src"); + if (files.includes("dist") || files.some((value) => typeof value === "string" && value.includes("test"))) { + errors.push("package files must not include compiled tests"); + } + + const scripts = record(manifest.scripts); + if (typeof scripts?.["prepack"] !== "string" || !scripts["prepack"].includes("build")) { + errors.push("prepack must build the release artifact"); + } + return errors; +} + +export function validatePack(report: PackReport, manifest: PackageManifest): string[] { + const errors: string[] = []; + const paths = new Set(report.files.map((file) => file.path.replaceAll("\\", "/"))); + for (const required of REQUIRED_ROOT_FILES) { + if (!paths.has(required)) errors.push(`package is missing required file ${required}`); + } + for (const required of [manifest.main, manifest.types]) { + if (typeof required === "string" && !paths.has(required.replace(/^\.\//, ""))) { + errors.push(`package is missing entrypoint ${required}`); + } + } + const bins = record(manifest.bin); + for (const target of Object.values(bins ?? {})) { + if (typeof target === "string" && !paths.has(target.replace(/^\.\//, ""))) { + errors.push(`package is missing binary target ${target}`); + } + } + + for (const path of paths) { + const allowed = REQUIRED_ROOT_FILES.has(path) || path.startsWith("dist/src/"); + if (!allowed) errors.push(`unexpected package content: ${path}`); + if (/(^|\/)(test|tests|_loopstate)(\/|$)/i.test(path) || /(^|\/)\.env(?:\.|$)/i.test(path)) { + errors.push(`sensitive or non-runtime package content: ${path}`); + } + } + if (report.unpackedSize > MAX_UNPACKED_BYTES) { + errors.push(`unpacked package exceeds ${MAX_UNPACKED_BYTES} bytes`); + } + return errors; +} + +export function validateWorkflowText(name: string, text: string): string[] { + const errors: string[] = []; + if (/^\s*pull_request_target\s*:/m.test(text)) errors.push(`${name}: pull_request_target is forbidden`); + if (!/^permissions:\s*$/m.test(text)) errors.push(`${name}: explicit workflow permissions are required`); + + const uses = [...text.matchAll(/^\s*(?:-\s*)?uses:\s+([^\s#]+)(?:\s+#.*)?$/gm)].map((match) => match[1]!); + for (const action of uses) { + if (action.startsWith("./") || action.startsWith("docker://")) continue; + const separator = action.lastIndexOf("@"); + const ref = separator >= 0 ? action.slice(separator + 1) : ""; + if (!/^[0-9a-f]{40}$/.test(ref)) errors.push(`${name}: action is not pinned to a full commit SHA: ${action}`); + } + + const jobs = (text.match(/^\s+runs-on:/gm) ?? []).length; + const timeouts = (text.match(/^\s+timeout-minutes:/gm) ?? []).length; + if (jobs !== timeouts) errors.push(`${name}: every runner job must set timeout-minutes`); + + for (const match of text.matchAll(/^\s*run:\s*(npm ci[^\n]*)$/gm)) { + if (!match[1]!.includes("--ignore-scripts")) errors.push(`${name}: npm ci must use --ignore-scripts`); + } + + if (/npm publish/.test(text)) { + if (/^\s*workflow_dispatch\s*:/m.test(text)) errors.push(`${name}: publishing cannot be manually dispatched`); + if (!/npm publish[^\n]*--provenance/.test(text)) errors.push(`${name}: npm publish must emit provenance`); + if (!/^\s+environment:\s*npm-production\s*$/m.test(text)) { + errors.push(`${name}: publishing must use the npm-production environment`); + } + if (!/^\s+id-token:\s*write\s*$/m.test(text)) errors.push(`${name}: publishing needs id-token write`); + if (!/^\s+attestations:\s*write\s*$/m.test(text)) errors.push(`${name}: publishing needs attestations write`); + if (!/git merge-base --is-ancestor HEAD origin\/main/.test(text)) { + errors.push(`${name}: release commit ancestry to main must be verified`); + } + if (!/npm install --global[^\n]*--ignore-scripts/.test(text)) { + errors.push(`${name}: the exact tarball must pass an install smoke test`); + } + } + return errors; +} + +export function validateInstallerText(name: string, text: string): string[] { + const errors: string[] = []; + if (/curl[^\n|]*\|\s*(?:sh|bash)\b/i.test(text) || /\birm\b[^\n|]*\|\s*iex\b/i.test(text)) { + errors.push(`${name}: pipe-to-shell installation is forbidden`); + } + if (/npm\s+install\s+-g\s+aether-agents/.test(text) && !/--ignore-scripts/.test(text)) { + errors.push(`${name}: global npm installation must disable lifecycle scripts`); + } + if (/PIPESTATUS/.test(text)) errors.push(`${name}: bash-only PIPESTATUS is forbidden in the POSIX installer`); + return errors; +} + +function readJson(path: string): unknown { + return JSON.parse(readFileSync(path, "utf8")) as unknown; +} + +function createPackReport(root: string): PackReport { + const npmCli = process.env["npm_execpath"]; + const command = npmCli ? process.execPath : process.platform === "win32" ? "npm.cmd" : "npm"; + const args = npmCli + ? [npmCli, "pack", "--dry-run", "--json", "--ignore-scripts"] + : ["pack", "--dry-run", "--json", "--ignore-scripts"]; + const raw = execFileSync(command, args, { + cwd: root, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + shell: !npmCli && process.platform === "win32", + }); + const parsed = JSON.parse(raw) as unknown; + if (!Array.isArray(parsed) || parsed.length !== 1) throw new Error("npm pack returned an unexpected report"); + return parsed[0] as PackReport; +} + +function runNpm(root: string, args: string[]): string { + const npmCli = process.env["npm_execpath"]; + const command = npmCli ? process.execPath : process.platform === "win32" ? "npm.cmd" : "npm"; + const commandArgs = npmCli ? [npmCli, ...args] : args; + return execFileSync(command, commandArgs, { + cwd: root, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + shell: !npmCli && process.platform === "win32", + }); +} + +function smokeInstallPackage(root: string, expectedVersion: string): string[] { + const errors: string[] = []; + const temp = mkdtempSync(join(tmpdir(), "aether-production-")); + try { + const packDir = join(temp, "pack"); + const prefix = join(temp, "prefix"); + mkdirSync(packDir); + const raw = runNpm(root, ["pack", "--json", "--ignore-scripts", "--pack-destination", packDir]); + const parsed = JSON.parse(raw) as PackReport[]; + const filename = parsed[0]?.filename; + if (!filename) return ["install smoke could not identify the packed tarball"]; + const tarball = join(packDir, filename); + runNpm(root, ["install", "--global", "--prefix", prefix, tarball, "--ignore-scripts"]); + const bin = process.platform === "win32" ? join(prefix, "aether.cmd") : join(prefix, "bin", "aether"); + if (!existsSync(bin)) return [`install smoke did not create ${bin}`]; + const options = { + cwd: temp, + encoding: "utf8" as const, + stdio: ["ignore", "pipe", "pipe"] as ["ignore", "pipe", "pipe"], + }; + const launch = process.platform === "win32" ? process.execPath : bin; + const launchArgs = process.platform === "win32" + ? [join(prefix, "node_modules", "aether-agents", "dist", "src", "main.js")] + : []; + const version = execFileSync(launch, [...launchArgs, "--version"], options).trim(); + if (version !== expectedVersion) errors.push(`installed CLI reported ${version}, expected ${expectedVersion}`); + execFileSync(launch, [...launchArgs, "--help"], options); + } catch (error) { + errors.push(`install smoke failed: ${error instanceof Error ? error.message : String(error)}`); + } finally { + rmSync(temp, { recursive: true, force: true }); + } + return errors; +} + +export function verifyProduction(root: string, expectedTag?: string): { + package: string; + version: string; + packedFiles: number; + packedBytes: number; + workflows: number; +} { + const manifest = readJson(join(root, "package.json")) as PackageManifest; + const pack = createPackReport(root); + const errors = [ + ...validateManifest(manifest, expectedTag), + ...validatePack(pack, manifest), + ]; + + const workflowDir = join(root, ".github", "workflows"); + const workflowNames = readdirSync(workflowDir).filter((name) => /\.ya?ml$/i.test(name)); + for (const name of workflowNames) { + errors.push(...validateWorkflowText(name, readFileSync(join(workflowDir, name), "utf8"))); + } + for (const name of ["install.sh", "install.ps1"]) { + errors.push(...validateInstallerText(name, readFileSync(join(root, name), "utf8"))); + } + errors.push(...validateInstallerText("README.md", readFileSync(join(root, "README.md"), "utf8"))); + + if (errors.length === 0) errors.push(...smokeInstallPackage(root, String(manifest.version))); + + if (errors.length > 0) throw new Error(`production verification failed:\n- ${errors.join("\n- ")}`); + return { + package: String(manifest.name), + version: String(manifest.version), + packedFiles: pack.entryCount, + packedBytes: pack.unpackedSize, + workflows: workflowNames.length, + }; +} + +const invokedPath = process.argv[1] ? resolve(process.argv[1]) : ""; +if (invokedPath === resolve(fileURLToPath(import.meta.url))) { + const tagAt = process.argv.indexOf("--tag"); + const expectedTag = tagAt >= 0 ? process.argv[tagAt + 1] : undefined; + if (tagAt >= 0 && !expectedTag) throw new Error("--tag requires a value"); + const result = verifyProduction(process.cwd(), expectedTag); + process.stdout.write(`${JSON.stringify({ ok: true, ...result })}\n`); +} diff --git a/test/production_hardening.test.ts b/test/production_hardening.test.ts new file mode 100644 index 0000000..2236fcd --- /dev/null +++ b/test/production_hardening.test.ts @@ -0,0 +1,100 @@ +import assert from "node:assert/strict"; +import { readFileSync, readdirSync } from "node:fs"; +import { join } from "node:path"; +import test from "node:test"; +import { + validateInstallerText, + validateManifest, + validatePack, + validateWorkflowText, + type PackReport, +} from "../scripts/verify-production.js"; +import { DEFAULT_CONFIG } from "../src/core/config.js"; + +const manifest = { + name: "aether-agents", + version: "1.2.3", + main: "dist/src/index.js", + types: "dist/src/index.d.ts", + bin: { aether: "dist/src/main.js" }, + files: ["dist/src", "README.md", "COMMANDS.md", "LICENSE", "NOTICE.md"], + engines: { node: ">=24" }, + scripts: { prepack: "npm run build" }, +}; + +const pack: PackReport = { + name: "aether-agents", + version: "1.2.3", + filename: "aether-agents-1.2.3.tgz", + size: 100, + unpackedSize: 1000, + entryCount: 8, + files: [ + "COMMANDS.md", + "LICENSE", + "NOTICE.md", + "README.md", + "package.json", + "dist/src/index.js", + "dist/src/index.d.ts", + "dist/src/main.js", + ].map((path) => ({ path, size: 1 })), +}; + +test("release manifest binds the tag and preserves the zero-runtime-dependency contract", () => { + assert.deepEqual(validateManifest(manifest, "v1.2.3"), []); + assert.match(validateManifest({ ...manifest, dependencies: { unsafe: "1.0.0" } }, "v1.2.4").join("\n"), /runtime dependencies/); + assert.match(validateManifest(manifest, "v1.2.4").join("\n"), /does not match/); +}); + +test("package allowlist rejects compiled tests and environment files", () => { + assert.deepEqual(validatePack(pack, manifest), []); + const poisoned = { + ...pack, + files: [...pack.files, { path: "dist/test/auth.test.js", size: 1 }, { path: ".env", size: 1 }], + }; + const errors = validatePack(poisoned, manifest).join("\n"); + assert.match(errors, /dist\/test/); + assert.match(errors, /\.env/); +}); + +test("workflow policy rejects floating actions and unbounded jobs", () => { + const valid = `permissions:\n contents: read\njobs:\n test:\n runs-on: ubuntu-latest\n timeout-minutes: 10\n steps:\n - uses: actions/checkout@0123456789abcdef0123456789abcdef01234567\n - run: npm ci --ignore-scripts\n`; + assert.deepEqual(validateWorkflowText("valid.yml", valid), []); + const invalid = valid + .replace("actions/checkout@0123456789abcdef0123456789abcdef01234567", "actions/checkout@v7") + .replace(" timeout-minutes: 10\n", ""); + const errors = validateWorkflowText("invalid.yml", invalid).join("\n"); + assert.match(errors, /not pinned/); + assert.match(errors, /timeout-minutes/); +}); + +test("publishing workflow must be event-gated, main-derived, and install-smoked", () => { + const invalid = `on:\n workflow_dispatch:\npermissions:\n contents: read\njobs:\n publish:\n runs-on: ubuntu-latest\n timeout-minutes: 10\n environment: npm-production\n permissions:\n id-token: write\n attestations: write\n steps:\n - run: npm publish package.tgz --provenance\n`; + const errors = validateWorkflowText("release.yml", invalid).join("\n"); + assert.match(errors, /manually dispatched/); + assert.match(errors, /ancestry/); + assert.match(errors, /install smoke/); +}); + +test("installer policy rejects pipe-to-shell and lifecycle-enabled global installs", () => { + assert.deepEqual(validateInstallerText("safe.sh", "npm install -g aether-agents@latest --ignore-scripts"), []); + assert.match(validateInstallerText("bad.sh", "curl https://example.test/install.sh | sh\nnpm install -g aether-agents").join("\n"), /pipe-to-shell/); +}); + +test("checked-in workflows and installers satisfy the production policy", () => { + const root = process.cwd(); + const workflowDir = join(root, ".github", "workflows"); + for (const name of readdirSync(workflowDir).filter((entry) => /\.ya?ml$/i.test(entry))) { + assert.deepEqual(validateWorkflowText(name, readFileSync(join(workflowDir, name), "utf8")), []); + } + for (const name of ["install.sh", "install.ps1", "README.md"]) { + assert.deepEqual(validateInstallerText(name, readFileSync(join(root, name), "utf8")), []); + } +}); + +test("operator-facing API defaults match the production transport path", () => { + assert.equal(DEFAULT_CONFIG.baseUrl, "https://api.aethersystems.net/cloud"); + assert.match(readFileSync(".env.example", "utf8"), /AETHER_BASE_URL=https:\/\/api\.aethersystems\.net\/cloud/); + assert.match(readFileSync("COMMANDS.md", "utf8"), /`https:\/\/api\.aethersystems\.net\/cloud`/); +}); diff --git a/tsconfig.json b/tsconfig.json index 367a493..4620447 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,6 @@ "declaration": true, "sourceMap": true }, - "include": ["src/**/*.ts", "test/**/*.ts"], + "include": ["src/**/*.ts", "test/**/*.ts", "scripts/**/*.ts"], "exclude": ["dist", "node_modules"] } From a2152d5b5bc7d3b9d08aa27854bca15260604940 Mon Sep 17 00:00:00 2001 From: dbarrante Date: Mon, 20 Jul 2026 14:28:30 -0400 Subject: [PATCH 3/4] =?UTF-8?q?loop(LOOP-13):=20final=20=E2=80=94=20comple?= =?UTF-8?q?te=20production=20hardening=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confidence: 0.90 Records LOOP-17/11/12/13 evidence, closes the final adversarial verifier gaps, and establishes the first drift/debt baseline. --- .../LOOP-07/2026-07-20T13-53-25-0400/trace.md | 10 +++ .../AUDIT-ARTIFACT.md | 45 ++++++++++ .../2026-07-20T14-04-43-0400/config.md | 7 ++ .../round-0-intake.md | 18 ++++ .../round-1-critique.md | 13 +++ .../round-1-revision.md | 8 ++ .../2026-07-20T14-04-43-0400/round-2-score.md | 14 +++ .../LOOP-11/2026-07-20T14-04-43-0400/trace.md | 8 ++ .../2026-07-20T14-04-43-0400/verdict.md | 13 +++ .../AUDIT-ARTIFACT.md | 47 ++++++++++ .../chaos-grade-matrix.md | 17 ++++ .../coverage-gap-map.md | 10 +++ .../mutation-report.md | 23 +++++ .../2026-07-20T14-12-13-0400/node-1.json | 13 +++ .../2026-07-20T14-12-13-0400/node-2.json | 23 +++++ .../2026-07-20T14-12-13-0400/node-3.json | 18 ++++ .../2026-07-20T14-12-13-0400/node-4.json | 12 +++ .../2026-07-20T14-12-13-0400/node-5.json | 18 ++++ .../2026-07-20T14-12-13-0400/node-6.json | 13 +++ .../2026-07-20T14-12-13-0400/node-7.json | 13 +++ .../2026-07-20T14-12-13-0400/node-8.json | 12 +++ .../2026-07-20T14-12-13-0400/node-9.json | 14 +++ .../LOOP-12/2026-07-20T14-12-13-0400/trace.md | 9 ++ .../AUDIT-ARTIFACT.md | 89 +++++++++++++++++++ .../complexity-report.md | 83 +++++++++++++++++ .../2026-07-20T14-19-07-0400/delta-report.md | 31 +++++++ .../duplication-dead-surface.md | 31 +++++++ .../2026-07-20T14-19-07-0400/node-1.json | 13 +++ .../2026-07-20T14-19-07-0400/node-2.json | 16 ++++ .../2026-07-20T14-19-07-0400/node-3.json | 14 +++ .../2026-07-20T14-19-07-0400/node-4.json | 20 +++++ .../2026-07-20T14-19-07-0400/node-5.json | 12 +++ .../2026-07-20T14-19-07-0400/node-6.json | 15 ++++ .../2026-07-20T14-19-07-0400/node-7.json | 22 +++++ .../2026-07-20T14-19-07-0400/scorecard.md | 20 +++++ .../structural-drift.md | 33 +++++++ .../target-architecture.json | 85 ++++++++++++++++++ .../LOOP-13/2026-07-20T14-19-07-0400/trace.md | 15 ++++ _loopstate/LOOP-13/debt-trend.md | 8 ++ .../AUDIT-ARTIFACT.md | 46 ++++++++++ .../breaker-round-1-attempt.md | 12 +++ .../breaker-round-1-candidates.md | 10 +++ .../breaker-round-2-attempt.md | 12 +++ .../breaker-round-2-candidates.md | 10 +++ .../breaker-round-3-attempt.md | 12 +++ .../breaker-round-3-candidates.md | 10 +++ .../breaker-round-4-attempt.md | 12 +++ .../breaker-round-4-candidates.md | 10 +++ .../breaker-round-5-attempt.md | 12 +++ .../breaker-round-5-candidates.md | 10 +++ .../breaker-round-6-attempt.md | 12 +++ .../breaker-round-6-candidates.md | 10 +++ .../breaker-round-7-attempt.md | 12 +++ .../breaker-round-7-candidates.md | 10 +++ .../breaker-round-8-attempt.md | 12 +++ .../breaker-round-8-candidates.md | 10 +++ .../builder-round-1-fix.md | 13 +++ .../builder-round-1-intake.md | 5 ++ .../builder-round-2-fix.md | 13 +++ .../builder-round-2-intake.md | 5 ++ .../builder-round-3-fix.md | 13 +++ .../builder-round-3-intake.md | 5 ++ .../convergence-round-1.md | 9 ++ .../convergence-round-2.md | 9 ++ .../convergence-round-3.md | 9 ++ .../convergence-round-4.md | 9 ++ .../convergence-round-5.md | 9 ++ .../convergence-round-6.md | 9 ++ .../convergence-round-7.md | 9 ++ .../convergence-round-8.md | 9 ++ .../2026-07-20T13-58-39-0400/human-report.md | 13 +++ .../LOOP-17/2026-07-20T13-58-39-0400/queue.md | 17 ++++ .../LOOP-17/2026-07-20T13-58-39-0400/trace.md | 7 ++ _loopstate/governance-ledger.md | 4 + ...7-20-devops-production-hardening-design.md | 34 ++++++- install.ps1 | 2 +- install.sh | 2 +- scripts/verify-production.ts | 14 ++- test/production_hardening.test.ts | 14 +++ 79 files changed, 1330 insertions(+), 10 deletions(-) create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/trace.md create mode 100644 _loopstate/LOOP-11/2026-07-20T14-04-43-0400/AUDIT-ARTIFACT.md create mode 100644 _loopstate/LOOP-11/2026-07-20T14-04-43-0400/config.md create mode 100644 _loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-0-intake.md create mode 100644 _loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-1-critique.md create mode 100644 _loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-1-revision.md create mode 100644 _loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-2-score.md create mode 100644 _loopstate/LOOP-11/2026-07-20T14-04-43-0400/trace.md create mode 100644 _loopstate/LOOP-11/2026-07-20T14-04-43-0400/verdict.md create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/AUDIT-ARTIFACT.md create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/chaos-grade-matrix.md create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/coverage-gap-map.md create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/mutation-report.md create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-1.json create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-2.json create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-3.json create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-4.json create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-5.json create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-6.json create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-7.json create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-8.json create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-9.json create mode 100644 _loopstate/LOOP-12/2026-07-20T14-12-13-0400/trace.md create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/AUDIT-ARTIFACT.md create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/complexity-report.md create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/delta-report.md create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/duplication-dead-surface.md create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-1.json create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-2.json create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-3.json create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-4.json create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-5.json create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-6.json create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-7.json create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/scorecard.md create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/structural-drift.md create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/target-architecture.json create mode 100644 _loopstate/LOOP-13/2026-07-20T14-19-07-0400/trace.md create mode 100644 _loopstate/LOOP-13/debt-trend.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/AUDIT-ARTIFACT.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-1-attempt.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-1-candidates.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-2-attempt.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-2-candidates.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-3-attempt.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-3-candidates.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-4-attempt.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-4-candidates.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-5-attempt.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-5-candidates.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-6-attempt.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-6-candidates.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-7-attempt.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-7-candidates.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-8-attempt.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-8-candidates.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-1-fix.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-1-intake.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-2-fix.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-2-intake.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-3-fix.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-3-intake.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-1.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-2.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-3.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-4.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-5.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-6.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-7.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-8.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/human-report.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/queue.md create mode 100644 _loopstate/LOOP-17/2026-07-20T13-58-39-0400/trace.md diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/trace.md b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/trace.md new file mode 100644 index 0000000..cd86576 --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/trace.md @@ -0,0 +1,10 @@ +# Invocation trace + +| node | role | reasoning summary | tools used | duration | confidence | result | +|---|---|---|---|---|---:|---| +| 1 | researcher | Map the repository-owned production path and mark live infrastructure outside scope. | git, GitHub, docs | bounded | 0.88 | PASS-WITH-UNKNOWNS | +| 2-4 | infrastructure reviewer | Confirm absence of container, unit, and IaC surfaces without inventing them. | file inventory, static scan | bounded | 0.93 | N/A-JUSTIFIED | +| 5-7 | security reviewer | Scan credential shapes, IAM, and network call paths without printing secrets. | history scan, tests, manual read | bounded | 0.88 | PASS-WITH-UNKNOWNS | +| 8 | supply-chain reviewer | Audit dependencies, package contents, action pins, SBOM and release provenance. | npm audit, npm pack, npm sbom | bounded | 0.94 | PASS | +| 9 | operations reviewer | Classify state, backup ownership, evidence retention, and recovery gaps. | docs, inventory | bounded | 0.85 | OPERATOR-DEFERRED | +| final | hostile SRE | Attack unknowns first and reject a silent pass on unreadable admin controls. | artifact review | bounded | 0.88 | FAIL-WITH-ARTIFACT | diff --git a/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/AUDIT-ARTIFACT.md b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/AUDIT-ARTIFACT.md new file mode 100644 index 0000000..57c1dc9 --- /dev/null +++ b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/AUDIT-ARTIFACT.md @@ -0,0 +1,45 @@ +# LOOP-11 Adversarial Review Artifact + +run_id: `2026-07-20T14-04-43-0400` +date: `2026-07-20` +target: PR #48 branch diff +branch: `loop/devops-production-hardening` +checkpoint: `3aa36f0` + +## Verdict: PASS + +FREE-MAD score history: `74 -> 92`. Round 1 found two HIGH policy-bypass gaps and one LOW operator-message drift. The revision added structural negative tests and fixed all three; round 2 re-attacked the exact paths and passed them. + +## Confidence block + +```yaml +confidence_block: + confidence: 0.90 + risk: medium + evidence: [cited static review, negative tests, package install smoke, full action pin scan] + unknown: [remote checks, npm-production configuration, branch protection, alert enablement] + missing_evidence: [successful pushed workflows, repository-admin export] +``` + +## Findings + +| id | severity | file | description | status | +|---|---|---|---|---| +| L11-001 | HIGH | `scripts/verify-production.ts` | Block-scalar `npm ci` could bypass lifecycle policy | RESOLVED | +| L11-002 | HIGH | `scripts/verify-production.ts` | Quoted/npm-i/npx installer forms could bypass lifecycle policy | RESOLVED | +| L11-003 | LOW | `install.sh`, `install.ps1` | Exact-version install was described as latest | RESOLVED | +| L11-004 | HIGH | external settings | Admin controls and npm credential scope are unreadable | OPERATOR-DEFERRED as L07-007 | + +## Tests generated + +- Block-scalar npm-ci lifecycle negative fixture. +- Quoted package-spec and npx lifecycle negative fixtures. + +## Governance row + +`| 2026-07-20 | LOOP-11 | 2026-07-20T14-04-43-0400 | PASS | 1 | 2 | 99 | 0 | 2 | 0 | 0 | 0.90 | score 74 to 92 |` + +## Recommended next loops + +- LOOP-12 mutation testing on `scripts/verify-production.ts` and its tests. +- LOOP-13 final drift/debt sweep. diff --git a/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/config.md b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/config.md new file mode 100644 index 0000000..7748dbe --- /dev/null +++ b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/config.md @@ -0,0 +1,7 @@ +# Debate configuration + +- protocol: FREE-MAD +- personas: hostile supply-chain engineer, skeptical release SRE, correctness reviewer +- round budget: 2 +- pass threshold: 0.85 confidence and zero unresolved cited HIGH/CRITICAL issues +- silent_agreement: false (round 1 produced cited disagreements) diff --git a/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-0-intake.md b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-0-intake.md new file mode 100644 index 0000000..ae06ef4 --- /dev/null +++ b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-0-intake.md @@ -0,0 +1,18 @@ +# LOOP-11 intake + +Target: full diff from `origin/main` through `3aa36f0` plus uncommitted LOOP-17 artifacts and reviewer revisions. + +Completion criteria: + +- No unpinned third-party action, implicit workflow permission, unbounded runner job, or persisted checkout credential. +- Release only from a version-matching tag whose commit belongs to main, through `npm-production`. +- The exact public tarball is allowlisted, attested, install-smoked, and published with provenance. +- No dependency lifecycle script runs in CI, release, README, or installers. +- Full suite and production verifier pass; all external unknowns remain explicit. + +confidence_block: + confidence: 0.88 + risk: high + evidence: [branch diff, workflow scan, package smoke, tests] + unknown: [repository-admin settings, remote workflow results] + missing_evidence: [pushed checks, admin settings export] diff --git a/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-1-critique.md b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-1-critique.md new file mode 100644 index 0000000..d474ee6 --- /dev/null +++ b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-1-critique.md @@ -0,0 +1,13 @@ +# Round 1 critique + +| issue | severity | cited evidence | score delta | +|---|---|---|---:| +| L11-001: workflow lifecycle guard inspected only one-line `run: npm ci`; a block scalar could execute `npm ci` without `--ignore-scripts` while the policy passed | HIGH | `validateWorkflowText` initial same-line regular expression; new negative block-scalar fixture | -12 | +| L11-002: installer guard did not match quoted package specs or `npm i`/npx forms, so current quoted installers could regress to lifecycle-enabled execution without a failing policy test | HIGH | initial package-spec matcher versus the quoted installer command | -10 | +| L11-003: both installers said an existing install would update to latest even when an exact version was requested | LOW | `install.sh` and `install.ps1` status text | -2 | +| L11-004: GitHub environment, branch rules, scanning enablement, and npm token scope are not visible | HIGH/EXTERNAL | LOOP-07 L07-007 and API permission failures | 0; cannot be fixed in branch | + +Round score: 74 / 100 +Verdict: REVISE + +The reviewers attacked the unknown list first and preserved L11-004 as an explicit external gate rather than using it to claim a code failure. diff --git a/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-1-revision.md b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-1-revision.md new file mode 100644 index 0000000..6de9af8 --- /dev/null +++ b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-1-revision.md @@ -0,0 +1,8 @@ +# Round 1 revision + +- Scanned every workflow line containing `npm ci`, including block scalars, and required `--ignore-scripts` on that line. +- Generalized installer/README validation across `npm install`, `npm i`, quoted `aether-agents@...` specs, and npx execution. +- Added negative regression fixtures for block-scalar CI, quoted installer specs, and npx. +- Corrected both installer update messages to report the requested release. + +Verification: TypeScript strict typecheck, 7 targeted production-hardening tests, and isolated package production verification all passed. diff --git a/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-2-score.md b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-2-score.md new file mode 100644 index 0000000..da40900 --- /dev/null +++ b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/round-2-score.md @@ -0,0 +1,14 @@ +# Round 2 re-attack and score + +| criterion | result | evidence | +|---|---|---| +| Block-scalar lifecycle bypass | KILLED | negative fixture reports missing `--ignore-scripts` | +| Quoted/global/npx installer bypass | KILLED | quoted and npx negative fixtures fail policy | +| Exact package execution | PASS | isolated global install, binary existence, version and help | +| Release authorization | PASS | release event only, protected environment, version binding, main ancestry | +| Supply-chain identity | PASS | full-SHA actions, least permissions, provenance and attestation | +| External controls | UNKNOWN, EXPLICIT | L07-007 remains operator-deferred | + +Round score: 92 / 100 +Verdict: PASS +Final confidence: 0.90 diff --git a/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/trace.md b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/trace.md new file mode 100644 index 0000000..7f88ac4 --- /dev/null +++ b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/trace.md @@ -0,0 +1,8 @@ +# Invocation trace + +| node | role | reasoning summary | tools used | duration | confidence | result | +|---|---|---|---|---|---:|---| +| 1-2 | planner | Ingest the full branch and configure three hostile reviewer lenses. | git diff, artifacts | bounded | 0.88 | PASS | +| 3-4 | reviewers | Attack external unknowns, workflow parsing variants, install paths, and exact artifact claims. | static scan, command smoke | bounded | 0.88 | REVISE | +| 5 | generator | Apply cited revisions and negative regression tests. | edit, typecheck, tests | bounded | 0.92 | PASS | +| 6-8 | verifier | Re-attack cited gaps, score history, and emit verdict. | targeted tests, production verifier | bounded | 0.90 | PASS | diff --git a/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/verdict.md b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/verdict.md new file mode 100644 index 0000000..b725166 --- /dev/null +++ b/_loopstate/LOOP-11/2026-07-20T14-04-43-0400/verdict.md @@ -0,0 +1,13 @@ +# Score-based verdict + +- score history: [74, 92] +- verdict: PASS +- unresolved cited CRITICAL/HIGH code issues: 0 +- external operator-deferred HIGH controls: 1 (L07-007) + +confidence_block: + confidence: 0.90 + risk: medium + evidence: [FREE-MAD two-round review, negative regressions, npm-test, production-preflight] + unknown: [remote workflow behavior, GitHub admin settings, npm credential scope] + missing_evidence: [successful PR checks, environment and branch policy export] diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/AUDIT-ARTIFACT.md b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/AUDIT-ARTIFACT.md new file mode 100644 index 0000000..57d066c --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/AUDIT-ARTIFACT.md @@ -0,0 +1,47 @@ +# LOOP-12 Test, Mutation, and Chaos Artifact + +run_id: `2026-07-20T14-12-13-0400` +date: `2026-07-20` +target: production hardening surface +branch: `loop/devops-production-hardening` +checkpoint: `3aa36f0` + +## Verdict: PASS-NO-CHAOS + +The scoped verifier has 90.69% line coverage and 100% function coverage. All 10 +high-risk non-equivalent mutants were killed for a 100% catalog mutation score. +Every fixed LOOP-07/17/11 bug has a permanent regression. The full 695-test suite +passed three consecutive runs. Chaos was skipped, as required, because no named +non-production environment or operator approval was provided. + +## Confidence block + +```yaml +confidence_block: + confidence: 0.93 + risk: medium + evidence: [90.69-line-coverage, 10-of-10-mutants-killed, 695-tests-x3, exact-package-smoke] + unknown: [ten chaos scenarios, remote runner behavior] + missing_evidence: [approved chaos sandbox, pushed CI matrix] +``` + +## Findings + +| id | severity | description | status | +|---|---|---|---| +| L12-001 | MEDIUM | Chaos resilience remains ungraded without an approved sandbox | OPERATOR-DEFERRED by mandatory gate | + +## Tests generated + +- Eight production-hardening contract/integration tests. +- Ten mutation trials, all killed. +- Three complete stability runs: 695/695 each. + +## Governance row + +`| 2026-07-20 | LOOP-12 | 2026-07-20T14-12-13-0400 | PASS-NO-CHAOS | 0 | 2 | 100 | 0 | 0 | 0 | 0 | 0.93 | 90.69 coverage, 100 mutation, 695 x3 |` + +## Recommended next loops + +- LOOP-13 drift/debt scoring now. +- Re-run LOOP-12 node 7 only when a named disposable sandbox and scenario approval exist. diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/chaos-grade-matrix.md b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/chaos-grade-matrix.md new file mode 100644 index 0000000..918010a --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/chaos-grade-matrix.md @@ -0,0 +1,17 @@ +# Chaos grade matrix + +No `chaos_env` was supplied, so all chaos scenarios were skipped under the +mandatory approval gate. Zero scenarios targeted production. + +| scenario | grade | TTR | finding | +|---|---|---|---| +| Database unavailable | SKIPPED | n/a | explicit sandbox required | +| Cache offline | SKIPPED | n/a | explicit sandbox required | +| API timeout | SKIPPED | n/a | explicit sandbox required | +| Network latency | SKIPPED | n/a | explicit sandbox required | +| Disk full | SKIPPED | n/a | explicit sandbox required | +| Memory pressure | SKIPPED | n/a | explicit sandbox required | +| Token limit hit | SKIPPED | n/a | explicit sandbox required | +| LLM unavailable | SKIPPED | n/a | explicit sandbox required | +| Model returns garbage | SKIPPED | n/a | explicit sandbox required | +| MCP server unavailable | SKIPPED | n/a | explicit sandbox required | diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/coverage-gap-map.md b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/coverage-gap-map.md new file mode 100644 index 0000000..1210e88 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/coverage-gap-map.md @@ -0,0 +1,10 @@ +# Coverage gap map + +| file | line | branch | function | priority | note | +|---|---:|---:|---:|---|---| +| `scripts/verify-production.ts` | 90.69% | 55.68% | 100.00% | P1 | All production guard functions and success path covered; remaining lines are defensive malformed-tool/error branches and CLI argument errors | +| Workflow YAML | policy-tested | variant-tested | n/a | P1 | Full-SHA, timeout, permission, lifecycle, release, ancestry, and install-smoke invariants | +| Installers | static + parser | negative variants | n/a | P1 | Pipe-to-shell, version injection, hidden exit status, lifecycle execution | + +Scope line coverage clears the 80% LOOP-12 gate. Branch coverage is recorded, +but this loop's declared quantitative gate is line coverage. diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/mutation-report.md b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/mutation-report.md new file mode 100644 index 0000000..daec713 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/mutation-report.md @@ -0,0 +1,23 @@ +# Mutation report + +Mutation target: `scripts/verify-production.ts` +Method: apply one source mutation, build, run the nearest named test, require a +non-zero test result, then restore the exact source before the next mutant. + +| mutant | mutation | killing test | result | +|---|---|---|---| +| M1 | invert version/tag equality | release manifest binds the tag | KILLED | +| M2 | disable zero-runtime-dependency rejection | release manifest contract | KILLED | +| M3 | disable full-SHA action rejection | workflow floating-action negative | KILLED | +| M4 | invert job timeout parity | workflow unbounded-job negative | KILLED | +| M5 | disable npm-ci lifecycle guard | block-scalar npm-ci negative | KILLED | +| M6 | disable manual publishing-dispatch rejection | publishing workflow negative | KILLED | +| M7 | disable curl-to-shell detection | installer pipe-to-shell negative | KILLED | +| M8 | disable npm/npx lifecycle detection | quoted and npx negatives | KILLED | +| M9 | invert installed CLI version comparison | exact packed CLI smoke | KILLED | +| M10 | invert maximum unpacked package size | valid package fixture | KILLED | + +Score: **10 / 10 = 100%**. No equivalent mutants were excluded. The catalog is +stratified over the highest-risk release, dependency, workflow, installer, and +artifact-integrity guards; it is not claimed as exhaustive mutation of every +operator in the file. diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-1.json b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-1.json new file mode 100644 index 0000000..aa07ed9 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-1.json @@ -0,0 +1,13 @@ +{ + "node": 1, + "scope": ["scripts/verify-production.ts", "test/production_hardening.test.ts", ".github/workflows", "install.sh", "install.ps1"], + "coverage": { "file": "dist/scripts/verify-production.js", "line_pct": 90.69, "branch_pct": 55.68, "function_pct": 100.0 }, + "git_sha": "3aa36f02ff0488367eb673e15e9e624b946694df", + "confidence_block": { + "confidence": 0.93, + "risk": "medium", + "evidence": ["node test coverage", "scoped test run"], + "unknown": ["remote workflow execution branches"], + "missing_evidence": ["successful GitHub Actions runs"] + } +} diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-2.json b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-2.json new file mode 100644 index 0000000..ac73b10 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-2.json @@ -0,0 +1,23 @@ +{ + "node": 2, + "fixed_bug_regressions": { + "L07-001": "release manifest and publishing policy tests", + "L07-002": "checked-in workflow policy test", + "L07-003": "installer policy and parser checks", + "L07-006": "operator-facing API defaults test", + "L17-001": "event, tag, and ancestry policy test", + "L17-002": "YAML shorthand action-pin test", + "L17-003": "exact tarball install/version/help smoke", + "L11-001": "block-scalar npm-ci negative fixture", + "L11-002": "quoted/npm-i/npx negative fixtures", + "L11-003": "checked-in installer policy test" + }, + "missing_regressions": [], + "confidence_block": { + "confidence": 0.95, + "risk": "low", + "evidence": ["LOOP artifacts", "test symbol trace", "targeted runs"], + "unknown": [], + "missing_evidence": [] + } +} diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-3.json b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-3.json new file mode 100644 index 0000000..28e42be --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-3.json @@ -0,0 +1,18 @@ +{ + "node": 3, + "behavioral_tests": { + "regression": 6, + "integration": 1, + "snapshot": 0, + "configuration_contract": 1 + }, + "flaky_tests_discarded": 0, + "stability_runs": ["695/695", "695/695", "695/695"], + "confidence_block": { + "confidence": 0.94, + "risk": "low", + "evidence": ["three full-suite runs", "exact package integration smoke"], + "unknown": ["hosted runner timing"], + "missing_evidence": ["remote CI matrix"] + } +} diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-4.json b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-4.json new file mode 100644 index 0000000..e6de178 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-4.json @@ -0,0 +1,12 @@ +{ + "node": 4, + "generative_strategy": "Deterministic adversarial variants were used instead of adding a property-testing runtime dependency: valid/mismatched tags, floating/full SHA refs, inline/block npm commands, quoted/unquoted package specs, npm/npx forms, and poisoned package paths.", + "counterexamples": [], + "confidence_block": { + "confidence": 0.88, + "risk": "medium", + "evidence": ["boundary variant matrix", "negative tests"], + "unknown": ["not an exhaustive YAML grammar fuzzer"], + "missing_evidence": ["future grammar-based workflow fuzzing"] + } +} diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-5.json b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-5.json new file mode 100644 index 0000000..e7d5d60 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-5.json @@ -0,0 +1,18 @@ +{ + "node": 5, + "tool": "agentic targeted source mutation", + "catalog_size": 10, + "catalog_executed": 10, + "killed": 10, + "survived": 0, + "equivalent": 0, + "mutation_score": 100.0, + "sampling": "100% of the declared ten high-risk production guard catalog; not an exhaustive every-operator mutation of the file", + "confidence_block": { + "confidence": 0.96, + "risk": "low", + "evidence": ["ten apply-run-revert mutation trials", "targeted failing tests"], + "unknown": ["unselected low-risk syntax mutations"], + "missing_evidence": ["future full AST mutation runner"] + } +} diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-6.json b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-6.json new file mode 100644 index 0000000..b09bf4f --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-6.json @@ -0,0 +1,13 @@ +{ + "node": 6, + "surviving_mutants": 0, + "remediations": [], + "updated_mutation_score": 100.0, + "confidence_block": { + "confidence": 0.96, + "risk": "low", + "evidence": ["mutation report", "source restoration check"], + "unknown": [], + "missing_evidence": [] + } +} diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-7.json b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-7.json new file mode 100644 index 0000000..cdd3674 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-7.json @@ -0,0 +1,13 @@ +{ + "node": 7, + "status": "SKIPPED", + "reason": "No chaos_env was provided. The loop forbids guessing a target and requires operator approval of the sandbox and scenario list before injection.", + "production_targets_touched": 0, + "confidence_block": { + "confidence": 1.0, + "risk": "low", + "evidence": ["no chaos commands executed"], + "unknown": ["runtime behavior under the ten chaos scenarios"], + "missing_evidence": ["explicit non-production sandbox and operator approval"] + } +} diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-8.json b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-8.json new file mode 100644 index 0000000..b195564 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-8.json @@ -0,0 +1,12 @@ +{ + "node": 8, + "status": "SKIPPED-WITH-NODE-7", + "graded_scenarios": 0, + "confidence_block": { + "confidence": 1.0, + "risk": "medium", + "evidence": ["approval gate preserved"], + "unknown": ["chaos degradation grades and recovery times"], + "missing_evidence": ["approved sandbox campaign"] + } +} diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-9.json b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-9.json new file mode 100644 index 0000000..d427663 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/node-9.json @@ -0,0 +1,14 @@ +{ + "node": 9, + "verdict": "PASS-NO-CHAOS", + "line_coverage": 90.69, + "mutation_score": 100.0, + "full_suite": "695/695 x3", + "confidence_block": { + "confidence": 0.93, + "risk": "medium", + "evidence": ["coverage", "mutation testing", "three stability runs", "regression sweep"], + "unknown": ["chaos resilience", "remote workflow results"], + "missing_evidence": ["approved chaos sandbox", "pushed CI checks"] + } +} diff --git a/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/trace.md b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/trace.md new file mode 100644 index 0000000..860a5d2 --- /dev/null +++ b/_loopstate/LOOP-12/2026-07-20T14-12-13-0400/trace.md @@ -0,0 +1,9 @@ +# Invocation trace + +| node | role | reasoning summary | tools used | duration | confidence | result | +|---|---|---|---|---|---:|---| +| 1-2 | QA mapper | Scope production guards, measure coverage, and map every fixed finding to a regression. | node coverage, artifact read | bounded | 0.94 | PASS | +| 3-4 | test generator | Exercise behavior, integration, and adversarial input variants without adding runtime dependencies. | node:test, package smoke | bounded | 0.90 | PASS | +| 5-6 | mutation adversary | Apply, run, and revert ten high-risk guard mutations. | apply patch, targeted tests | bounded | 0.96 | 10 KILLED | +| 7-8 | chaos controller | Enforce missing-environment approval gate; touch no production target. | control logic | bounded | 1.00 | SKIPPED | +| 9 | verifier | Audit denominator, restoration, coverage, regression sweep, and three-run stability. | full tests, report review | bounded | 0.93 | PASS-NO-CHAOS | diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/AUDIT-ARTIFACT.md b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/AUDIT-ARTIFACT.md new file mode 100644 index 0000000..031e019 --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/AUDIT-ARTIFACT.md @@ -0,0 +1,89 @@ +# LOOP-13 AUDIT-ARTIFACT + +## Verdict + +**PASS-WITH-BASELINE-DEBT** — confidence **0.90**. + +The first drift/debt run established a reproducible descriptive baseline for +128 source modules and 424 static import/re-export edges. It found one +pre-existing type-level circular dependency, three duplication families, one +suspected-dead internal TUI prototype, 88 of 1,306 functions above cyclomatic +10, and one file above the 800-line house limit. The production-hardening branch +does not modify `src/**`, so none of those source findings were introduced here. + +## Scorecard + +| axis | score | +|---|---:| +| Security | 85 | +| Performance | N/A | +| Maintainability | 66 | +| Complexity | 62 | +| Testability | 76 | +| Accessibility | N/A | +| Composite over scored axes | 72.3 | + +See `scorecard.md` for formulas and evidence. N/A axes are not scored silently. + +## Findings + +- **L13-001 MEDIUM:** `core/auth.ts <-> core/transport.ts` static SCC. The + `transport -> auth` edge is type-only; move the token-store contract or the + login transport policy to a leaf module. +- **L13-003 MEDIUM:** memory event fields are repeated across protocol, stream, + and renderer shapes. +- **L13-004 MEDIUM:** media flag parsing is duplicated between top-level and + slash commands. +- **L13-005 LOW:** progress-bar calculation is implemented in three UI modules. +- **L13-006 MEDIUM:** `TuiLayout` has test callers only and is not publicly + exported; classify as suspected-dead until the operator confirms its future. +- **L13-007 MEDIUM:** `src/commands/chat.ts` is 967 lines and is both a high-degree + module and the sole >800-line source file. + +## Delta + +Initial baseline. No trend delta, axis drop, cycle addition, or watchlist growth +can be claimed. The next run compares against +`2026-07-20T14-19-07-0400` and must preserve or disclose formula changes. + +## Adversarial verification + +The Forensic Architect completed two FREE-MAD rounds. It forced explicit +first-run labeling, rejected a dead verdict for dynamically loaded command +modules, recomputed Complexity as 62, and verified that every unmeasured or +unavailable surface is listed. No score changed by more than five points on +recomputation. + +## Read-only guarantee + +LOOP-13 changed no runtime, test, installer, workflow, or implementation +documentation file. Its writes are under `_loopstate/LOOP-13/` plus the required +governance metadata. Runtime changes already present in the working tree were +created and tested by the preceding LOOP-11/12 stages. + +## Recommended next loops + +- **LOOP-01:** own L13-001, L13-003, L13-004, and the `chat.ts`/high-degree + maintainability seams in a separately scoped architecture refactor. +- **LOOP-02 or LOOP-06:** assess terminal accessibility and consolidate progress + calculations without erasing intentional visual differences. +- **LOOP-12:** continue whole-repository coverage improvement; retain the + production verifier's mutation catalog. +- **LOOP-07:** close L07-007 with a repository/npm administrator by verifying + branch protection, required checks, environment reviewers, token scope, + scanning, and backup mirror controls. + +## Confidence + +- score: **0.90** +- evidence: structured AST graph, SCC analysis, exact-window duplication scan, + package export and dynamic import checks, complexity AST analysis, LOOP-07 and + LOOP-12 artifacts +- unknown: production traffic, repository/npm administrator settings, cognitive + complexity, complete call graph, current performance and accessibility state +- missing evidence: prior LOOP-13 baseline, benchmark artifact, accessibility + artifact, administrator read access + +## Governance row + +`| 2026-07-20 | LOOP-13 | 2026-07-20T14-19-07-0400 | PASS-WITH-BASELINE-DEBT | 0 | 2 | 97 | 0 | 1 | 0 | 0 | 0.90 | First descriptive baseline; composite 72.3; two axes N/A |` diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/complexity-report.md b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/complexity-report.md new file mode 100644 index 0000000..546cdbb --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/complexity-report.md @@ -0,0 +1,83 @@ +# Complexity report + +## Method + +- Parsed 129 TypeScript files in `src/` and `scripts/` with the TypeScript 5.9.3 + compiler API used only as an analysis tool. +- Cyclomatic complexity starts at 1 per function and adds one for conditional, + loop, catch, case, ternary, and `&&`/`||`/`??` branch points. Nested functions + are counted separately. +- Static dependency degree and depth include relative imports and re-exports in + 128 `src/` files: 424 edges, maximum SCC-collapsed depth 10. +- Cognitive complexity and a complete call graph are unmeasured because no + compatible analyzer/code-graph service is configured. Import degree is not + mislabeled as call-graph size. +- Thirty-seven classes were found; the six `extends` clauses all directly extend + built-in `Error`, so observed local inheritance depth is 1. + +## Aggregates + +| metric | value | threshold / interpretation | +|---|---:|---| +| TypeScript files | 129 | `src` plus production verification script | +| functions | 1,306 | AST function-like nodes | +| functions with cyclomatic > 10 | 88 (6.74%) | watch | +| files > 800 lines | 1 | house-limit breach | +| maximum dependency depth | 10 | baseline | +| import SCCs with >1 member | 1 | one type-level cycle | +| maximum inheritance depth | 1 | low | +| cognitive complexity | unmeasured | unsupported by configured analyzer | +| call-graph size | unmeasured | no call-graph service configured | + +## Top-20 function risk ranking + +| rank | function | cyclomatic | +|---:|---|---:| +| 1 | `src/commands/slash.ts:102 handleSlash` | 86 | +| 2 | `src/core/stream.ts:79 normalizeFrame` | 69 | +| 3 | `src/commands/chat.ts:684 anonymous handler` | 53 | +| 4 | `src/main.ts:50 main` | 47 | +| 5 | `src/commands/code.ts:102 cmdCode` | 44 | +| 6 | `src/ui/keys.ts:75 decodeKey` | 43 | +| 7 | `src/commands/goals.ts:76 handleGoal` | 35 | +| 8 | `src/core/render.ts:65 frame` | 33 | +| 9 | `src/ui/text.ts:19 charWidth` | 33 | +| 10 | `src/commands/memory.ts:80 cmdMemory` | 30 | +| 11 | `src/commands/chat.ts:554 anonymous handler` | 29 | +| 12 | `src/commands/slash_media.ts:27 parseSlashFlags` | 27 | +| 13 | `src/ui/host_render.ts:81 event` | 26 | +| 14 | `src/core/web.ts:87 isBlockedIPv6` | 24 | +| 15 | `src/ui/logs_viewer.ts:59 viewerReduce` | 24 | +| 16 | `src/commands/goals.ts:22 decomposeGoal` | 23 | +| 17 | `src/commands/media.ts:54 parseFlags` | 23 | +| 18 | `src/commands/slash_hud.ts:64 hudSlash` | 23 | +| 19 | `src/commands/slash_media.ts:195 storyboardSlash` | 23 | +| 20 | `src/core/session_log.ts:50 loggedEvent` | 23 | + +The new `scripts/verify-production.ts` has three functions over 10: +`validateWorkflowText` 23, `validatePack` 16, and `validateManifest` 15. These +are pure policy validators with focused tests and are tracked as part of this +branch's delta, not silently omitted. + +## God-file and high-degree watchlist + +Top decile is 13 of 128 source modules. `src/commands/chat.ts` also breaches the +800-line house limit. + +| file | lines | fan-in | fan-out | degree | reason | delta | +|---|---:|---:|---:|---:|---|---| +| `src/core/transport.ts` | 421 | 40 | 2 | 42 | top decile | initial | +| `src/ui/theme.ts` | 67 | 37 | 1 | 38 | top decile | initial | +| `src/commands/chat.ts` | 967 | 4 | 32 | 36 | top decile and >800 | initial | +| `src/core/context.ts` | 32 | 31 | 3 | 34 | top decile | initial | +| `src/commands/code.ts` | 445 | 1 | 26 | 27 | top decile | initial | +| `src/commands/slash.ts` | 490 | 1 | 19 | 20 | top decile | initial | +| `src/main.ts` | 253 | 0 | 19 | 19 | top decile entrypoint | initial | +| `src/index.ts` | 27 | 0 | 17 | 17 | top decile public barrel | initial | +| `src/core/brain_protocol.ts` | 338 | 14 | 0 | 14 | top decile contract leaf | initial | +| `src/core/errors.ts` | 131 | 14 | 0 | 14 | top decile error leaf | initial | +| `src/core/tool_executor.ts` | 280 | 10 | 4 | 14 | top decile | initial | +| `src/commands/mcp.ts` | 407 | 0 | 13 | 13 | top decile dynamic entrypoint | initial | +| `src/ui/text.ts` | 146 | 13 | 0 | 13 | top decile utility leaf | initial | + +No growth verdict is possible until a subsequent run uses this snapshot. diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/delta-report.md b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/delta-report.md new file mode 100644 index 0000000..a5e51df --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/delta-report.md @@ -0,0 +1,31 @@ +# Delta report + +Baseline reference: none. This is the first LOOP-13 run and establishes the +descriptive baseline. + +| measure | current | delta | +|---|---:|---| +| Security | 85 | initial | +| Performance | N/A | initial; evidence absent | +| Maintainability | 66 | initial | +| Complexity | 62 | initial | +| Testability | 76 | initial | +| Accessibility | N/A | initial; evidence absent | +| Composite | 72.3 | initial | +| static graph | 128 nodes / 424 edges | initial | +| dependency depth | 10 | initial | +| SCCs | 1 | initial | +| functions cyclomatic >10 | 88 / 1,306 (6.74%) | initial | +| files >800 lines | 1 | initial | +| duplicate families | 3 | initial | +| suspected-dead surfaces | 1 | initial | + +No score drop, new cycle, watchlist join, or trend direction can be asserted +without a prior run. The next run must reuse the formulas in `scorecard.md` or +explicitly report a formula change. + +Relative to `origin/main`, this hardening branch does not modify `src/**`; it +adds the production verifier under `scripts/`, its tests, delivery workflows, +installers, and operations documentation. The verifier contributes three +functions over the cyclomatic threshold but has 90.69% line coverage and a +100% score across the declared ten-mutant high-risk catalog. diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/duplication-dead-surface.md b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/duplication-dead-surface.md new file mode 100644 index 0000000..0c3f87e --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/duplication-dead-surface.md @@ -0,0 +1,31 @@ +# Duplication and dead-surface analysis + +The duplicate scan used exact ten-meaningful-line windows across 128 `src/*.ts` +files, then grouped overlapping windows and manually confirmed same-runtime +semantics. + +## Duplication + +| ID | family | evidence | classification | route | +|---|---|---|---|---| +| L13-003 | memory event shape | `src/core/brain_protocol.ts:89`, `src/core/stream.ts:35`, `src/ui/status_renderer.ts:21` repeat the same memory-frame fields | confirmed structural duplication | LOOP-01: establish one shared contract without coupling UI upward | +| L13-004 | media flag parsing | `src/commands/media.ts:54` and `src/commands/slash_media.ts:27` duplicate model/aspect/count/4k/vector/duration/1080p/audio/save/ref/open parsing | confirmed same-runtime behavior duplication | LOOP-01: extract a shared pure parser | +| L13-005 | progress rendering | `src/ui/tui_layout.ts:340`, `src/ui/status_renderer.ts:243`, and `src/ui/progress.ts:5` independently implement progress bars | confirmed feature duplication with two intentional visual variants | LOOP-02 or LOOP-06: consolidate calculation while preserving styles | + +The raw scan yielded 16 overlapping windows, all collapsing into the first two +families above; overlapping windows are not counted as separate debt. + +## Dead surface + +| ID | surface | inbound evidence | external-contract check | verdict | +|---|---|---|---|---| +| L13-006 | `src/ui/tui_layout.ts` / `TuiLayout` | zero production static or dynamic callers; constructor references occur only in `test/tui.test.ts` | not exported by `src/index.ts`; package exports exposes only the root API | suspected-dead prototype, MEDIUM; operator confirmation required before deletion | + +Zero-static-inbound command modules such as `commands/mcp.ts`, `audit.ts`, +`doctor.ts`, `memory.ts`, `receipt.ts`, and `config.ts` are **not dead**: `main.ts` +and `commands/slash.ts` load them dynamically. `src/main.ts`, `src/index.ts`, +`core/smoke.ts`, and the public `terminal_session.ts` are entrypoints. No route or +service was asserted dead without inbound and external-contract evidence. + +No separately deployed service declarations exist in this repository, so there +is no dead-service finding. Actual external API traffic remains unknown. diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-1.json b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-1.json new file mode 100644 index 0000000..acf0a95 --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-1.json @@ -0,0 +1,13 @@ +{ + "node": 1, + "status": "complete", + "baseline": "descriptive-first-run", + "artifact": "target-architecture.json", + "graph": {"nodes": 128, "edges": 424, "max_dependency_depth": 10}, + "confidence": { + "score": 0.89, + "evidence": ["architecture docs", "package entrypoints", "structured TypeScript import graph"], + "unknown": ["complete normative layer policy", "production traffic graph"], + "missing_evidence": ["prior LOOP-13 baseline"] + } +} diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-2.json b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-2.json new file mode 100644 index 0000000..804a074 --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-2.json @@ -0,0 +1,16 @@ +{ + "node": 2, + "status": "complete", + "findings": [ + {"id": "L13-001", "type": "circular-dependency", "severity": "MEDIUM", "members": ["src/core/auth.ts", "src/core/transport.ts"], "runtime_cycle": false}, + {"id": "L13-002", "type": "high-coupling-baseline", "severity": "INFO", "top_degree": 42} + ], + "layer_violations": 0, + "microservice_monolith_delta": "initial", + "confidence": { + "score": 0.90, + "evidence": ["424-edge static graph", "Tarjan SCC analysis", "manual dynamic-dispatch check"], + "unknown": ["runtime call graph", "production traffic"], + "missing_evidence": ["prior baseline"] + } +} diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-3.json b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-3.json new file mode 100644 index 0000000..f92a991 --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-3.json @@ -0,0 +1,14 @@ +{ + "node": 3, + "status": "complete", + "duplicate_families": 3, + "dead_confirmed": 0, + "suspected_dead": 1, + "raw_overlapping_windows": 16, + "confidence": { + "score": 0.88, + "evidence": ["exact token-window scan", "manual same-runtime confirmation", "package exports check", "dynamic import check"], + "unknown": ["external deep-import workarounds", "production traffic"], + "missing_evidence": ["runtime coverage map for every exported symbol"] + } +} diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-4.json b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-4.json new file mode 100644 index 0000000..83a2d9e --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-4.json @@ -0,0 +1,20 @@ +{ + "node": 4, + "status": "complete-with-unmeasured-metrics", + "files": 129, + "functions": 1306, + "functions_cyclomatic_over_10": 88, + "functions_cyclomatic_over_10_pct": 6.74, + "files_over_800_lines": 1, + "top_decile_degree_watchlist": 13, + "max_dependency_depth": 10, + "inheritance_depth": 1, + "cognitive_complexity": "unmeasured", + "call_graph_size": "unmeasured", + "confidence": { + "score": 0.90, + "evidence": ["TypeScript AST analysis", "static import graph", "line counts"], + "unknown": ["cognitive complexity", "complete runtime call graph"], + "missing_evidence": ["compatible cognitive-complexity analyzer", "code-graph call data"] + } +} diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-5.json b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-5.json new file mode 100644 index 0000000..77400d4 --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-5.json @@ -0,0 +1,12 @@ +{ + "node": 5, + "status": "complete", + "scores": {"security": 85, "performance": null, "maintainability": 66, "complexity": 62, "testability": 76, "accessibility": null, "composite": 72.3}, + "na_reasons": {"performance": "no current benchmark or upstream performance artifact", "accessibility": "no current accessibility artifact"}, + "confidence": { + "score": 0.87, + "evidence": ["LOOP-07 artifact", "LOOP-12 artifact", "nodes 1-4"], + "unknown": ["repository-admin controls", "npm environment controls", "performance", "accessibility", "cognitive complexity"], + "missing_evidence": ["admin read access", "benchmark artifact", "a11y artifact"] + } +} diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-6.json b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-6.json new file mode 100644 index 0000000..3d3154c --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-6.json @@ -0,0 +1,15 @@ +{ + "node": 6, + "status": "complete", + "baseline_ref": null, + "delta": "initial", + "trend_row_appended": true, + "formula_changed": false, + "high_delta_findings": 0, + "confidence": { + "score": 0.96, + "evidence": ["first trend row", "scorecard formulas", "origin/main comparison"], + "unknown": [], + "missing_evidence": ["prior run"] + } +} diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-7.json b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-7.json new file mode 100644 index 0000000..41cd091 --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/node-7.json @@ -0,0 +1,22 @@ +{ + "node": 7, + "status": "complete", + "reviewer": "Forensic Architect", + "debate": "FREE-MAD", + "rounds": 2, + "attacks": { + "baseline_circularity": "passed after explicit descriptive-first-run labeling", + "dead_verdicts": "passed; only TuiLayout remains suspected-dead with inbound and export evidence", + "score_inflation": "passed; complexity independently recomputes to 62", + "delta_laundering": "passed; initial row and formula lock are explicit", + "unknowns": "passed; dynamic imports, traffic, cognitive complexity, call graph, performance, accessibility, and admin settings are enumerated" + }, + "read_only_check": "No runtime, test, installer, workflow, or documentation implementation file was changed by LOOP-13; writes are loop artifacts and required governance metadata only. Pre-existing working-tree changes belong to LOOP-11/12.", + "verdict": "PASS-WITH-BASELINE-DEBT", + "confidence": { + "score": 0.90, + "evidence": ["independent formula recomputation", "git diff attribution", "dead-surface external-contract check"], + "unknown": ["live traffic", "admin settings", "unmeasured complexity dimensions"], + "missing_evidence": ["prior baseline"] + } +} diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/scorecard.md b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/scorecard.md new file mode 100644 index 0000000..98644c2 --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/scorecard.md @@ -0,0 +1,20 @@ +# Technical-debt scorecard + +Scores are 0-100, where 100 is clean. `N/A` axes are excluded from the +composite. Every formula is fixed here for the next run. + +| axis | score | reproducible formula | evidence | +|---|---:|---|---| +| Security | 85 | `100 - 15 * unresolved_external_HIGH - 25 * open_code_CRITICAL - 10 * open_code_HIGH`; values `1,0,0` | LOOP-07 L07-007 is an operator-deferred HIGH for unreadable branch protection, environment, secret, and scanning settings; audit reports 0 known dependency CVEs and no credential-shaped secret | +| Performance | N/A | no score when no benchmark or upstream performance audit exists | no LOOP performance artifact or regression budget exists | +| Maintainability | 66 | `100 - 8*files_over_800 - 5*SCC - 4*duplicate_families - 4*suspected_dead - 5*missing_normative_architecture`; values `1,1,3,1,1` | nodes 1-4 | +| Complexity | 62 | `100 - round(3 * pct_functions_CC_gt_10) - 8*files_over_800 - 5*SCC - 5*(max_dependency_depth>=10)`; values `6.74,1,1,true` | 88/1,306 functions over 10, one god file, one SCC, depth 10 | +| Testability | 76 | `round(0.55*overall_line_coverage + 0.25*verifier_line_coverage + 0.20*targeted_mutation_score)`; values `60.98,90.69,100` | LOOP-12 coverage and mutation evidence; 695/695 tests passed three times | +| Accessibility | N/A | no score when no current accessibility audit exists | terminal accessibility/WCAG status has no LOOP-02/06 artifact in scope | + +**Composite:** `(85 + 66 + 62 + 76) / 4 = 72.25`, reported as **72.3**. + +Confidence is 0.87. Missing performance/accessibility data is visible as N/A, +not converted into flattering scores. Security confidence is limited by GitHub +repository-admin and npm environment visibility; complexity confidence is +limited by unmeasured cognitive and call-graph metrics. diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/structural-drift.md b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/structural-drift.md new file mode 100644 index 0000000..2eb6a3b --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/structural-drift.md @@ -0,0 +1,33 @@ +# Structural drift findings + +This is the first LOOP-13 run, so the current graph is a **descriptive baseline**. +The table does not claim that current code is an ideal target architecture. + +| ID | type | evidence | severity | baseline delta | suggested cut | +|---|---|---|---|---|---| +| L13-001 | circular dependency | `src/core/transport.ts:7` imports the `TokenStore` type from `auth.ts`; `src/core/auth.ts:24` imports `LOGIN_PATH` and `isCredentialSafeUrl` from `transport.ts` | MEDIUM | initial | Move `TokenStore` to a leaf contract module, or move login route and credential URL policy to a transport-policy leaf | +| L13-002 | high coupling | `src/core/transport.ts` degree 42; `src/ui/theme.ts` 38; `src/commands/chat.ts` 36; `src/core/context.ts` 34 | INFO | initial | Track degree deltas; split only when a feature change would otherwise enlarge these seams | + +The SCC is type-level on the `transport -> auth` edge, so it is not asserted to +be a runtime initialization cycle. It is still a build-time ownership smell and +is routed to LOOP-01. + +No confirmed layer violation was found. The only explicit directional rule in +the available architecture material is that UI must not import commands; the +current static graph has zero `ui -> commands` edges. Existing `core -> ui` and +command-composition edges are retained in the first-run baseline because the +repository intentionally contains rendering integration in core and command +dispatch helpers within commands. + +No microservice-to-monolith delta can be computed on a first run. The repository +is a single published Node package whose external service boundaries are the +hosted Aether API, optional local Ollama, GitHub Actions, and npm. + +## Unaudited surfaces + +- Dynamic import edges are not in the numeric graph. The known CLI/slash dynamic + dispatch sites were checked manually and explain several zero-static-inbound + command modules. +- Runtime traffic and external consumers are unavailable. +- Call-level and shared-state coupling were not available from a code-graph + service, so module imports are the reproducible proxy. diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/target-architecture.json b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/target-architecture.json new file mode 100644 index 0000000..5f6fd70 --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/target-architecture.json @@ -0,0 +1,85 @@ +{ + "schema_version": 1, + "run_id": "2026-07-20T14-19-07-0400", + "target": "DBarr3/aether-agent", + "snapshot_ref": "3aa36f02ff0488367eb673e15e9e624b946694df", + "baseline_kind": "descriptive-first-run", + "sources": [ + "CONTRIBUTING.md", + "README.md", + "docs/PRODUCTION_OPERATIONS.md", + "src/index.ts", + "src/main.ts", + "TypeScript static import and re-export graph" + ], + "modules": [ + { + "name": "entrypoints", + "paths": ["src/main.ts", "src/index.ts"], + "responsibility": "CLI dispatch and public package surface" + }, + { + "name": "commands", + "paths": ["src/commands/**"], + "responsibility": "Top-level and slash-command orchestration" + }, + { + "name": "core", + "paths": ["src/core/**", "src/types.ts", "src/version.ts"], + "responsibility": "Transport, agent behavior, policy, storage, and shared domain logic" + }, + { + "name": "ui", + "paths": ["src/ui/**"], + "responsibility": "Terminal rendering, interaction, and embeddable terminal surfaces" + }, + { + "name": "delivery", + "paths": [".github/**", "install.sh", "install.ps1", "scripts/verify-production.ts"], + "responsibility": "CI, release, package verification, and installation" + } + ], + "declared_boundaries": [ + "src/core/client.ts is the universal chat route shared by every surface", + "src/main.ts is the CLI entry and command dispatcher", + "src/index.ts is the public package export surface", + "commands may orchestrate core and UI modules", + "UI must not import commands", + "release automation publishes only a verified package built from a release tag on main" + ], + "static_graph": { + "language": "TypeScript", + "nodes": 128, + "edges_including_reexports": 424, + "max_dependency_depth": 10, + "strongly_connected_components_over_one_node": 1, + "dynamic_imports": "manually checked at CLI and slash dispatch sites; not included in edge totals" + }, + "god_nodes_top_decile": [ + {"file": "src/core/transport.ts", "fan_in": 40, "fan_out": 2, "degree": 42}, + {"file": "src/ui/theme.ts", "fan_in": 37, "fan_out": 1, "degree": 38}, + {"file": "src/commands/chat.ts", "fan_in": 4, "fan_out": 32, "degree": 36}, + {"file": "src/core/context.ts", "fan_in": 31, "fan_out": 3, "degree": 34}, + {"file": "src/commands/code.ts", "fan_in": 1, "fan_out": 26, "degree": 27}, + {"file": "src/commands/slash.ts", "fan_in": 1, "fan_out": 19, "degree": 20}, + {"file": "src/main.ts", "fan_in": 0, "fan_out": 19, "degree": 19}, + {"file": "src/index.ts", "fan_in": 0, "fan_out": 17, "degree": 17}, + {"file": "src/core/brain_protocol.ts", "fan_in": 14, "fan_out": 0, "degree": 14}, + {"file": "src/core/errors.ts", "fan_in": 14, "fan_out": 0, "degree": 14}, + {"file": "src/core/tool_executor.ts", "fan_in": 10, "fan_out": 4, "degree": 14}, + {"file": "src/commands/mcp.ts", "fan_in": 0, "fan_out": 13, "degree": 13}, + {"file": "src/ui/text.ts", "fan_in": 13, "fan_out": 0, "degree": 13} + ], + "service_boundaries": [ + "GitHub Actions build and release plane", + "npm registry publication plane", + "end-user Node 24+ runtime", + "Aether hosted API boundary", + "optional local Ollama boundary" + ], + "unknown": [ + "No normative architecture ADR enumerates every allowed module edge", + "No production traffic graph exists for external callers", + "Static graph excludes runtime-computed module loading" + ] +} diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/trace.md b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/trace.md new file mode 100644 index 0000000..242e665 --- /dev/null +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/trace.md @@ -0,0 +1,15 @@ +# LOOP-13 trace + +| node | role | action | evidence | result | +|---:|---|---|---|---| +| 1 | architect | Capture descriptive first-run target from docs, entrypoints, and graph | 128 nodes, 424 edges, architecture docs | complete | +| 2 | drift scanner | Analyze boundaries, SCCs, depth, and layer direction | one type-level SCC, zero confirmed layer violations | complete | +| 3 | surface archaeologist | Scan exact ten-line windows, dynamic entries, exports, and test-only callers | three duplicate families, one suspected-dead surface | complete | +| 4 | complexity analyst | Parse function branches, degrees, depth, inheritance, and file size | 1,306 functions, 88 over 10, one >800 file | complete with cognitive/call graph unmeasured | +| 5 | scorer | Apply documented formulas using LOOP-07/12 and node evidence | composite 72.3 over four scored axes | complete | +| 6 | historian | Create first trend row and initial delta | baseline established | complete | +| 7 | Forensic Architect | FREE-MAD attack on circular baseline, dead claims, score inflation, deltas, and unknowns | two rounds | PASS-WITH-BASELINE-DEBT, confidence 0.90 | + +The analysis used no runtime mutations. Temporary analyzer dependencies were +installed outside the repository under the projectless workspace's `work/` +area and are not part of the deliverable. diff --git a/_loopstate/LOOP-13/debt-trend.md b/_loopstate/LOOP-13/debt-trend.md new file mode 100644 index 0000000..51f8cba --- /dev/null +++ b/_loopstate/LOOP-13/debt-trend.md @@ -0,0 +1,8 @@ +# Aether Agent technical-debt trend + +`N/A` axes are excluded from the composite. Formula definitions live in each +run's `scorecard.md`; formula changes must be called out in `delta-report.md`. + +| date | run_id | target | Sec | Perf | Maint | Cplx | Test | A11y | composite | +|---|---|---|---:|---:|---:|---:|---:|---:|---:| +| 2026-07-20 | 2026-07-20T14-19-07-0400 | DBarr3/aether-agent | 85 | N/A | 66 | 62 | 76 | N/A | 72.3 | diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/AUDIT-ARTIFACT.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/AUDIT-ARTIFACT.md new file mode 100644 index 0000000..6ec7da0 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/AUDIT-ARTIFACT.md @@ -0,0 +1,46 @@ +# LOOP-17 Sparring Partners Artifact + +run_id: `2026-07-20T13-58-39-0400` +date: `2026-07-20` +target: production policy, workflows, package, and installers +branch: `loop/devops-production-hardening` +checkpoint commit: `3aa36f0` + +## Verdict: CONVERGED + +Five consecutive breaker rounds produced no novel finding at the configured static production-assurance difficulty. The final-five breaker win-rate is flat at zero, all three novel findings are resolved, and the symptom-fix ratio is 0.00. + +## Confidence block + +```yaml +confidence_block: + confidence: 0.90 + risk: medium + evidence: [eight breaker attempts, production-policy tests, full npm suite, exact-tarball smoke] + unknown: [remote GitHub checks, repository-admin settings, npm-production secret scope] + missing_evidence: [successful pushed workflow runs, admin settings export] +``` + +## Findings + +| id | severity | evidence | description | status | +|---|---|---|---|---| +| L17-001 | HIGH | release workflow trigger and ref path | Manual dispatch/non-main release was insufficiently constrained | RESOLVED | +| L17-002 | HIGH | initially failing shorthand negative test | `- uses:` could evade the action pin policy scanner | RESOLVED | +| L17-003 | HIGH | dry-run-only package verifier | Verified paths did not prove the tarball installed and launched | RESOLVED | + +## Regression tests + +- Event gating, main ancestry, and exact-tarball smoke policy. +- Both YAML `uses` forms and full-SHA enforcement. +- Isolated global install with binary, version, and help checks. + +## Governance row + +`| 2026-07-20 | LOOP-17 | 2026-07-20T13-58-39-0400 | CONVERGED | 1 | 2 | 98 | 0 | 1 | 0 | 0 | 0.90 | 3 findings resolved; final five held |` + +## Recommended next loops + +- LOOP-11 hostile review of the entire branch diff. +- LOOP-12 mutation testing of the production policy. +- LOOP-13 final drift/debt sweep. diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-1-attempt.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-1-attempt.md new file mode 100644 index 0000000..e9c91a2 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-1-attempt.md @@ -0,0 +1,12 @@ +# Breaker round 1 attempt + +| vector | result | evidence | finding | +|---|---|---|---| +| Trigger or manually dispatch publication for a tag whose commit is not on main. | BROKE | The first release draft permitted workflow_dispatch and did not verify tag ancestry. | L17-001 | + +confidence_block: + confidence: 0.94 + risk: high + evidence: [manual-read, regression-test, command-output] + unknown: [remote enforcement until pushed] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-1-candidates.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-1-candidates.md new file mode 100644 index 0000000..cfd9d38 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-1-candidates.md @@ -0,0 +1,10 @@ +# Breaker round 1 candidates + +Primary vector: Trigger or manually dispatch publication for a tag whose commit is not on main. + +confidence_block: + confidence: 0.90 + risk: high + evidence: [static-analysis, policy-tests] + unknown: [remote GitHub administration state] + missing_evidence: [remote workflow execution] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-2-attempt.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-2-attempt.md new file mode 100644 index 0000000..2457dda --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-2-attempt.md @@ -0,0 +1,12 @@ +# Breaker round 2 attempt + +| vector | result | evidence | finding | +|---|---|---|---| +| Hide a floating action behind YAML shorthand (`- uses:`) so the policy scanner misses it. | BROKE | The initial regression test failed because the scanner recognized only a standalone uses key. | L17-002 | + +confidence_block: + confidence: 0.94 + risk: high + evidence: [manual-read, regression-test, command-output] + unknown: [remote enforcement until pushed] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-2-candidates.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-2-candidates.md new file mode 100644 index 0000000..13ef4f6 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-2-candidates.md @@ -0,0 +1,10 @@ +# Breaker round 2 candidates + +Primary vector: Hide a floating action behind YAML shorthand (`- uses:`) so the policy scanner misses it. + +confidence_block: + confidence: 0.90 + risk: high + evidence: [static-analysis, policy-tests] + unknown: [remote GitHub administration state] + missing_evidence: [remote workflow execution] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-3-attempt.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-3-attempt.md new file mode 100644 index 0000000..d50bd5d --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-3-attempt.md @@ -0,0 +1,12 @@ +# Breaker round 3 attempt + +| vector | result | evidence | finding | +|---|---|---|---| +| Ship an allowlisted tarball that cannot be installed or invoked, because only package paths were inspected. | BROKE | The first preflight used npm pack --dry-run but never installed and launched the packed CLI. | L17-003 | + +confidence_block: + confidence: 0.94 + risk: high + evidence: [manual-read, regression-test, command-output] + unknown: [remote enforcement until pushed] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-3-candidates.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-3-candidates.md new file mode 100644 index 0000000..1293aa1 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-3-candidates.md @@ -0,0 +1,10 @@ +# Breaker round 3 candidates + +Primary vector: Ship an allowlisted tarball that cannot be installed or invoked, because only package paths were inspected. + +confidence_block: + confidence: 0.90 + risk: high + evidence: [static-analysis, policy-tests] + unknown: [remote GitHub administration state] + missing_evidence: [remote workflow execution] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-4-attempt.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-4-attempt.md new file mode 100644 index 0000000..a885871 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-4-attempt.md @@ -0,0 +1,12 @@ +# Breaker round 4 attempt + +| vector | result | evidence | finding | +|---|---|---|---| +| Reintroduce a floating GitHub Action or persisted checkout credential. | HELD | All external actions are full 40-character SHAs; checkout persist-credentials is false; policy tests reject floating refs. | none | + +confidence_block: + confidence: 0.89 + risk: medium + evidence: [manual-read, regression-test, command-output] + unknown: [remote enforcement until pushed] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-4-candidates.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-4-candidates.md new file mode 100644 index 0000000..cd10d92 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-4-candidates.md @@ -0,0 +1,10 @@ +# Breaker round 4 candidates + +Primary vector: Reintroduce a floating GitHub Action or persisted checkout credential. + +confidence_block: + confidence: 0.90 + risk: medium + evidence: [static-analysis, policy-tests] + unknown: [remote GitHub administration state] + missing_evidence: [remote workflow execution] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-5-attempt.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-5-attempt.md new file mode 100644 index 0000000..c96c50e --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-5-attempt.md @@ -0,0 +1,12 @@ +# Breaker round 5 attempt + +| vector | result | evidence | finding | +|---|---|---|---| +| Publish a tag that mismatches package.json or points outside main. | HELD | verify:production binds v; release workflow checks git ancestry to origin/main. | none | + +confidence_block: + confidence: 0.89 + risk: medium + evidence: [manual-read, regression-test, command-output] + unknown: [remote enforcement until pushed] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-5-candidates.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-5-candidates.md new file mode 100644 index 0000000..125ff05 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-5-candidates.md @@ -0,0 +1,10 @@ +# Breaker round 5 candidates + +Primary vector: Publish a tag that mismatches package.json or points outside main. + +confidence_block: + confidence: 0.90 + risk: medium + evidence: [static-analysis, policy-tests] + unknown: [remote GitHub administration state] + missing_evidence: [remote workflow execution] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-6-attempt.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-6-attempt.md new file mode 100644 index 0000000..90c3e29 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-6-attempt.md @@ -0,0 +1,12 @@ +# Breaker round 6 attempt + +| vector | result | evidence | finding | +|---|---|---|---| +| Execute a compromised dependency lifecycle script during CI, release, or global installation. | HELD | Every npm ci/install path uses --ignore-scripts and the package contract forbids runtime dependencies. | none | + +confidence_block: + confidence: 0.89 + risk: medium + evidence: [manual-read, regression-test, command-output] + unknown: [remote enforcement until pushed] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-6-candidates.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-6-candidates.md new file mode 100644 index 0000000..a0896f9 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-6-candidates.md @@ -0,0 +1,10 @@ +# Breaker round 6 candidates + +Primary vector: Execute a compromised dependency lifecycle script during CI, release, or global installation. + +confidence_block: + confidence: 0.90 + risk: medium + evidence: [static-analysis, policy-tests] + unknown: [remote GitHub administration state] + missing_evidence: [remote workflow execution] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-7-attempt.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-7-attempt.md new file mode 100644 index 0000000..7d45a5f --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-7-attempt.md @@ -0,0 +1,12 @@ +# Breaker round 7 attempt + +| vector | result | evidence | finding | +|---|---|---|---| +| Leak tests, loop artifacts, or environment files into the public npm tarball. | HELD | Package allowlist accepts only required root docs and dist/src; negative fixtures for dist/test and .env are killed. | none | + +confidence_block: + confidence: 0.89 + risk: medium + evidence: [manual-read, regression-test, command-output] + unknown: [remote enforcement until pushed] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-7-candidates.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-7-candidates.md new file mode 100644 index 0000000..00db937 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-7-candidates.md @@ -0,0 +1,10 @@ +# Breaker round 7 candidates + +Primary vector: Leak tests, loop artifacts, or environment files into the public npm tarball. + +confidence_block: + confidence: 0.90 + risk: medium + evidence: [static-analysis, policy-tests] + unknown: [remote GitHub administration state] + missing_evidence: [remote workflow execution] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-8-attempt.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-8-attempt.md new file mode 100644 index 0000000..b78b6c4 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-8-attempt.md @@ -0,0 +1,12 @@ +# Breaker round 8 attempt + +| vector | result | evidence | finding | +|---|---|---|---| +| Use a network-to-shell installer or inject shell syntax through the requested release version. | HELD | README requires download-and-inspect; both installers reject unsafe version characters, quote the package spec, and preserve npm's exit code. | none | + +confidence_block: + confidence: 0.89 + risk: medium + evidence: [manual-read, regression-test, command-output] + unknown: [remote enforcement until pushed] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-8-candidates.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-8-candidates.md new file mode 100644 index 0000000..9bbd6fa --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/breaker-round-8-candidates.md @@ -0,0 +1,10 @@ +# Breaker round 8 candidates + +Primary vector: Use a network-to-shell installer or inject shell syntax through the requested release version. + +confidence_block: + confidence: 0.90 + risk: medium + evidence: [static-analysis, policy-tests] + unknown: [remote GitHub administration state] + missing_evidence: [remote workflow execution] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-1-fix.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-1-fix.md new file mode 100644 index 0000000..de2c5f5 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-1-fix.md @@ -0,0 +1,13 @@ +# Builder round 1 fix + +Removed manual publication dispatch, fetched full history, and required git merge-base --is-ancestor HEAD origin/main. + +Verification: publishing workflow must be event-gated, main-derived, and install-smoked. +Commit: `3aa36f0` (composite LOOP-07 remediation checkpoint). + +confidence_block: + confidence: 0.92 + risk: medium + evidence: [regression-test, npm-test, production-preflight] + unknown: [remote workflow execution] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-1-intake.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-1-intake.md new file mode 100644 index 0000000..41d37fd --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-1-intake.md @@ -0,0 +1,5 @@ +# Builder round 1 intake + +Finding: L17-001 +Priority: HIGH +Classification: novel production-assurance gap, not a variant of an earlier fix. diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-2-fix.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-2-fix.md new file mode 100644 index 0000000..38178df --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-2-fix.md @@ -0,0 +1,13 @@ +# Builder round 2 fix + +Extended the structural workflow scan to cover both standalone and list-item uses syntax. + +Verification: workflow policy rejects floating actions and unbounded jobs. +Commit: `3aa36f0` (composite LOOP-07 remediation checkpoint). + +confidence_block: + confidence: 0.92 + risk: medium + evidence: [regression-test, npm-test, production-preflight] + unknown: [remote workflow execution] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-2-intake.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-2-intake.md new file mode 100644 index 0000000..08eb40e --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-2-intake.md @@ -0,0 +1,5 @@ +# Builder round 2 intake + +Finding: L17-002 +Priority: HIGH +Classification: novel production-assurance gap, not a variant of an earlier fix. diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-3-fix.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-3-fix.md new file mode 100644 index 0000000..3ae99a5 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-3-fix.md @@ -0,0 +1,13 @@ +# Builder round 3 fix + +Added isolated exact-tarball global installation, binary existence, version, and help smoke checks locally and in the release job. + +Verification: npm run verify:production exact-package smoke. +Commit: `3aa36f0` (composite LOOP-07 remediation checkpoint). + +confidence_block: + confidence: 0.92 + risk: medium + evidence: [regression-test, npm-test, production-preflight] + unknown: [remote workflow execution] + missing_evidence: [successful GitHub checks] diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-3-intake.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-3-intake.md new file mode 100644 index 0000000..3659307 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/builder-round-3-intake.md @@ -0,0 +1,5 @@ +# Builder round 3 intake + +Finding: L17-003 +Priority: HIGH +Classification: novel production-assurance gap, not a variant of an earlier fix. diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-1.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-1.md new file mode 100644 index 0000000..00227bb --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-1.md @@ -0,0 +1,9 @@ +# Convergence round 1 + +- verdict: CONTINUE +- novel finding this round: yes +- consecutive no-new-finding streak: 0 +- cumulative findings: 1 +- cumulative resolved: 1 +- symptom-fix ratio: 0.00 +- breaker branch edits: 0 diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-2.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-2.md new file mode 100644 index 0000000..b92d6eb --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-2.md @@ -0,0 +1,9 @@ +# Convergence round 2 + +- verdict: CONTINUE +- novel finding this round: yes +- consecutive no-new-finding streak: 0 +- cumulative findings: 2 +- cumulative resolved: 2 +- symptom-fix ratio: 0.00 +- breaker branch edits: 0 diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-3.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-3.md new file mode 100644 index 0000000..7e247cd --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-3.md @@ -0,0 +1,9 @@ +# Convergence round 3 + +- verdict: CONTINUE +- novel finding this round: yes +- consecutive no-new-finding streak: 0 +- cumulative findings: 3 +- cumulative resolved: 3 +- symptom-fix ratio: 0.00 +- breaker branch edits: 0 diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-4.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-4.md new file mode 100644 index 0000000..5a02577 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-4.md @@ -0,0 +1,9 @@ +# Convergence round 4 + +- verdict: CONTINUE +- novel finding this round: no +- consecutive no-new-finding streak: 1 +- cumulative findings: 3 +- cumulative resolved: 3 +- symptom-fix ratio: 0.00 +- breaker branch edits: 0 diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-5.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-5.md new file mode 100644 index 0000000..5ad7e93 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-5.md @@ -0,0 +1,9 @@ +# Convergence round 5 + +- verdict: CONTINUE +- novel finding this round: no +- consecutive no-new-finding streak: 2 +- cumulative findings: 3 +- cumulative resolved: 3 +- symptom-fix ratio: 0.00 +- breaker branch edits: 0 diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-6.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-6.md new file mode 100644 index 0000000..0708f34 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-6.md @@ -0,0 +1,9 @@ +# Convergence round 6 + +- verdict: CONTINUE +- novel finding this round: no +- consecutive no-new-finding streak: 3 +- cumulative findings: 3 +- cumulative resolved: 3 +- symptom-fix ratio: 0.00 +- breaker branch edits: 0 diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-7.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-7.md new file mode 100644 index 0000000..8eb1bec --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-7.md @@ -0,0 +1,9 @@ +# Convergence round 7 + +- verdict: CONTINUE +- novel finding this round: no +- consecutive no-new-finding streak: 4 +- cumulative findings: 3 +- cumulative resolved: 3 +- symptom-fix ratio: 0.00 +- breaker branch edits: 0 diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-8.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-8.md new file mode 100644 index 0000000..f5ebbb9 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/convergence-round-8.md @@ -0,0 +1,9 @@ +# Convergence round 8 + +- verdict: CONVERGED +- novel finding this round: no +- consecutive no-new-finding streak: 5 +- cumulative findings: 3 +- cumulative resolved: 3 +- symptom-fix ratio: 0.00 +- breaker branch edits: 0 diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/human-report.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/human-report.md new file mode 100644 index 0000000..209d872 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/human-report.md @@ -0,0 +1,13 @@ +# LOOP-17 human report + +- Rounds: 8 / 20 budget +- Breaker wins: 3, then 5 consecutive held rounds +- Open queue entries: 0 +- Resolved queue entries: 3 +- Breaker win-rate over final five rounds: 0, 0, 0, 0, 0 +- Symptom-fix ratio: 0.00 +- Root-cause fixes: 3 / 3 +- Breaker branch edits: 0 (breaker artifacts were read-only; builder owned mutations) +- Verdict: CONVERGED at the configured five-round no-new-finding threshold + +Residual external unknowns are repository-admin controls and remote workflow results; they are not silent passes and remain in LOOP-07 finding L07-007. diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/queue.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/queue.md new file mode 100644 index 0000000..8d9f2c6 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/queue.md @@ -0,0 +1,17 @@ +# LOOP-17 append-only findings/fix queue + +| timestamp | author | entry | severity | class | status | evidence | +|---|---|---|---|---|---|---| +| 2026-07-20T13:58:40-04:00 | breaker | L17-001 release dispatch/main-ancestry bypass | HIGH | novel | OPEN | initial release workflow | +| 2026-07-20T13:59:10-04:00 | builder | L17-001 root-cause fix | HIGH | novel | RESOLVED at `3aa36f0` | ancestry check + event-only trigger + regression policy | +| 2026-07-20T13:59:30-04:00 | breaker | L17-002 YAML shorthand bypasses action pin scanner | HIGH | novel | OPEN | failing production-hardening test | +| 2026-07-20T14:00:00-04:00 | builder | L17-002 parser coverage fix | HIGH | novel | RESOLVED at `3aa36f0` | shorthand negative test passes | +| 2026-07-20T14:00:20-04:00 | breaker | L17-003 packed artifact was not install-smoked | HIGH | novel | OPEN | dry-run-only verifier path | +| 2026-07-20T14:01:00-04:00 | builder | L17-003 exact-tarball smoke | HIGH | novel | RESOLVED at `3aa36f0` | isolated install, version, help checks | +| 2026-07-20T14:01:20-04:00 | breaker | round 4 held | - | no finding | CLOSED | pinned actions and non-persisted credentials | +| 2026-07-20T14:01:40-04:00 | breaker | round 5 held | - | no finding | CLOSED | version/tag and main ancestry gates | +| 2026-07-20T14:02:00-04:00 | breaker | round 6 held | - | no finding | CLOSED | lifecycle scripts disabled | +| 2026-07-20T14:02:20-04:00 | breaker | round 7 held | - | no finding | CLOSED | package allowlist | +| 2026-07-20T14:02:40-04:00 | breaker | round 8 held | - | no finding | CLOSED | safe installer path | + +Corrections, if any, must be appended below; prior rows are immutable. diff --git a/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/trace.md b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/trace.md new file mode 100644 index 0000000..576b9a7 --- /dev/null +++ b/_loopstate/LOOP-17/2026-07-20T13-58-39-0400/trace.md @@ -0,0 +1,7 @@ +# Invocation trace + +| node/track | role | reasoning summary | tools used | duration | confidence | result | +|---|---|---|---|---|---:|---| +| rounds 1-8 | breaker | Attack release authorization, workflow parser coverage, artifact executability, dependency execution, and installer injection. | read, static scan, tests | bounded session | 0.90 | 3 BROKE / 5 HELD | +| rounds 1-3 | builder | Fix root causes and add permanent negative regression coverage. | edit, npm test, package smoke | bounded session | 0.92 | PASS | +| convergence 1-8 | referee | Reclassify novel vs variant, verify branch isolation and final-zero trend. | queue analysis | bounded session | 0.90 | CONVERGED | diff --git a/_loopstate/governance-ledger.md b/_loopstate/governance-ledger.md index 5236524..44e3dc9 100644 --- a/_loopstate/governance-ledger.md +++ b/_loopstate/governance-ledger.md @@ -3,3 +3,7 @@ | date | loop | run_id | verdict | retries | debate_rounds | tool_success_pct | human_overrides | hallucinations_caught | false_positive_rate | rollbacks | confidence | note | |---|---|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---| | 2026-07-20 | LOOP-07 | 2026-07-20T13-53-25-0400 | FAIL-WITH-ARTIFACT | 0 | 2 | 96 | 0 | 0 | 0 | 0 | 0.88 | Code ready; repository-admin and npm controls unverified | +| 2026-07-20 | LOOP-17 | 2026-07-20T13-58-39-0400 | CONVERGED | 1 | 2 | 98 | 0 | 1 | 0 | 0 | 0.90 | Three findings resolved; final five breaker rounds held | +| 2026-07-20 | LOOP-11 | 2026-07-20T14-04-43-0400 | PASS | 1 | 2 | 99 | 0 | 2 | 0 | 0 | 0.90 | FREE-MAD score improved from 74 to 92 | +| 2026-07-20 | LOOP-12 | 2026-07-20T14-12-13-0400 | PASS-NO-CHAOS | 0 | 2 | 100 | 0 | 0 | 0 | 0 | 0.93 | 90.69 line coverage, 100 mutation score, 695 tests x3 | +| 2026-07-20 | LOOP-13 | 2026-07-20T14-19-07-0400 | PASS-WITH-BASELINE-DEBT | 0 | 2 | 97 | 0 | 1 | 0 | 0 | 0.90 | First descriptive baseline; composite 72.3; two axes N/A | diff --git a/docs/specs/2026-07-20-devops-production-hardening-design.md b/docs/specs/2026-07-20-devops-production-hardening-design.md index cd05f2d..4c7aa10 100644 --- a/docs/specs/2026-07-20-devops-production-hardening-design.md +++ b/docs/specs/2026-07-20-devops-production-hardening-design.md @@ -1,7 +1,7 @@ # Spec: DevOps / Production Hardening Loop **Branch:** `loop/devops-production-hardening` -**Status:** Draft — assigned to employee, PR opened as starting point +**Status:** Implemented and locally verified; repository-admin controls remain operator-deferred **Date:** 2026-07-20 ## Purpose @@ -13,7 +13,7 @@ branch (`fix/terminal-auth-401-ux`) and the TS7 upgrade branch ## Loops to run (in order) -Source: `C:\Users\lilbe\Documents\GitHub\agentic-loops\skills\` +Source: [`DBarr3/agentic-loops`](https://github.com/DBarr3/agentic-loops/tree/main/skills) 1. **LOOP-07-infra-devops.md** — infra/devops hardening pass (deploy config, CI, env handling, observability). @@ -35,5 +35,31 @@ Source: `C:\Users\lilbe\Documents\GitHub\agentic-loops\skills\` ## Notes -Minimal spec intentionally — this is the starting point for the loop run, -not the full write-up. Expand with findings per loop stage. +This file began as the starting point and now records the completed run. + +## Run results + +| loop | run id | verdict | result | +|---|---|---|---| +| LOOP-07 | `2026-07-20T13-53-25-0400` | FAIL-WITH-ARTIFACT | Hardened CI, CodeQL, release provenance, artifact attestation, package verification, installers, dependency updates, and production operations. The code is ready; GitHub/npm administrator controls are not readable with the available credential. | +| LOOP-17 | `2026-07-20T13-58-39-0400` | CONVERGED | Resolved three breaker findings: manual/non-main release bypass, YAML block-scalar verification bypass, and lack of exact-tarball install smoke. Five consecutive no-new-finding rounds held. | +| LOOP-11 | `2026-07-20T14-04-43-0400` | PASS | FREE-MAD score improved from 74 to 92 after closing quoted/npx installer and multiline workflow-checker bypasses plus exact-version message drift. | +| LOOP-12 | `2026-07-20T14-12-13-0400` | PASS-NO-CHAOS | Scoped verifier coverage is 90.69%; all 10 declared high-risk mutants were killed; the 695-test suite passed three consecutive runs. Chaos was correctly skipped because no named sandbox or explicit destructive-test approval was provided. | +| LOOP-13 | `2026-07-20T14-19-07-0400` | PASS-WITH-BASELINE-DEBT | Established the first drift baseline: one pre-existing type-level SCC, three duplication families, one suspected-dead prototype, and one >800-line source file. Composite debt score is 72.3 across four evidenced axes. | + +## Deliverables + +- Cross-platform CI on Node 24 with immutable action pins, read-only default permissions, clean-install lifecycle suppression, timeouts, tests, production verification, audit, and SBOM evidence. +- Weekly and pull-request CodeQL analysis. +- Release-only npm publication from an exact tag on `main`, protected by an npm production environment, OIDC provenance, build attestation, exact tarball install smoke, and retained evidence. +- Safe version-selectable Bash and PowerShell installers that never pipe remote code to a shell and suppress package lifecycle scripts. +- A production verifier enforcing manifest, package allowlist/size, workflow, release, installer, and installed-CLI invariants. +- Operations, rollback, backup, observability, ownership, and required-administrator-control documentation. +- Full loop artifacts and governance trail under `_loopstate/LOOP-07`, `LOOP-17`, `LOOP-11`, `LOOP-12`, and `LOOP-13`. + +## Remaining operator gate + +An administrator must verify or configure branch protection and required checks, +the `npm-production` environment and reviewers, least-scope `NPM_TOKEN`, secret +scanning/push protection, Dependabot alerts, and the private backup mirror. No +live infrastructure or repository-admin mutation was attempted by these loops. diff --git a/install.ps1 b/install.ps1 index 6716896..aaa60b6 100644 --- a/install.ps1 +++ b/install.ps1 @@ -118,7 +118,7 @@ $alreadyInstalled = $false $aetherPath = Get-Command aether -ErrorAction SilentlyContinue if ($aetherPath) { $alreadyInstalled = $true - Write-Info "aether-agent already installed — will update to latest." + Write-Info "aether-agent already installed — will install $Version." } # ── Install ── diff --git a/install.sh b/install.sh index 45c6b82..d449246 100644 --- a/install.sh +++ b/install.sh @@ -99,7 +99,7 @@ ALREADY="" if command -v aether >/dev/null 2>&1; then AETH_V=$(aether --version 2>/dev/null || echo "unknown") ALREADY="yes" - info "aether-agent ${AETH_V} already installed — will update to latest." + info "aether-agent ${AETH_V} already installed — will install ${AETHER_VERSION}." fi # ── Install ── diff --git a/scripts/verify-production.ts b/scripts/verify-production.ts index 4dc31a6..981e636 100644 --- a/scripts/verify-production.ts +++ b/scripts/verify-production.ts @@ -126,8 +126,10 @@ export function validateWorkflowText(name: string, text: string): string[] { const timeouts = (text.match(/^\s+timeout-minutes:/gm) ?? []).length; if (jobs !== timeouts) errors.push(`${name}: every runner job must set timeout-minutes`); - for (const match of text.matchAll(/^\s*run:\s*(npm ci[^\n]*)$/gm)) { - if (!match[1]!.includes("--ignore-scripts")) errors.push(`${name}: npm ci must use --ignore-scripts`); + for (const line of text.split(/\r?\n/)) { + if (/\bnpm ci(?:\s|$)/.test(line) && !line.trimStart().startsWith("#") && !line.includes("--ignore-scripts")) { + errors.push(`${name}: npm ci must use --ignore-scripts`); + } } if (/npm publish/.test(text)) { @@ -153,8 +155,12 @@ export function validateInstallerText(name: string, text: string): string[] { if (/curl[^\n|]*\|\s*(?:sh|bash)\b/i.test(text) || /\birm\b[^\n|]*\|\s*iex\b/i.test(text)) { errors.push(`${name}: pipe-to-shell installation is forbidden`); } - if (/npm\s+install\s+-g\s+aether-agents/.test(text) && !/--ignore-scripts/.test(text)) { - errors.push(`${name}: global npm installation must disable lifecycle scripts`); + for (const line of text.split(/\r?\n/)) { + const globalInstall = /\bnpm\s+(?:install|i)\s+(?:--global|-g)\s+["']?aether-agents/.test(line); + const oneShot = /\bnpx\b[^\n]*\baether-agents/.test(line); + if ((globalInstall || oneShot) && !line.includes("--ignore-scripts")) { + errors.push(`${name}: npm execution must disable lifecycle scripts`); + } } if (/PIPESTATUS/.test(text)) errors.push(`${name}: bash-only PIPESTATUS is forbidden in the POSIX installer`); return errors; diff --git a/test/production_hardening.test.ts b/test/production_hardening.test.ts index 2236fcd..b51fa55 100644 --- a/test/production_hardening.test.ts +++ b/test/production_hardening.test.ts @@ -7,6 +7,7 @@ import { validateManifest, validatePack, validateWorkflowText, + verifyProduction, type PackReport, } from "../scripts/verify-production.js"; import { DEFAULT_CONFIG } from "../src/core/config.js"; @@ -67,6 +68,8 @@ test("workflow policy rejects floating actions and unbounded jobs", () => { const errors = validateWorkflowText("invalid.yml", invalid).join("\n"); assert.match(errors, /not pinned/); assert.match(errors, /timeout-minutes/); + const blockScalar = valid.replace(" - run: npm ci --ignore-scripts", " - run: |\n npm ci"); + assert.match(validateWorkflowText("block.yml", blockScalar).join("\n"), /ignore-scripts/); }); test("publishing workflow must be event-gated, main-derived, and install-smoked", () => { @@ -80,6 +83,8 @@ test("publishing workflow must be event-gated, main-derived, and install-smoked" test("installer policy rejects pipe-to-shell and lifecycle-enabled global installs", () => { assert.deepEqual(validateInstallerText("safe.sh", "npm install -g aether-agents@latest --ignore-scripts"), []); assert.match(validateInstallerText("bad.sh", "curl https://example.test/install.sh | sh\nnpm install -g aether-agents").join("\n"), /pipe-to-shell/); + assert.match(validateInstallerText("quoted.sh", "npm install -g \"aether-agents@latest\"").join("\n"), /lifecycle scripts/); + assert.match(validateInstallerText("readme.md", "npx aether-agents").join("\n"), /lifecycle scripts/); }); test("checked-in workflows and installers satisfy the production policy", () => { @@ -98,3 +103,12 @@ test("operator-facing API defaults match the production transport path", () => { assert.match(readFileSync(".env.example", "utf8"), /AETHER_BASE_URL=https:\/\/api\.aethersystems\.net\/cloud/); assert.match(readFileSync("COMMANDS.md", "utf8"), /`https:\/\/api\.aethersystems\.net\/cloud`/); }); + +test("production verifier installs and launches the exact packed CLI", { timeout: 60_000 }, () => { + const result = verifyProduction(process.cwd(), "v0.1.0"); + assert.equal(result.package, "aether-agents"); + assert.equal(result.version, "0.1.0"); + assert.equal(result.workflows, 3); + assert.ok(result.packedFiles > 0); + assert.ok(result.packedBytes > 0); +}); From 98b7b0a5a20cdfc625df03400272d4cbf1eeaf81 Mon Sep 17 00:00:00 2001 From: dbarrante Date: Mon, 20 Jul 2026 14:35:20 -0400 Subject: [PATCH 4/4] fix(ci): handle Windows workspace path aliases Keep lexical traversal and canonical link checks on matching path representations, and cover aliased workspace roots. --- .../hosted-ci-repair.md | 18 +++++++++++++++++ .../LOOP-07/2026-07-20T13-53-25-0400/trace.md | 1 + .../AUDIT-ARTIFACT.md | 9 ++++++--- .../2026-07-20T14-19-07-0400/delta-report.md | 14 ++++++++----- ...7-20-devops-production-hardening-design.md | 1 + src/core/workspace_scope.ts | 9 +++++++-- test/workspace_scope.test.ts | 20 +++++++++++++++++++ 7 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 _loopstate/LOOP-07/2026-07-20T13-53-25-0400/hosted-ci-repair.md diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/hosted-ci-repair.md b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/hosted-ci-repair.md new file mode 100644 index 0000000..8424ff4 --- /dev/null +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/hosted-ci-repair.md @@ -0,0 +1,18 @@ +# Hosted Windows CI repair + +After the loop branch was pushed, GitHub Actions run `29767917285` exposed one +Windows-only failure in `context_registry_scope.test.ts`. The hosted runner's +temporary directory could be represented through an alias whose real path did +not string-match the alias-form pinned file. `confineToWorkspace` compared a +canonical root to that lexical candidate and rejected the valid file as outside +the workspace. + +The repair keeps two independent security checks on matching representations: + +1. lexical candidate against lexical root, preserving `..` escape rejection; +2. canonical candidate against canonical root, preserving symlink/junction + escape rejection. + +`workspace_scope.test.ts` now covers an aliased workspace root. The original +lexical and link-escape regression remains green. This is a portability repair +required by the new Windows matrix, not a relaxation of the workspace boundary. diff --git a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/trace.md b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/trace.md index cd86576..bbf955d 100644 --- a/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/trace.md +++ b/_loopstate/LOOP-07/2026-07-20T13-53-25-0400/trace.md @@ -8,3 +8,4 @@ | 8 | supply-chain reviewer | Audit dependencies, package contents, action pins, SBOM and release provenance. | npm audit, npm pack, npm sbom | bounded | 0.94 | PASS | | 9 | operations reviewer | Classify state, backup ownership, evidence retention, and recovery gaps. | docs, inventory | bounded | 0.85 | OPERATOR-DEFERRED | | final | hostile SRE | Attack unknowns first and reject a silent pass on unreadable admin controls. | artifact review | bounded | 0.88 | FAIL-WITH-ARTIFACT | +| hosted follow-up | CI repair | Diagnose Windows runner alias failure; separate lexical and canonical containment representations; add junction-alias regression. | Actions run 29767917285; targeted 5/5 | bounded | 0.95 | repaired and repushed | diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/AUDIT-ARTIFACT.md b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/AUDIT-ARTIFACT.md index 031e019..da7a301 100644 --- a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/AUDIT-ARTIFACT.md +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/AUDIT-ARTIFACT.md @@ -8,8 +8,10 @@ The first drift/debt run established a reproducible descriptive baseline for 128 source modules and 424 static import/re-export edges. It found one pre-existing type-level circular dependency, three duplication families, one suspected-dead internal TUI prototype, 88 of 1,306 functions above cyclomatic -10, and one file above the 800-line house limit. The production-hardening branch -does not modify `src/**`, so none of those source findings were introduced here. +10, and one file above the 800-line house limit. None of those source findings +were introduced by the hardening work. A later hosted-CI follow-up touched only +the containment logic in `workspace_scope.ts` and did not alter the measured +graph or complexity aggregates. ## Scorecard @@ -59,7 +61,8 @@ recomputation. LOOP-13 changed no runtime, test, installer, workflow, or implementation documentation file. Its writes are under `_loopstate/LOOP-13/` plus the required governance metadata. Runtime changes already present in the working tree were -created and tested by the preceding LOOP-11/12 stages. +created and tested by the preceding LOOP-11/12 stages. The hosted Windows repair +was applied only after this read-only run completed. ## Recommended next loops diff --git a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/delta-report.md b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/delta-report.md index a5e51df..72b5730 100644 --- a/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/delta-report.md +++ b/_loopstate/LOOP-13/2026-07-20T14-19-07-0400/delta-report.md @@ -24,8 +24,12 @@ No score drop, new cycle, watchlist join, or trend direction can be asserted without a prior run. The next run must reuse the formulas in `scorecard.md` or explicitly report a formula change. -Relative to `origin/main`, this hardening branch does not modify `src/**`; it -adds the production verifier under `scripts/`, its tests, delivery workflows, -installers, and operations documentation. The verifier contributes three -functions over the cyclomatic threshold but has 90.69% line coverage and a -100% score across the declared ten-mutant high-risk catalog. +At the LOOP-13 snapshot, the hardening branch did not modify `src/**`; it added +the production verifier under `scripts/`, its tests, delivery workflows, +installers, and operations documentation. A later hosted-CI follow-up made one +targeted portability fix in `src/core/workspace_scope.ts` after Windows exposed +an alias-vs-realpath mismatch. That follow-up adds no import edge or function and +does not change this baseline's cycle, degree, depth, duplication, or complexity +aggregates. The verifier contributes three functions over the cyclomatic +threshold but has 90.69% line coverage and a 100% score across the declared +ten-mutant high-risk catalog. diff --git a/docs/specs/2026-07-20-devops-production-hardening-design.md b/docs/specs/2026-07-20-devops-production-hardening-design.md index 4c7aa10..0574c9f 100644 --- a/docs/specs/2026-07-20-devops-production-hardening-design.md +++ b/docs/specs/2026-07-20-devops-production-hardening-design.md @@ -54,6 +54,7 @@ This file began as the starting point and now records the completed run. - Release-only npm publication from an exact tag on `main`, protected by an npm production environment, OIDC provenance, build attestation, exact tarball install smoke, and retained evidence. - Safe version-selectable Bash and PowerShell installers that never pipe remote code to a shell and suppress package lifecycle scripts. - A production verifier enforcing manifest, package allowlist/size, workflow, release, installer, and installed-CLI invariants. +- A Windows portability repair for workspace containment when runner temp paths use an alias; lexical traversal and canonical symlink guards remain independently enforced. - Operations, rollback, backup, observability, ownership, and required-administrator-control documentation. - Full loop artifacts and governance trail under `_loopstate/LOOP-07`, `LOOP-17`, `LOOP-11`, `LOOP-12`, and `LOOP-13`. diff --git a/src/core/workspace_scope.ts b/src/core/workspace_scope.ts index f34fef9..1dd035b 100644 --- a/src/core/workspace_scope.ts +++ b/src/core/workspace_scope.ts @@ -59,9 +59,14 @@ function canonicalizeCandidate(candidate: string): string { } export function confineToWorkspace(cwd: string, candidate: string, mustExist = false): string { + // Keep the lexical and canonical checks on matching path representations. + // Windows runners can expose the temp directory through an alias whose + // realpath differs from the path returned by os.tmpdir(). Comparing that + // alias-form candidate to a canonical root rejects a valid in-workspace file. + const lexicalRoot = fold(resolve(cwd).replace(/[\\/]+$/, "") || sep); + const lexical = fold(resolve(lexicalRoot, candidate)); + if (!inside(lexicalRoot, lexical)) throw new Error("path escapes workspace"); const root = normalizeWorkspace(cwd); - const lexical = fold(resolve(root, candidate)); - if (!inside(root, lexical)) throw new Error("path escapes workspace"); const canonical = canonicalizeCandidate(lexical); if (!inside(root, canonical)) throw new Error("path escapes workspace through a link"); if (mustExist && (!existsSync(canonical) || !statSync(canonical).isFile())) { diff --git a/test/workspace_scope.test.ts b/test/workspace_scope.test.ts index dd13399..1c465fd 100644 --- a/test/workspace_scope.test.ts +++ b/test/workspace_scope.test.ts @@ -58,3 +58,23 @@ test("confined paths reject lexical and symlink escapes", (t) => { rmSync(outside, { recursive: true, force: true }); } }); + +test("confined paths accept an aliased workspace root and return the canonical file", (t) => { + const parent = mkdtempSync(join(tmpdir(), "scope-alias-")); + const root = join(parent, "real"); + const alias = join(parent, "alias"); + mkdirSync(root); + const file = join(root, "ok.txt"); + writeFileSync(file, "ok"); + try { + try { + symlinkSync(root, alias, "junction"); + } catch { + t.diagnostic("workspace alias creation unavailable"); + return; + } + assert.equal(confineToWorkspace(alias, join(alias, "ok.txt"), true), normalizeWorkspace(file)); + } finally { + rmSync(parent, { recursive: true, force: true }); + } +});