8000 Gates: no credential in the project (M10.2 secret scan) by adeelahmad · Pull Request #107 · adeelahmad/aloop · GitHub
[go: up one dir, main page]

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
8000
Diff view
2 changes: 1 addition & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ original plan.
| **M7 — Any host** | v0.18 | Session 9: `HostAdapter`, capability matrix, `.aladdin/config.yaml`, host swap, metrics (§4.9–4.10) | done: `HostAdapter` + `host check` in CI, capability matrix, usage accuracy, `.aladdin/config.yaml` with migration, `host swap`, the full metrics table incl. context-window share (tests: `host-adapter`, `portable-config`, `host-swap`, `metrics`); DeepSeek Harness (`dsh`) as a fourth host, tested on its real runtime (tests: `dsh-plugin`, `dsh-runtime`); a measured live dsh run is open (needs a DeepSeek key) |
| **M8 — Aladdin 1.0** | v1.0 | Session 10: `/aladdin` + `aladdin` CLI with aliases, site, measured public runs (§4.11). The rename to aloop shipped early in v0.11.0 | in progress: `bin/aladdin` + `/aladdin` with `aloop`/`agentic` aliases (test: `aladdin-cli.test.py`); natural-language auto-trigger on every host (`intent-trigger`, test: `intent-trigger.test.py`); README reordered product-first; site at `docs/index.html` (Pages switch is the owner's). evidence ledger `docs/evidence/README.md` + `run-summary` (test: `evidence.test.py`). `e2e-live.sh --host codex|opencode` runs the same loop on those hosts (`aloop install <host>` into the run repo, then `codex exec --json` / `opencode run --format json`; `--dry-run` prints the command; test: `e2e-live-hosts.test.py`). Open: the measured public runs themselves (this environment has no `codex`/`opencode` CLI: run `make e2e-live YES=1 HOST=codex` and `HOST=opencode` on a machine with both and add the rows; reason: a number we cannot measure is not published) Mac release run (2026-10-19) found two bugs Linux CI never sees, both fixed with tests: `stats sprint-close` counted 0 attempts when the project sat behind a symlink (macOS `/var` → `/private/var`; paths are now compared by realpath), and `make conformance` hung when another aloop checkout's `plugin/bin` was ahead on PATH (`ensure-tools --resolve` returned that checkout's shim; any aloop bin dir is now skipped). RED is now verified on any language: a non-cargo repo runs its `standards.md` matrix's test command, and a RED whose tests pass or do not collect is blocked (pytest, `go test`; test: `any-language.test.py`). Reason: the north star is any intent, and RED was dynamically checked only for Rust. GREEN and the final gate now refuse Python/Go/JS skips, and the final gate finds a planned `def`/`func` test (before, it found only `fn`, so every planned Python test blocked). `INTENT=slugify` is the Python fixture. Run 36 (slugify, the first Python run): 11/12, 11m37s, $0.83, 5 wakes, 0 plugin-source reads, 15 blocks (9 caught by self-checks). The sprint merged a correct `slugify` with 13 passing tests and the final gate passed. The one failed check was the fixture's own: bare `pytest` leaves the repo root off `sys.path` (now `python3 -m pytest`). It found two gate bugs that Rust runs had hidden. First, a worker's worktree has no `docs/agents/` (git-ignored), so the gates never found `standards.md` and ran the default Rust matrix: RED blocked 4 times on `cargo fmt`, and in a Rust project a custom matrix was silently swapped for the default. Second, `ctx-symbols conflicts` walked the merged task checkout under `.aladdin/worktrees/` and reported `slugify` as a HIGH duplicate, so the structural reviewer was rejected 4 times. Fixed with tests: the gates read the story's sprint `standards.md` (else this tree, else the main tree), and a non-cargo project with no matrix blocks with a message naming `standards.md`; ctx-symbols skips `.aladdin/`. Run 37 on these fixes: 12/12, 9m42s, $0.69, 1 wake, 11 blocks (7 caught by self-checks). It found that a `standards.md` whose matrix is a table parses empty, so RED blocked 3 times; `gate-standards-cited` now refuses a standards.md with no runnable `## Gate matrix` block (test: `any-language.test.py`). Run 38 (`INTENT=roman`, the first Go run): 12/12, 14m15s, $1.02, 3 wakes, 9 blocks (7 caught by self-checks). It found that the sprint dir's `init.md` had no `type: init` header (the supervisor began it with a dispatch block), so the structural reviewer was blocked 7 times; `aloop budget contract` now writes that header (test: `budget-contract.test.py`). Run 39 on the fix: 12/12, 7m52s, $0.56, 0 init.md blocks. It found that GREEN's default scope let it touch Rust test files only, so a Go GREEN removing RED's `_test.go` shim was blocked 10 times; GREEN now shares RED's any-language test globs (test: `any-language.test.py`). Run 40 on it: 12/12, 13m03s, $0.75, 1 wake, 0 GREEN scope blocks (5 blocks, all caught by self-checks). The structural reviewer's HIGH foundation-poisoning finding in runs 38 and 40 was a false positive: a worker's `selfcheck` resolved a stale 0.11.0 `ctx-symbols` (before the `.aladdin/` exclusion) ahead of the session's current build; `ensure-tools --resolve` now skips a stale own build (test: `ensure-tools.test.py`). RED is verified on JS too (a test file that cannot load what it imports is blocked; test: `any-language.test.py`), and `INTENT=camel` is the JS fixture. Run 41 (camel, the first JS run): 12/12, 6m45s, $0.56, 1 wake, 2 blocks (both RED self-checks refusing a source edit). The loop is now measured on Rust, Python, Go and JS. Run 42 (`INTENT=extend`, the first brownfield intent: add `compare` to a crate whose `parse` already works and is tested): 13/13, 7m11s, $0.67, 2 blocks (both self-check catches); the seed's code and tests were left unchanged and still pass (reason: every earlier fixture started from an empty library, and most real intents land on existing code). Run 43 (`INTENT=modify`: change that existing `parse` to accept a leading `v` and trim whitespace): 13/13, 9m10s, $0.67, 6 blocks, all self-check catches; the change was 3 lines in `parse`, with its signature and seed tests kept. `INTENT=cli` (an application: a wc-like binary tested by running it, checked by running the merged binary) is ready; run 44 (the retry after the usage limit lifted): 13/13, 24m55s, $1.54, 6 blocks (all self-check catches), and the merged binary works. Its cost came from planning: 2 stories and 7 tasks for a small tool, so 7 RED/SCAFFOLD/GREEN cycles and 56 supervisor turns (runs 42–43: 1–2 tasks, 16–30 turns). The planner now sizes tasks up to the budget instead of one per behaviour (reason: per-task cost is a fixed ~8 supervisor turns). Run 45 on it: 3 tasks (from 7), 13/13, 16m22s, $1.07. Its HIGH foundation-poisoning halts were `ctx-symbols` reading a test's `const USAGE` as a duplicate of the binary's; a test constant no longer counts (test: `conflicts.rs`). Run 46 on both fixes: 3 tasks, 13/13, 15m19s, $0.99, no review halt, 6/6 blocks self-check catches. Its 4 `targets` "symbol is not defined" blocks were T3 modifying symbols T2 plans in an existing file; a symbol another target plans now counts (test: `semantic-targets.test.py` case 8). Run 47 on it: 13/13, $1.04, 2 blocks (from 6), no "not defined"; but 5 tasks in 2 stories, so planner task count still varies. The spread is now bounded: before execution, `targets check` refuses two adjacent tasks of one story on the same files with 8 or fewer tests between them (replayed on past plans it would have merged runs 23, 40, 43, 44 and 47 and passed every other; test: `semantic-targets.test.py` case 9). Run 48 on it: 2 tasks, 13/13, $0.76, 25 supervisor turns (run 44: 7 tasks, $1.54, 56). One GREEN self-check block in each of runs 46–48 was a false positive: the Rust suppression grep matched an iterator's `args().skip(1)` (a JS pattern); Rust now checks only `#[ignore]` (also with a reason, which it used to miss) and `cfg(not(test))` (test: `suppression-contract.test.py`). Planner friction next: across runs 36–41 the most frequent block was `targets` "create needs planned_symbol" on a new test file (8); a test-file create now takes it from the task's `tests` (test: `semantic-targets.test.py`). The `gate-validate-artifact` plan.md refusals (4) were the persona's bare `stage: 1`, now quoted and checked (test: `persona-shapes.test.py`). Next: the final-gate block order (3). Running every suite under a locally built bash 3.2 found that the `standards.md` gate matrix was silently replaced by the default Rust matrix (`mapfile` does not exist in bash 3.2). It is fixed and guarded by `bash-portable.test.py`. A `macos` CI job now runs the host tests and the conformance fixtures on macOS (reason: both were found only by hand on the owner's Mac). |
| **M9 — Mission layer** | v0.13 | The "Missionary Aladdin Genie" spec (2026-10-17): a human-set mission above goals; fixed roles (research, product testing, state, evaluation, planning, ethics) build a confirmed baseline and a reproducible evaluation layer from user journeys; goals derived from the gap list become Aladdin intents; re-evaluate on a cadence and after each sprint; GA4 usage informs goals and is never a gate; every element has a deterministic check (§4.12). Reason: the owner's mission-driven loop, stacked on Aladdin | in progress: M9.0 core (`aloop mission init / check / approve / status`, sealed mission + approval events, area discovery, test glob; test: `mission-core.test.py`); M9.2 baseline, M9.3 journeys and M9.10 assess/evaluate (`mission-{baseline,journeys,evaluate}.test.py`). Open tracks: M9.1, M9.4–M9.9; the task graph is in §4.12, track notes in `plugin/mission/README.md`; hardened after an adversarial review (ledger records over status fields) |
| **M10 — Agent habitat** | v1.1 | Containment around the gates (§4.13): contained workers, a security and supply-chain gate, fetched content treated as data, one stop for a whole project. Reason: a 2026-10-20 comparison with Replit's "agent habitat" (orchestration, error recovery, safety and sandboxing, networking, identity) found aloop strong on orchestration, recovery and gates but missing containment | in progress: M10.2 dependency half done: GREEN refuses a dependency added to a manifest (Cargo.toml, package.json, requirements*.txt, pyproject.toml, go.mod) that the approved plan does not name (`bin/deps`; test: `supply-chain.test.py`). Next: M10.2 secret scan and dependency audit in the final gate's matrix; M10.1, M10.3, M10.4 planned; plan approval as a gate is M0.9 |
| **M10 — Agent habitat** | v1.1 | Containment around the gates (§4.13): contained workers, a security and supply-chain gate, fetched content treated as data, one stop for a whole project. Reason: a 2026-10-20 comparison with Replit's "agent habitat" (orchestration, error recovery, safety and sandboxing, networking, identity) found aloop strong on orchestration, recovery and gates but missing containment | in progress: M10.2 dependency and secret halves done: GREEN refuses a dependency added to a manifest (Cargo.toml, package.json, requirements*.txt, pyproject.toml, go.mod) that the approved plan does not name (`bin/deps`), and a credential (private key, fixed-prefix token) in the lines it adds; `gate-final` refuses one anywhere in the project (`bin/secrets`, built in rather than a matrix line so it runs on every stack with no install; test: `supply-chain.test.py`). Next: M10.2 dependency audit (`cargo audit`, `npm audit`, `pip-audit`, `govulncheck`) in the final gate's matrix, which needs the standards role to write it and the tool on the host; M10.1, M10.3, M10.4 planned; plan approval as a gate is M0.9 |

**Order rationale:** identity and conformance fixtures first, so every later change is
measured against today's behaviour; budgets and handoff next, because they shape every run;
Expand Down
2 changes: 2 additions & 0 deletions plugin/agents/final-gate.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ matrix is green with zero suppressions and every plan-ready.md fully ticked.
Do not fail the sprint on any other pattern: an `#[allow(dead_code)]` in `tests/common/`
(shared test helpers each test crate uses only part of) is not a suppression. Note any
other lint `allow` you see in `src/` in your report for the retrospective.
- No credential in the project (private keys, cloud and API tokens): `selfcheck final-gate`
scans for one and names the file and line. Report it; never paste or move the secret.
- Walk every story's plan-ready.md; confirm every checkbox is [x].

## Tools
Expand Down
2 changes: 2 additions & 0 deletions plugin/agents/green-worker.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ your init block's `### Budget` line has the exact numbers). Plan for it:
- Never reaches green by suppressing/weakening a test.
- Adds no dependency to a manifest (Cargo.toml, package.json, requirements*.txt, pyproject.toml,
go.mod) that the plan does not name; the gate refuses it. Use what the project already has.
- Writes no credential into the project: read keys from the environment. The gate refuses a
private key or API token in the lines a task adds.

## Comms — the channel (story-bound, append-only)
Your brief is the LAST `## … · green-worker · …` block in `$STORY_DIR/init.md`; read the
Expand Down
1 change: 1 addition & 0 deletions plugin/bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ ctx-symbols) → WARN + grep fallback; never a silent pass.
| `intent` | the internal Intent Contract (ROADMAP §4.1): `new` issues the canonical `intent_id` + skeleton under `docs/agents/intents/`; `check FILE [--mode reviewed\|autonomous]` validates fields and the clarification policy (exit 2 invalid, 3 an `ask` needs the human); `summary FILE` lists defaults/assumptions/spikes for approval. Run by `gate-intake`. |
| `ids` | canonical identity + referential integrity (ROADMAP §4.5): `freeze SPRINT_DIR` checks the structured references (story frontmatter ↔ dir, dirs ↔ stories.md, validate.md ↔ tasks.md headings, `split_from` → a retired task) and issues frozen UUIDv7 ids in `sprintN/ids.json`; a retired alias is never reused · `check` · `resolve ALIAS`. Run by `gate-stage2-complete`; events then carry `story_uid`/`task_uid`. |
| `deps` | supply chain (ROADMAP M10.2): `ad 8000 ded --base REF` lists the dependencies a change adds to Cargo.toml, package.json, requirements*.txt, pyproject.toml or go.mod · `unplanned --base REF --story DIR` the ones the story's plan does not name (exit 2); gate-green-verify blocks on them. No third-party parser. |
| `secrets` | secret scan (ROADMAP M10.2): `scan [--base REF]` reports `path:line: kind` for a private key or a fixed-prefix token (AWS, GitHub, GitLab, Slack, Stripe, Google, Anthropic, OpenAI), never the text; with `--base` only added lines. gate-green-verify scans the task's added lines, gate-final the whole project (exit 2). `aloop: allow-secret` on a line exempts a fixture. |
| `targets` | semantic execution map (ROADMAP §4.6): the planner's `<story>/targets.json` — change targets and, with `tasks`, the whole semantic plan. `render STORY_DIR [--check]` writes tasks.md / validate.md / plan.md as deterministic views over it (the gate refuses a hand-edited view) · `check STORY_DIR` (run by `gate-stage2-complete`) · `target Tn STORY_DIR` (file:line, or `planned` for the shadow workspace) · `shadow` · `scope Tn STORY_DIR` (the diff scope gate-green-verify derives when SCOPE_GLOBS is unset) · `context Cn --budget N` (just the target's definition) · `refs\|callers\|callees` via the LSP server in `AGENTIC_LSP_COMMAND` (`_lsp.py`: references + call hierarchy); no server or a missing capability → exit 4, never guessed. Backend: ctx-symbols, else a definition regex flagged weaker. |
| `impact` | optional cross-project impact provider seam (ROADMAP §4.8): `query consumers\|rationale\|migration\|standards SUBJECT [--task]` runs `AGENTIC_IMPACT_PROVIDER` (defaults.md or env), keeps only results with a `source_ref`, saves them under `docs/agents/impact/` and emits `artifact.created`. No provider or a failing one → WARN, exit 0. Builds no index. |
| `layout` | the run-data dir (ROADMAP M1): `show` (which of `.aladdin/` / `.agentic/` is live and why) · `check` (exit 2 unless every run dir is git-ignored) · `migrate` (move `.agentic/` run data into `.aladdin/`, refused while worker worktrees are live; in `make test-hosts`) |
Expand Down
9 changes: 9 additions & 0 deletions plugin/bin/_gatelib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,15 @@ assert_planned_dependencies() {
fi
}

# Secrets (ROADMAP M10.2): no credential in the project. With an argument, only lines added since
# that ref (a task's diff); without, every file (the sprint sign-off). exit 2 if one is found.
assert_no_secrets() {
local out
if ! out="$("$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/secrets" scan ${1:+--base "$1"} --root "$(repo_dir)" 2>&1)"; then
fail "credential in the project: $(echo "$out" | tr '\n' ';' | sed 's/; *$//'). Read it from the environment or a git-ignored file instead; a test fixture that must look like a key carries an \`aloop: allow-secret\` comment on its line"
fi
}

# True if $1 matches any space-separated glob in $2.
# Test files by convention: Rust's tests/ and tests/common/, any tests/ dir, and the Python/Go/JS
# test-file names. RED writes only these; GREEN may touch them besides its scope, to drop shims.
Expand Down
9 changes: 6 additions & 3 deletions plugin/bin/gate-final
< 8BD0 td id="diff-11805444418c57dc50a1299777d70b482fcc1c0f1268e1d5afb21927e8e508acL54" data-line-number="54" class="blob-num blob-num-context js-linkable-line-number">
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# FINAL GATE (sprint sign-off): full standards matrix green, zero suppressions,
# every plan-ready.md fully [x]. (Rust, Python, Go, JS; matrix from standards.md.)
# FINAL GATE (sprint sign-off): full standards matrix green, zero suppressions, no credential
# in the project, every plan-ready.md fully [x]. (Rust, Python, Go, JS; matrix from standards.md.)
GATE_NAME="gate-final"
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
: "${CLAUDE_PLUGIN_ROOT:=$(cd "$HERE/.." && pwd)}"
Expand All @@ -18,6 +18,9 @@ root="$(repo_dir)"
# 1) Zero suppressions anywhere.
assert_no_suppression

# 1b) No credential anywhere in the project (ROADMAP M10.2).
assert_no_secrets

# 2) Every plan-ready.md fully ticked — in this tree AND in the sprint's docs in the main
# tree (a worker worktree has docs/agents/ sparse-checked out, so look there too).
sprint_dir="${SPRINT_DIR:-}"
Expand Down Expand Up @@ -52,7 +55,7 @@ fi
# 3) Full gate matrix (from standards.md) green.
run_standards_matrix

note "FINAL gate passed: matrix green, zero suppressions, all plan-ready ticked, every planned test on main"
note "FINAL gate passed: matrix green, zero suppressions, no secrets, all plan-ready ticked, every planned test on main"

# 4) Sprint close — by the harness, only on the real SubagentStop run (never a self-check):
# sprintN/stats.md, docs/agents/NEXT.md (the post-/clear handoff), token totals.
Expand Down
5 changes: 4 additions & 1 deletion plugin/bin/gate-green-verify
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# GREEN gate: this task's tests pass, no regression, diff within tasks.md scope,
# the standards.md matrix is green, zero suppressions, no unplanned dependency, output.md validates.
# the standards.md matrix is green, zero suppressions, no unplanned dependency or added credential, output.md validates.
# (Target: Rust. The matrix is the one DECLARED IN standards.md — not hardcoded.)
GATE_NAME="gate-green-verify"
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Expand Down Expand Up @@ -38,6 +38,9 @@ fi

# 3b) Supply chain: no dependency the approved plan does not name (ROADMAP M10.2).
assert_planned_dependencies
# and no credential in the lines this task added (the final gate scans the whole project).
secrets_base="$(diff_base)"
[ -n "$secrets_base" ] && assert_no_secrets "$secrets_base"

# 4) The standards.md gate matrix (fmt/lint/test/coverage/...) must be green.
# This also asserts the task's tests now pass and nothing regressed.
Expand Down
Loading
Loading
0