Files
IUMBTEMS/schemas/audit-record.schema.json
484aa12dcd Release 1.1.0: Dialectic Restoration (#24)
* M0 spike: prove OpenCode v2 extension points against a real in-process host

17/17 green against published @opencode/sdk 2.0.24 with a scripted fake model:
tool.execute.before blocking (plugin + host-native tools, Effect and Promise APIs),
permission.evaluate deny, per-agent wildcard-deny hiding of tools/MCP tools/skills,
hidden subagents + depth limit, websearch/command/compaction/context/shell hooks,
question forms, lsp passthrough, and a TUI session.panel rendered headlessly.
Also records Pi 1.0.4 extension signatures and the Antigravity hooks contract,
which refutes the plan's inferred hooks.json defect.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Start 1.0 monorepo: remove legacy tree, add es-core utilities

Legacy Python runner and JS plugins are removed on the rewrite branch
(preserved at tag legacy-final). Adds the Bun workspace scaffold and es-core
foundations: canonical hashing, atomic writes with a crash-safe lock, portable
globs, JSONC, a strict YAML-subset frontmatter parser, and a subprocess runner
with timeouts, output caps, scrubbed env and a bwrap tmp-overlay read-only mode.
17 tests green.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M1: Factory state machine, mechanical gates, trust engine, and OpenCode v2 plugin

Complete Milestone 1 of the IUMBTEMS 1.0 rewrite on branch rewrite:
- Canonical Zod schemas (frontier, goal, phase, gates, waiver, approval, audit)
- Cryptographic hash-chained JSONL audit logger and integrity verifier
- Per-seat path write permissions (manager, programmer, qa-a, qa-b), control file protection, and STOP kill-file monitor
- Mechanical gates engine with manifest auto-detect, secret scanner, budget checks, and waiver evaluation
- Git worktree runner for phase isolation under .factory/worktrees/phase-<id>
- Factory state machine with code-enforced transitions, retry policy, and autonomy caps
- Promoted @heretek-ai/es-testkit from spikes/m0 for real in-process OpenCode testing
- OpenCode v2 Promise plugin (@heretek-ai/epistemic-swarm) with tools, hooks, and commands
- CLI runner (es-factory) and coarse stdio MCP server (@heretek-ai/es-cli)
- 42/42 tests pass across 9 test suites; bun run check clean.

* M1 remediation: canonical path policy, seat registry and control baseline

Replaces the seat permission check that every agent bypassed. Paths are
canonicalised (resolve, normalise, realpath of the existing prefix) before
policy runs, so absolute paths, '..' segments and symlinks into .factory are
caught. Control files are deny-write for every agent (config controls ask for
non-seat agents), seats are looked up from a canonical agent registry by agent
ID, the programmer may only write inside its phase worktree, QA is read-only,
and the user-global state dir (signing key, trust store) is off limits.
Adds the shell policy (human-only CLIs, control-file mentions, read-only seats
via bwrap or an allowlist) and a control-file hash baseline.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M1 remediation: human-only signed approvals and a rewritten factory state machine

Approvals: the subject is derived from the factory layout (never a caller-
supplied path), validated, hashed and HMAC-signed with a key in the user-global
state dir; records carry channel, OS user and the audit head. verifyApproval
re-hashes the artifacts, so a spec edited after approval blocks the build and a
hand-written record is rejected.

Factory: GRILL → RESEARCH → SPEC → BUILD ⇄ QA → RELEASE → DONE (+ HALTED).
Seat checks by agent ID; gates run in the phase worktree; complete requires
changes, a recorded red test run, acceptance criteria and green gates; QA
verdicts only from QA seats, tiebreak only from the manager; failure policy
3 + replan + 2 = halt on the 5th failure; passing phases fast-forward a run
integration branch; release runs gates on it and opens a PR (DONE needs the
URL). Every op checks STOP, control-file drift, spend and the runtime cap
(counted from autonomy start). Side effects are journaled; state and audit
move under .factory/runtime/, and approval anchors detect log truncation.

The OpenCode plugin and CLI still target the old API and are reworked next.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M1 remediation: gates engine with trust pinning, parsed findings and logs

- Commands come from .factory/gates.json or manifest detection (JS/TS, Python,
  Rust, Go), all in check mode; typecheck only when a tsconfig exists.
- Fail closed: an untrusted command set never runs (hash of commands plus the
  package scripts they reach, approved by a human in the user-global trust
  store); missing tools, invalid gates.json and unreachable OSV on a
  dependency change are findings.
- Findings are file:line:rule with fix hints, parsed from tsc, biome, eslint,
  prettier, ruff, mypy, cargo, gofmt, go vet and bun/vitest/jest/pytest/go/cargo
  test output; every command log and a full summary land in
  .factory/runs/<run>/gates/<stamp>/.
- Touched scope runs runner-native related tests; full scope diffs against a
  base. Built-ins: secret scanner + gitleaks, OSV (osv-scanner or API), budgets
  (diff size, file length, complexity, dependency justification, test with
  behaviour change), artifact schemas, control drift, and tool-config edits
  inside a factory phase.
- Waivers are HMAC-signed and expiring; forged ones are ignored and reported.
- Phase worktrees get node_modules/.venv symlinks so tools resolve there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M1 remediation: OpenCode v2 plugin rebuilt on the core policy, with a human-only TUI

- Agents: the canonical registry compiles to v2 agents (factory, grill
  primaries; hidden manager/programmer/QA/research seats) with versioned
  markdown prompts, model tiers and wildcard-deny scoping of es_* tools,
  subagents, skills and MCP servers. Built-in agents are never modified;
  their es_* tool view is trimmed per request by the context hook.
- Tools (direct, one permission action each): status, frontier write,
  approval *request*, research complete, spec validate, build start, gates,
  gated complete, QA verdict, tiebreak, replan, release. There is no
  approve tool.
- Hooks: write/edit/patch/read/shell pass through the canonical-path policy;
  read-only seats run shells under bwrap (or allowlist + post-run diff check
  that halts); STOP and a halted run stop seats and es tools; autonomous seats
  never wait on an 'ask'; programmer edits get fast gate feedback; the
  factory state block is injected for factory agents and kept on compaction;
  spend accrues from step costs (estimated from tokens when 0).
- Human channel: an RPC contract the TUI calls after a confirm dialog; every
  mutation redeems a short-lived preview token over the same subject. The TUI
  adds /es-approve, /es-trust, /es-resume and /es-status.
- Real-host tests (published @opencode/sdk 2.0.24, fake model) cover the
  attacks from the review and a full grill → release run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M1 remediation: es CLI with TTY-gated human actions, real MCP server, headless driver

- Tool logic moves to a harness-neutral core module (esTools) used by the
  OpenCode plugin and the MCP server, and by the coming Claude/Pi/Antigravity
  adapters.
- es CLI: status; factory begin/stop/resume/pr/run --headless; approve,
  trust, waive and resume require an interactive terminal and a typed random
  code, and show exactly what is signed; gates run (touched/staged/full, JSON)
  and an opt-in git hook installer (core.hooksPath); audit verify; mcp.
- MCP: newline-delimited JSON-RPC over stdio (initialize, ping, tools/list,
  tools/call) with no approve/trust/waive/resume tool; caller identity is an
  advisory agent argument (or ES_AGENT).
- Headless: driver interface plus an `opencode run --standalone --format
  json` driver; the loop stops for the grill, pending approvals, halts and
  stalls instead of guessing.
- Policy: es factory resume/pr and gates install-git are human-only CLIs;
  .factory/git-hooks is a control path.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M2: hook bridge — Claude Code hooks running inside OpenCode v2

Core (harness-neutral, packages/core/src/hooks):
- Canonical spec = Claude Code's hooks JSON schema (validated, unknown keys
  kept) plus namespaced extras: "x-es-policy": gate|observer and the
  "x-es.ShellPreExec" event.
- Loading follows Claude's merge rules (.opencode/hooks.json,
  .claude/settings{,.local}.json, user configs; identical handlers run once;
  disableAllHooks). Project hooks are hash-pinned and run only after a human
  trusts them (shared trust dialog with gate commands).
- Matchers: Claude exact-list/regex semantics against both the Claude and
  host-native tool names; `if` permission rules over Bash subcommands
  (including $() and backticks) and file paths.
- Runner: command (exec/shell form, stdin JSON, exit-2 and JSON output rules),
  http, and prompt hooks via the host's model; mcp_tool/agent reported as
  capability loss.
- Engine: parallel handlers; deny > defer > ask > allow; merged input edits and
  context; gates fail closed (error, timeout, missing trust), observers fail
  open; every run audit-logged; mtime reload; per-harness capability table and
  capability-loss report.

OpenCode wiring: PreToolUse/PostToolUse(Failure) on tool hooks (deny,
updatedInput, ask via the host prompt), UserPromptSubmit on prompt admission
(block rejects), SessionStart (startup and after compaction), PermissionRequest
on permission.evaluate, x-es.ShellPreExec on shell.create.before, and Stop
emulated on execution end with an 8-continuation cap (advisory for plain
chat). The factory session also auto-continues while work remains (stall-
capped). /hooks and `es hooks` show status, trust and capability loss.

Real-host tests cover each event, trust gating and fail-closed gates.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M3: LSP runtime for OpenCode v2 (which accepts but does not run the lsp key)

Core (packages/core/src/lsp):
- Registry of built-in servers (TypeScript, pyright, gopls, rust-analyzer,
  clangd) merged with OpenCode's own `lsp` config key (command, extensions,
  env, initialization, disabled; lsp:false turns it off), read from the same
  opencode.json(c) files, since plugins get no config API.
- TypeScript resolves to the workspace's native `tsc --lsp --stdio` for
  TypeScript >= 7, else typescript-language-server; binaries come from the
  workspace node_modules, PATH, or a pinned install.
- Ask-once installs: a human consents (es lsp install / TUI), the exact npm
  tarball's sha512 must match the pin, and it installs with scripts disabled
  into the user state dir. Agents cannot install (human-only CLI).
- Client on vscode-jsonrpc: full document sync, push + pull diagnostics with a
  debounced settle, definition/references/hover/symbols/rename.
- Manager: lazy start per (server, root) (phase worktrees get their own),
  idle shutdown, concurrency cap with LRU eviction.

Surfaces: post-edit diagnostics appended to write/edit/patch results;
lsp_diagnostics/definition/references/hover/symbols tools ("lsp" permission)
and lsp_rename ("lsp_rename"; applying passes the write policy per file);
per-agent access from the registry (programmer full, QA/manager/factory read,
grill/research none); a touched-scope "lsp" gate in the engine; /lsp and
`es lsp` status/diagnostics/install. Tested against real servers (native
tsc --lsp, gopls) in core and on the real OpenCode host.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M4: research stack — cited, cached, audited evidence

- Content-addressed source cache (.factory/research/sources/<sha256>.md +
  .json, URL index): tracked next to the report so reviewers can verify
  citations; tampered sources (bytes no longer matching the name) are refused;
  uncited sources are pruned when research completes.
- Pure verbatim-quote verifier: whitespace/typography/markdown-normalised exact
  match, ellipsis fragments in order, minimum length; no fuzzy matching.
- Epistemic auditor: every claim line needs a tag; [VERIFIED: sha256:<hash>
  "quote"] must quote the cached source verbatim; INFERRED/HYPOTHESIS/
  NEGATIVE_KNOWLEDGE need substance; pruning moves failing claims to a
  'Pruned claims' section; coverage report with counts only (no composite
  score). Enforced on research artifacts only (REPORT.md in the artifacts gate).
- Providers: Brave, Firecrawl, SearXNG (env-configured) and a plain page
  fetcher with a dependency-free HTML-to-text converter. DDG scraping dropped.
- The configured provider registers as the host websearch (results cached;
  default only when none is selected) and host webfetch results are cached too.
- Research seats get es_research_search/fetch/audit; the factory's
  es_research_complete refuses a report that does not pass the audit and writes
  coverage.json. Prompts v2 for factory/research seats. `es research` CLI.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* M5: shared tree-sitter structural index; import-graph affected tests

- Pinned grammars (typescript/tsx/javascript/python/go): each wasm comes from
  an exact npm tarball, verified against its SRI integrity and per-file sha256
  before it is cached (user cache dir) and loaded; a tampered cache entry is
  refused and a network failure latches for 10 minutes; offline/ES_GRAMMARS=off
  falls back to the regex scanner.
- Structural extraction: imports (static/type/dynamic/require/reexport/
  side-effect) and top-level symbols per language, with a conservative regex
  fallback when grammars are unavailable.
- Import graph: relative and workspace resolution for JS/TS ('.js' -> '.ts',
  index files, package.json exports including './*'), Python relative and
  absolute modules, and Go module paths (walking up to the nearest go.mod),
  plus reverse reachability for affected tests.
- The gates engine prefers graph-derived affected tests for touched scope
  (language-filtered per runner; runner-native selection stays the fallback),
  so touched-scope test selection no longer guesses by file-name stem alone.
- structure tests (grams pins, untar, cache integrity/offline, extraction,
  graph, caps) and gates integration tests. 170 tests green.

* M5: brainstorm — divergent lenses, mechanical dedupe, rubric, diversified shortlist

- Eight built-in lenses (inversion, constraint-removal, scamper, cross-domain,
  extreme-user, scale-shift, failure-first, adjacent-possible), each run by a
  hidden fast-tier subagent (es-lens-<id>); a visible primary 'brainstormer'
  orchestrates and a hidden deep-tier 'es-brainstorm-critic' scores.
- es_brainstorm_plan freezes the lens set and the caps (6 lenses default, 5
  ideas each, 600 chars) and returns the subagents to launch; es_brainstorm_
  record enforces caps and collapses near-duplicates (word-bigram Jaccard);
  es_brainstorm_score is critic-only and validates 1-5 on the rubric (novelty,
  upside, feasibility, fit); es_brainstorm_complete refuses gaps and unscored
  survivors, ranks, and selects the diversified shortlist with a forced outlier
  slot, writing brainstorm.json + BRAINSTORM.md under .factory/brainstorm/.
- Seat-scoped writes ('brainstorm' scope), /brainstorm command, es brainstorm
  plan|show|lenses in the CLI, per-agent model overrides (models.agents).
- 13 core tests + 3 real-host tests (186 total green).

* M5: darkharvest — fail-closed SPDX, provenance, policy-enforced matrix, clean-room specs

- Deterministic SPDX detection from local bytes (signature phrases over
  LICENSE/COPYING files, then SPDX headers, then manifest claims marked
  unverified); AGPL/GPL/LGPL/MPL are classified as copyleft and unknown stays
  unknown. detectLicense never guesses.
- Sources: GitHub/GitLab discovery APIs (injectable fetch, token-aware), a
  shallow clone, local directories and npm/PyPI/crates metadata; API licence
  fields are recorded as unverified claims.
- es_harvest_scan builds a profile (languages, size, dependency graph from the
  shared tree-sitter index, commit, licence) with per-field provenance and
  warnings; es_harvest_read enforces a per-candidate token budget and refuses
  paths outside the candidate; failed scans are refusals, not silent gaps.
- es_harvest_matrix applies the licence policy: depend/vendor require a
  text-verified, whitelisted licence, everything else is downgraded to
  clean-room with the reason, and allowed cells get SPDX attribution.
  es_harvest_complete writes harvest.json, HARVEST.md, VENDOR-PLAN.md and one
  clean-room spec per rebuild cell under .factory/harvest/.
- es_harvest_prior_art feeds brainstorm's optional prior-art check (relations
  remain the LLM's judgement; failed searches are UNVERIFIED).
- Visible 'harvester' primary seat, 'harvest' write scope, /harvest command,
  es harvest show|scan in the CLI. 14 core tests + 3 real-host tests
  (203 total green).

* M6a: CI (check + docs drift) and nightly against OpenCode v2 HEAD

- Spike (spikes/m6/RESULTS.md): v2 lives on anomalyco/opencode refs/heads/v2
  (the default/dev branch is the v1 line); sdk/plugin export TS directly, so a
  shallow clone + bun install + node_modules relink runs our real-host suite
  against HEAD with no build. 34/34 packages/opencode tests pass against v2
  HEAD 369aad3; cold end-to-end 29s.
- scripts/v2-head.sh: clone/fetch v2 HEAD, install, relink @opencode/sdk and
  @opencode/plugin, run the suite (bun install restores the published links).
- .github/workflows/ci.yml: install --frozen-lockfile, bun run check,
  docs drift. nightly.yml: scripts/v2-head.sh packages/opencode on schedule.
- scripts/docs.ts + docs:gen/docs:check: emit schemas/*.schema.json (37 Zod
  contracts, z.toJSONSchema) + docs/SCHEMAS.md; --check fails on drift and
  stale files. Root zod devDependency added for the generator (same 4.6.5 pin
  as core, recorded justification: docs tooling under isolated installs).
- 203 tests green; docs check green.

* M6b: queereye interview core — DTCG tokens, WCAG gate, generated guide

- 7-axis schema-typed interview (brand, color, type, layout, effects,
  dark/light, a11y): one form per axis, recommendations, deterministic
  counter-questions for vague answers, smuggling refusal for pasted hex/font
  literals, repair hooks for unknown options and unsafe decoration, and
  cross-axis contradiction refusal (low-contrast taste vs the a11y gate).
- DTCG-shaped token validation: $value/$type/$description only, group
  $type inheritance, alias chains with circular/dangling errors, mixed
  raw+alias refusal, list-smuggled keys refused, and one-off mints (raw
  values outside <domain>.primitive.<name>) as compile errors.
- Ratio-first palette generation (WCAG AA 4.5/3, AAA 7 reported): the
  completed loop emits a primitive -> semantic -> component tree with zero
  one-offs (reuse ratio ~0.5), and every text pair passes.
- Pure renderer: Style-Dictionary-compat CSS vars + STYLE_GUIDE.md, plus
  .factory/design/ contract (hard error outside, idempotent atomic writes,
  user-customized files backed up with a conflict note, interview.json
  resume, corrupt-JSON errors).
- Visible 'designer' primary seat, 'design' write scope, /design command,
  es design status|render|check CLI, queereye skill; designer prompt snapshot.
- 24 core tests + 3 real-host tests; 230 total green; generated schemas and
  docs refreshed.

* M6c: four TUI panels + panel RPCs + /research command

- Structured panel RPCs on the human-only channel: factoryState (stage,
  run, spend/ceiling, phases with QA, pending approvals, halt, PR),
  lspState (servers, availability, running roots, diagnostics), hooksState
  (handlers, trust, project lines, recent errors, per-handler capability
  loss), brainstormState (brief, coverage, duplicates, shortlist, outlier).
- Four fullscreen panels as session.panel slot claims (panels.tsx):
  factory dashboard, LSP manager, hook inspector (ADVISORY rows from
  capabilityLoss) and brainstorm board; commands es-factory / es-lsp-panel /
  es-hooks / es-brainstorm open them via ui.panel.open.
- HookEngine.list() exposes loaded handlers for capability-loss reporting.
- /research command: report size, audit coverage from coverage.json and the
  cached-source count.
- Tests: TUI slot/command/open assertions on the fake context, panel RPC
  shapes on the real host. 232 total green.

* M6d: capability matrix, layered config, generated docs, rewrite guides

- Capability matrix (packages/core/src/capabilities.ts): 15 capabilities
  declared per harness; opencode rows are ENFORCED with a proof (test file or
  the M0 spike record), claude/pi/antigravity rows are declared gaps for
  1.1-1.3. enforcedWithoutProof() is a contract the tests and docs enforce.
- Canonical config (schema/config.ts + config.ts): models/tiers/agents,
  afterEdit, spend estimate, PR opener, LSP after-edit, search provider and
  the darkharvest license whitelist; layered global -> project -> plugin
  options with recursive per-key merge and strict unknown-key rejection.
  createRuntime resolves it and the harvest tools default to its whitelist;
  /config and `es config show` print the effective config and its layers.
- Generated docs join the drift pipeline: docs/CAPABILITIES.md (matrix +
  hook bridge detail; missing proofs fail the generator) and docs/CONFIG.md
  (from the emitted JSON Schema); docs/SCHEMAS.md refreshed.
- README.md and AGENTS.md rewritten for the 1.0 monorepo (the legacy Python
  guide is gone); testkit isolates XDG_CONFIG_HOME so real-host tests are
  deterministic against user config files.
- 241 tests green (capabilities contract + config layering); docs drift green.

* M6e prep: dogfood runbook (factory builds the tree-sitter complexity budget)

docs/DOGFOOD.md: how to load the local plugin, grill the feature with a
spend ceiling, approve frontier/spec, run the autonomous build, review the
draft PR and record the outcome. The run itself needs a human and a real
model provider.

* CI fix: QA shell sandbox test accepts the allowlist path

The runner has no bubblewrap, so the read-only seat refuses 'touch' outright
instead of running it in the sandbox. The guarantee under test is that no
write escapes; the test now accepts either mechanism (sandbox ran it, or the
allowlist refused it) and still asserts the file does not exist.

* M6a note: nightly workflow activates at cutover (scheduled workflows only run from the default branch); CI green on rewrite after the sandbox-test fix

* M6 remediation 1/6: code scanning, workflow hardening and Sonar security fixes

- CodeQL default setup drops the python language (the legacy tree is gone,
  so the analyzer failed with 'no source code seen during build')
- Pin actions/checkout and oven-sh/setup-bun to commit SHAs and set a
  least-privilege permissions block in ci.yml and nightly.yml; the nightly
  also gains push-to-rewrite and workflow_dispatch so it can run pre-cutover
- bun install --ignore-scripts in CI (v2-head keeps scripts: OpenCode's
  postinstall fixes node-pty, so that line is NOSONAR-justified)
- Fix the missing String.localeCompare comparator in the queereye token walk
  (Sonar S2871) and drop an unused import
- NOSONAR-justify the intentional /tmp permission allow and PATH lookups

* M6 remediation 2/6: honest capability rows, self-hosted dependency gate

- HOOK_CAPABILITIES: claude/pi/antigravity events are UNSUPPORTED until their
  adapters ship (1.1-1.3); only opencode is enforced. The generated
  docs/CAPABILITIES.md no longer claims enforcement for harnesses that do not
  exist, and capabilities.test now fails any enforced hook row without a proven
  enforced matrix row.
- New core gate src/gates/minimal-deps.ts plus scripts/deps.ts: core's runtime
  dependencies must stay within the plan's allowlist (zod, web-tree-sitter,
  vscode-jsonrpc, vscode-languageserver-protocol) or carry a recorded
  justification in .factory/dependencies.json; wired into 'bun run check'.
- Document the deliberate enforcement limits (read-only fallback, control-file
  baseline scope, advisory MCP caller identity) in README and the MCP
  instructions string.

* M6 remediation 3/6: resolve the CodeQL new-code alerts (45)

Fix, not dismiss:
- ReDoS (28): replace ambiguous alternations/nested quantifiers with linear
  single-quantifier patterns and, where regex could not be made safe, hand
  written scanners (blankBlockComments, stripLiterals, HTML tokenizer).
  Files: gates/parsers.ts, gates/budgets.ts, structure/extract.ts,
  research/html.ts, research/auditor.ts, harvest/spdx.ts, harvest/scan.ts,
  hooks/tools.ts, worktree/git.ts.
- HTML sanitization (11): htmlToText now walks a tag tokenizer and never
  emits raw markup, so no tag-stripping regex remains; scripts/docs.ts escapes
  backslashes before pipe escaping; replaceAll for the 'first occurrence' spots.
- File-system race (3): read through a single descriptor (open + fstat +
  readFile) in structure/graph.ts and harvest/scan.ts, and re-read the signing
  key via a helper in approval/keystore.ts.
- Prototype pollution (1): reject __proto__/constructor/prototype slot names.
- Shell command (1): gates/security.ts resolves binaries by scanning PATH with
  accessSync instead of interpolating into 'sh -c'.
- Missing regexp anchor (1): group the DOC_OR_CONFIG alternatives explicitly.

bun run check: 246 tests green; docs:check green.

* M6 remediation 4/6: finish CodeQL ReDoS/sanitization and Sonar reliability

Second pass after the first fixes still tripped js/polynomial-redos:
- budgets.ts: replace the combined function-header regex with an ordered list
  of linear patterns, taking the leftmost match.
- structure/extract.ts: normalize whitespace before the Python import/def
  patterns; replace the go receiver group with a linear capture; blank line
  comments with a scanner (lineCommentIndex) instead of a regex.
- research/html.ts: locate title/body/main by indexOf instead of tag regexes.
- gates/parsers.ts: split the mypy column-optional pattern in two; parse the
  vitest/pytest/generic trailing text in code instead of in the regex.
- research/auditor.ts + queereye/slots.ts: drop the ambiguous whitespace
  quantifier / inline the __proto__ guard so the analyser recognises it.

Sonar reliability (S2871): add explicit code-unit comparators to every
argument-less Array#sort in shipped code (order-preserving), and NOSONAR the
two probe-fixture /tmp paths under spikes/ (analysis scope decision).

bun run check: 246 tests green.

* M6 remediation 5/6: clear the last CodeQL ReDoS/PP hits and the CLI PATH vuln

- parsers.ts: parse mypy with single-space patterns; parse vitest/jest, pytest
  and the generic fallback by scanning lines/tokens instead of regexes.
- budgets.ts: match function headers against a whitespace-normalised line with
  single-space patterns.
- auditor.ts: tag body uses [^\]]*; structure/extract.ts extracts the Go
  receiver with a char-code scan (lastWord).
- queereye/slots.ts: per-axis value maps use a null prototype.
- cli/headless.ts: resolve the opencode binary to an absolute path (accessSync
  PATH scan) so no child process is spawned via PATH; onPath's 'sh -c' is gone.

bun run check: 246 tests green.

* M6 remediation 6/6: close the last CodeQL hits (TAG, headers, prototype)

- auditor.ts: parseTags walks the line with indexOf/startsWith instead of a
  regex; the tag body is a plain slice.
- budgets.ts: restrict the function-header character classes so no two
  quantifiers can overlap (params/return types use [\w$ ,:?=*...] classes).
- queereye/slots.ts: per-axis answers are a Map (no bracket assignment or
  delete on a keyed object); store.ts, ops.ts, cli/main.ts and the queereye
  test follow the Map. Removes the prototype-polluting assignment for good.

bun run check: 246 tests green.

* M6 remediation: simplify complexity header identifiers (drop $ from classes)

* M6 remediation: detect method headers without a signature regex

Match 'name(' at line start, then require ')' followed by '{' via indexOf;
the regex no longer consumes the parameter/return-type text.

* M6 remediation: detect function headers with a scanner, no regex

Replace the header regex list (and the last js/polynomial-redos hit) with
detectHeader: a linear char scanner for function/fn/func/arrow/method forms.
Complexity behaviour is unchanged (the complexity test still passes).

* M6 remediation: publishing, cutover note, MinHash + embeddings dedupe

- Publish workflow (workflow_dispatch, OIDC trusted publishing + provenance,
  defaults to dry-run) and per-package prepublishOnly builds so dist exists.
- CHANGELOG.md cutover note: no migration, legacy-final tag, breaking change.
- Brainstorm dedupe: MinHash (new default) with n-gram still selectable, and an
  optional OpenAI-compatible embeddings endpoint (config 'embeddings', batching
  + caching client, async cosine dedupe) wired through the brainstorm tools.
- Docs/schemas regenerated; 250 tests green (MinHash + embeddings tests added).

* M6 remediation: pin npm and skip lifecycle scripts in the publish workflow

* M6 remediation: opt-in, cost-capped behaviour evals

- Core grader (packages/core/src/evals/grade.ts) with tests: case-insensitive
  contains/notContains, collects every failure.
- evals/cases/{grill,brainstorm,factory}.json and scripts/evals.ts: runs each
  case via 'opencode run', bounded by ES_EVAL_TIMEOUT_MS, writes results to
  evals/results/, and skips cleanly when the CLI is absent.
- nightly.yml gains an evals job gated on the ES_EVAL_API_KEY secret; never in
  the default test run. .gitignore now ignores only evals/results/.

* M6 remediation: fix workflow validation (secrets not allowed in if; quote publish flags)

* PR #18 remediation 1/8: confine darkharvest to the project; harvested code never executes

Review of PR #18 found (probed) that the harvester could plan `local:<state dir>`
and read the approval signing key with es_harvest_read, follow symlinks out of a
cloned candidate, and trigger a candidate repo's own core.fsmonitor by running
`git ls-files` inside it.

- assertSourceAllowed: agent-driven local and file-path git sources must
  canonicalize inside the project; the user-global state dir is refused for
  everyone; ext:: and other git helper transports are refused. Checked at plan
  time and again in scanSource. Only the es CLI (a human) passes allowOutside.
- Harvested content is untrusted: the structural index walks it instead of
  running git (GraphOptions.untrusted), license/manifest/candidate reads go
  through readRegularFile (no symlinks, same-inode check, 2 MB cap), and
  readCandidate checks containment after resolving symlinks.
- The commit comes from .git/HEAD, refs and packed-refs read as files.
- shallowClone: GIT_ALLOW_PROTOCOL (file only for allowed local sources), no
  submodules, no LFS smudge, no terminal prompt, `--` before the URL.
- Tests: outside/state-dir/transport refusals, symlink escapes, and an
  fsmonitor sentinel that must not run.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation 2/8: seal engine-owned brainstorm/harvest/design state

The seat write scopes covered .factory/<x>/**, and the gated tools read that
JSON back: the harvester could hand-edit matrix.json (probe: a GPL candidate
ended up in VENDOR-PLAN.md) or a profile's licence, and the brainstormer could
write scores.json and skip the critic.

- Engine state (plan/ideas/scores/result, harvest plan/matrix/profiles/report/
  vendor plan/clean-room specs, design tokens/guide/css) is a factory control
  file: no agent's host edits may touch it; the tools write through core. The
  shell control-mention check covers the engine JSON. Seats keep notes/**.
- Defense in depth: es_harvest_matrix and es_harvest_complete re-derive every
  licence from the scanned bytes (currentLicense), and complete re-applies the
  policy to the stored matrix (recheckMatrix), reporting any downgrade.
- Candidate ids never take the reserved notes/ or clean-room/ directories.
- Tests: policy matrix for every agent, hand-edited profile and matrix, and a
  real-host write denial for the harvester.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation 3/8: fail-closed SPDX detection by full-text template match

The signature detector failed open and misidentified (probed on real texts):
the JSON licence ("Good, not Evil") and MIT + Commons Clause came out as MIT,
GPL-3.0 as AGPL-3.0 (its section 13 mentions the Affero GPL), LGPL-2.1 as
GPL-2.0; one SPDX header in any of 200 files verified a whole repo; only the
first licence file counted.

- templates.ts: SPDX license-list v3.29.0 templates for the whitelist, bundled
  in assets/licenses and pinned by sha256 (fetched sources cached under
  .research/sources). A permissive/public-domain verdict needs the whole file
  to match: words after SPDX-style normalisation, <<var>> spans checked by
  their own regex, optional blocks, nothing left over. The leading copyright
  var absorbs only copyright / rights / short title lines, so a prepended rider
  cannot hide in it. Memoised matching, no whole-text backtracking regex.
- Copyleft and source-available licences by title line first, then by their
  distinctive date lines; permissive look-alikes are "unknown" with a note.
- Every licence file in the root counts (LICENSE*, COPYING*, UNLICENSE); the
  most restrictive wins; permissive files combine with AND; an unrecognised
  file makes the project unknown. Headers add restrictions but never verify.
- checkVerdict requires every id of an expression to be whitelisted.
- Corpus test over real SPDX texts (21 licences) plus riders and edits; over
  the 1,415 LICENSE files in node_modules every plain whitelisted text matches,
  and the misses are mixed, notice-only or concatenated files (fail-closed).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation 4/8: the project config is a pinned control file; it cannot pick endpoints, keys or estimates

.factory/config.json was agent-writable and unpinned, and its project layer
could set embeddings.url plus apiKeyEnv (any env var sent as a Bearer token to
any URL) and the estimate rates behind the spend ceiling. The configured
licenseWhitelist was never used: the harvest plan took the model's list.

- .factory/config.json is a factory control file (editor and shell) and is
  pinned in the control baseline; a hand edit is drift until a human accepts it.
- New `es rebaseline` (TTY + typed code): accept hand edits to pinned control
  files (gates.json, config.json, frontier) without a full approval.
- A project file may not set `embeddings` or `estimate`; global config or
  plugin options only (clear error naming both files).
- es_harvest_plan may only narrow the configured whitelist; ids outside it are
  reported as ignored.
- Docs regenerated (schema descriptions).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation 5/8: undo the ReDoS-rewrite regressions in the gates

The CodeQL-driven rewrites traded correctness for regex shape:
- complexity: detectHeader dropped TS methods and arrows with return types
  (probe: `method(x: number): string {` was never measured, so the budget
  silently skipped it) and cut parameter lists at the first ")". It now matches
  parentheses by depth and accepts an optional `: ReturnType` before `{`/`=>`.
- tsc parser: `[^()]` lost every finding for paths with parentheses (Next.js
  route groups). Lines are now split at the diagnostic marker, plain and pretty.
- parseTags: brackets inside a tag body (a quote citing "[1]") ended the tag;
  the scan now matches depth.
- affected tests: the graph selected helpers under test/ as tests; only
  runnable test files are selected (helpers are still traversed), and bun gets
  "./" paths so arguments are paths, not name filters.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation 6/8: publish what users install (pack, smoke, then publish the tarballs)

- `npm publish` from the package folders would have shipped
  "@heretek-ai/es-core": "workspace:*" literally. Publishing now packs with
  `bun pm pack` (which rewrites workspace:* to real versions) and publishes
  exactly those tarballs with provenance.
- The CLI bundle inlined core, so core's assets resolved outside the installed
  package (probe: `es harvest scan` under Node failed with ENOENT on the
  licence templates). The CLI now keeps its dependencies external and uses
  core's own dist.
- scripts/pack-smoke.sh builds, packs, npm-installs the three tarballs, refuses
  a leaked workspace: protocol, runs `es --help` and a real harvest scan under
  Node, loads core's prompts and skills, and imports the plugin's server entry
  under Bun. CI runs it on Node 22 (the minimum engine); publish runs it first
  and publishes its tarballs. setup-node is pinned to the v7.0.0 commit SHA.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation 7/8: panels render for real and follow the run; capability rows cite our own proofs

- Panels fetched once and never refreshed, and their only test was a fake slot
  that never rendered JSX. They now refetch on the server's `changed` event and
  every 2 s while open (usePanelState), and RPC failures render as text.
- test/panels.test.ts renders all four panels with OpenTUI's headless test
  renderer and the reactive Solid build (subprocess with the Solid preload, so
  the transform stays out of the real-host tests) and asserts the frames,
  including a refresh after a change event and a failure state.
- New real-host tests for rows that cited only the M0 spike: per-seat skill
  scoping, primaries keep the question tool while subagent seats lose it, the
  factory invokes a hidden seat it may spawn, and compaction carries the
  factory state. The capability matrix now cites these tests; docs regenerated.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation 8/8: real, capped evals; headless runs in the right project; prior art with provenance

- Found while building the eval runner: `opencode run` resolves its project
  from PWD, not the spawn cwd. The headless driver inherited the caller's PWD,
  so `es --cwd <project> factory run --headless` drove OpenCode in the wrong
  project. It now sets PWD to the project root (test: a stub CLI that reports
  the PWD it was given; red before the fix).
- Evals were hollow (no cost cap, no plugin or provider wiring, checks that
  only looked for crash strings; the CI job always skipped). Now each case is
  one real `opencode run` turn in an isolated workspace (project, HOME, XDG)
  that loads this plugin with ES_EVAL_MODEL, graded from the JSON event stream
  (text, tools used / not used, error events) and killed at a per-case step
  cap or timeout — the stream has no cost events, so steps bound the spend.
  Cases check behaviour: the grill asks before writing the frontier, the
  brainstormer plans with es_brainstorm_plan, the factory with no run points
  at the grill and starts nothing. Tested on the real CLI with the fake model
  (skipped without the CLI). The nightly job installs the CLI's platform
  package (no install scripts) and runs only when the model and key are set.
- Brainstorm prior art was model-asserted. es_harvest_prior_art records its
  searches under .factory/runtime (agents cannot write it), and
  es_brainstorm_complete accepts only URLs a recorded search returned.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation: docs follow the fixes (limits, dogfood config, changelog)

- README "Known enforcement limits": config.json is pinned (es rebaseline),
  engine state is deny-write, licence detection is strict by design (mixed,
  notice-only and concatenated licence files are clean-room), agents harvest
  only inside the project, evals are opt-in and step-capped; pack-smoke in the
  development commands.
- DOGFOOD: commit the project config before approving the frontier (or
  es rebaseline later); embeddings/estimate belong in the global config.
- CHANGELOG: fail-closed licence verdicts and the tarball publishing path.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation: clear the SonarCloud quality gate without changing behaviour

The PR's quality gate failed after the remediation push (reliability D,
security B), mostly on older code under newer rules (S8786 super-linear
regexes, S7781, S7758, S7503, shell [[ ]]) plus a few in the new code.

- Each flagged regex is now a scanner or an anchored, linear test: mypy,
  jest and go-test parsing, TOML licence fields, the tar NUL strip, the import
  fallback, markdown links and ellipses in quotes, the hook verdict JSON,
  patch targets, go.mod and requirements pins, the run id, YAML keys and
  comments, kebab-case doc names. Every rewrite was diffed against the
  previous implementation (178 repo files for the import scanner, prompts and
  skills plus edge cases for YAML, quote/patch/go.mod/requirements/run-id
  samples): identical output. New parser tests pin mypy, jest and go test.
- Sort comparators, replaceAll, codePointAt, no async without await (the
  factory's mutate and the gates' timed now accept sync callbacks),
  crypto.timingSafeEqual for MAC comparison, git spawned by absolute path in
  the eval workspace (findExecutable), [[ ]] in v2-head.sh.
- The M0 spike suite (17/17) still passes with its probe plugin's promise
  executors rewritten without async.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* PR #18 remediation: resolve the two CodeQL alerts in the new code

- js/file-system-race (util/fs.ts readRegularFile): the lstat-then-open check
  is replaced by opening with O_NOFOLLOW, so a symlink fails to open and there
  is no check-then-use window at all.
- js/bad-tag-filter (spdx.ts header parsing): instead of deleting comment
  closers (`-->`, `*/`, `#`) from the header line, keep the expression itself
  (ids, AND/OR/WITH, parentheses) and drop tokens without letters or digits;
  the header test now includes an HTML-comment header.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* IUMBTEMS 1.1 M1: foundation — claims, tree, search policy, configure (#21)

Security: cached evidence is tool-only. A research or factory seat could
write .factory/research/sources/<sha256>.md with fabricated text (the
cache's tamper check is satisfied by construction) and cite it as
VERIFIED; proven on a real host before the fix. The source cache,
coverage.json, the research dossier, the signed brief and .factory/claims/
are now control files; es config set and es research retract are
human-only.

Claim stack (packages/core/src/claims/): content-hash claims with tag and
lifecycle status; a witness that re-checks quotes against the cache and
code locations against the file on disk; dossiers that refuse unwitnessed
claims (research emits .factory/research/dossier.json); retraction-driven
degradation; ClaimStore; a deterministic lexicographic rival ranker; the
signed proof-carrying research brief (es research export / verify-brief),
fixing legacy B3/B7/B8.

Design tree (frontier 1.1, breaking): kind/recommended/round; saves are
diffed against the previous tree (no deletions, reopen before changing a
settled answer, rounds never go back); the next round is returned; a tree
line in <factory-state> and the panel. Deferred fact nodes must be answered
as (fact:<id>) before research completes. grill v1 -> v2, factory v3 -> v4.

Search as policy: research seats get no host websearch/webfetch (proven on
a real host); es_research_fetch keys by canonical URL (ported exactly from
webcache.py) and serves fresh fetched snapshots. Config: research.depth,
cacheTtlDays, searchTimeoutS, searxngUrl (global-only); es config set is
human-only, schema-validated, refuses other control drift, re-pins.

Dogfood gate skipped by user decision (2026-10-07).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* IUMBTEMS 1.1 M2: seats — code-audit pair, OSS scout, es audit/scout, fires (#22)

Breaking: FactoryState v1 -> v2 (no migration; 1.0.x run states are refused
with the fresh-run instruction).

Code-audit pair (es-auditor-thesis maps invariants, es-auditor-antithesis
red-teams them with CWE ids). es_audit_verdict witnesses every finding on
disk — file inside the target, lines in range, excerpt verbatim — and a
hallucinated reference refuses the whole verdict (legacy never checked line
refs). Audits block (decision 2): a phase audit holds passPhase and a failed
one sends the phase back; an open/failed path audit refuses the next stage
transition. Splits go to the manager (es_tiebreak with audit, ranked findings
attached); only a human dismisses (es audit dismiss). Each round writes
records, a code-audit dossier and a deterministic REPORT.md.

OSS scout (/scout): plan, discover (shared with darkharvest), scan
(fail-closed license), OSV advisories cached and cited, witnessed
assessments; core recomputes every verdict — adopt only for verified,
whitelisted, permissive licenses — flags severe advisories, ranks, writes
the dossier and REPORT.md. Adoption stays human.

es audit <target> / es scout "<feature>": headless, human-only, honour STOP
and the ceiling; with no run, --max-usd N starts one at a TTY (decision 7);
the driver is checked before any side effect. A halted run now stops every
seat tool except es_status. Fire suites: audit-fires and scout-fires run
deterministically on the real host in CI; the same graders score nightly
model runs. New ENFORCED rows: claims, audit, scout.

Dogfood gate skipped by user decision (2026-10-07).

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* IUMBTEMS 1.1 M3: rigor — fires, packs, drift, queereye 02/03, docs (#23)

All five fire suites are merge-blocking: grill-fires (no deleted nodes, no
silent settled edits, no round regress; deferred facts gate research),
factory-gate (the build starts only through the approvals; STOP and the
spend ceiling halt), darkharvest-fires (depend only for verified permissive
licences) join audit-fires and scout-fires on the real host. The same
graders score the nightly model runs.

Domain packs are the full legacy port: quant, biopharma and legal
constitutions (tier weights, negative-knowledge bonus, reject penalty,
accept threshold, banned domains, mandatory tags, standard or
zero-tolerance retraction) selected with config.domainPack. With a pack
active, es_research_complete rejects claims that break the constitution
and refuses a score below its threshold; without one nothing changes.

docs:check now fails on prompt/skill/registry drift, with a canary that
proves each class is caught, and queereye phases 02/03 land under
.factory/design/: component specs (button, dialog, form-input, table, nav,
toast), the pinned MIT webref snapshot, the headless CSF play harness,
TUI lowering notes, and the full skill-harvest ledger bundle with its
cite-gate receipt (fail-closed MIT/Apache-2.0/BSD-3-Clause/ISC whitelist).

SYSTEM_ARCHITECTURE.md documents the pipeline, seats, claim flow and trust
invariants; the CHANGELOG carries the M3 entries.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Release 1.1.0: version bumps and a single VERSION source

packages/{core,cli,opencode} go 1.0.5 -> 1.1.0. The CLI's version moves
to src/version.ts (one source used by the CLI, the MCP serverInfo
fallback and the help banner), and a test pins it to the package version
— the old literal had drifted to 1.0.0.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* A3 hardening: control files are read-only-gated in every agent shell

An adversarial audit of the 1.1.0 candidate found that a non-seat agent
(the user's default agent) could write control files through interpreter
one-liners (python3 -c / node -e / bun -e) because the shell rule matched
only metacharacter mutations. Seats were covered; the non-seat path was
not, while the project claims control files are deny-write for all agents.

A control-file mention in an agent shell command is now refused unless the
command is provably read-only: every segment on the read-only allowlist,
with no redirection, here-doc or command substitution. Reading a control
file with cat still works for non-seat agents; interpreter one-liners,
in-place editing, redirection and substitution are refused. The forge test
now covers the shell path, and the core policy test pins the interpreter
cases.

Red check: reverting the condition turns both new tests red.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* Parity hardening: R11 full-key NK identity and legacy structural rules

A parity audit against 0.7.25 found two port drifts:

- R11: normalizeClaim capped the stored query/finding and nothing retained
  the full cleaned pair, so two NK rows differing only past 2000 code
  points collapsed to the same id and scored as one. Claims now carry
  nkKey (sha256 of the full cleaned pair); claimId and the pack scorer
  dedupe on it while the stored fields stay capped, matching legacy
  (storage truncated, identity and scoring full-key).
- The pack constitution now carries the legacy structural rules again:
  VERIFIED needs its hash and quote (or a code location), INFERRED its
  reasoning, HYPOTHESIS its falsification, NK its query and finding. The
  witness enforces the same field requirements. The legacy INFERRED
  parents-non-empty rule is documented as not applicable to 1.x REPORT.md
  claims (no parent ids are recorded there).

Red checks: dropping nkKey, the INFERRED rule, or the witness reasoning
check each turns its suite red.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: John Smith <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
2026-10-07 13:13:33 -04:00

129 lines
2.5 KiB
JSON

{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"audit": {
"type": "string"
},
"round": {
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
"seat": {
"type": "string",
"enum": [
"auditor-thesis",
"auditor-antithesis"
]
},
"by": {
"type": "string"
},
"verdict": {
"type": "string",
"enum": [
"pass",
"fail"
]
},
"notes": {
"type": "string"
},
"findings": {
"type": "array",
"items": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"invariant",
"vulnerability",
"debt"
]
},
"title": {
"type": "string",
"minLength": 3
},
"severity": {
"type": "string",
"enum": [
"critical",
"high",
"medium",
"low",
"info"
]
},
"cwe": {
"type": "string",
"pattern": "^CWE-\\d+$"
},
"file": {
"type": "string",
"minLength": 1
},
"lines": {
"type": "array",
"prefixItems": [
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
},
{
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 9007199254740991
}
],
"items": false,
"minItems": 2,
"maxItems": 2
},
"excerpt": {
"type": "string",
"minLength": 1
},
"detail": {
"type": "string",
"minLength": 8
},
"remediation": {
"type": "string"
},
"holds": {
"type": "boolean"
}
},
"required": [
"kind",
"title",
"severity",
"file",
"lines",
"excerpt",
"detail"
],
"additionalProperties": false
}
},
"at": {
"type": "string"
}
},
"required": [
"audit",
"round",
"seat",
"by",
"verdict",
"notes",
"findings",
"at"
],
"additionalProperties": false
}