Commit Graph

3 Commits

Author SHA1 Message Date
joakimp 02af927f26 feat(pi-task): separate WATCHED roots from WRITABLE ones, and catch a real violation
Closes the gap the reliability testing left open: no real child had ever tripped
the boundary diff. T2 could not do it, and the reason is structural rather than
bad luck — with read_only: true a write is DEFIANCE, and a well-behaved child
refuses, so the detector never runs against a real delta.

Fix: `roots` is now the WATCHED set and `write_allowed` the CHANGEABLE subset. A
violation is then producible by a child that is OBEYING, which is also the
realistic hazard: nobody's agent defiantly rewrites a repo, but plenty of commands
leave artefacts behind.

T4, run to prove it: write task in root A, plus an instruction to verify a module
in root B (watched, NOT writable) with `python3 -m py_compile`. The child obeyed
perfectly — status=ok, typo fixed, module compiled — and still tripped the diff,
because py_compile dropped __pycache__/ into B. Exit 1, violation named, and A's
authorised edit correctly NOT flagged.

It also served as the in-anger test of this morning's --ignored fix: __pycache__/
is gitignored in B, so `git status --porcelain` reported B as CLEAN on the very
same event that `--porcelain --ignored` caught. Pre-fix, T4 would have PASSED.
The fixture test said the same thing; this says it about a real child.
2026-09-07 21:22:45 +02:00
joakimp 5d503e191f fix(pi-task): boundary diff missed IGNORED files; test the git path; adversarial results
Found by the reliability testing, in the tool's own security-relevant check:
boundary() used plain `git status --porcelain`, which OMITS ignored files. A child
writing .env, a credential, or a build artefact into a root therefore read back
as CLEAN. Measured on a fixture repo: an ignored secret.txt produced ZERO
porcelain lines, and `!! secret.txt` once --ignored was passed.

Now always `status --porcelain --ignored`, keeping a sha256 + entry count and
substituting it for the text above 8 KB so a node_modules tree cannot dump
megabytes into every audit dir. Also detects a root whose kind changes.

selftest grows 10 -> 14 checks. The GIT path had NO coverage at all before this
(only the manifest path did), despite being what every real run uses: now covers
clean-repo, ignored-file (the regression), modified-tracked-file, and HEAD move.

Adversarial suite documented in the README: T1 false premise -> correctly
status=failed; T2 tempting write under read_only -> refused, repo verified
untouched by a second route; T3 poisoned caller-asserted fact (wrong node pin)
-> contradicted the caller from the file, and corrected the downstream inference.
T3 is the important one: caller-asserted context.facts is a confabulation vector
this design introduces, and it held.

Still untested, stated in the README rather than implied: no real child has ever
tripped the boundary diff (T2 refused), everything so far is read-only analysis,
nothing iterative, and all specs were written with more care than a rushed one.

Also: add .gitignore (repo had none) and remove the bin/__pycache__ I left behind.
2026-09-07 21:00:47 +02:00
joakimp f89439e667 feat(pi-task): prototype headless subtask runner with a verified result envelope
`fork` passes the child getHeader()+getBranch() -- the whole untrimmed parent
branch -- so in a long session it continues the parent's narrative instead of
doing the task (4/4 dispatches on 2026-09-06 ignored their brief; one filed a
diary entry as the parent). Upstream considers that by design.

bin/pi-task inverts the defaults: context is an explicit, default-empty JSON
spec; the child is a fresh isolated session with --no-extensions (so the
mempalace bridge, an extension, cannot file anything under our identity); and
the answer must parse as a declared envelope or the task is recorded FAILED
regardless of how fluent the prose was. Adds a post-hoc boundary diff over
roots[], a per-run audit dir, wall-clock kill and post-hoc cost accounting.

`pi-task selftest` feeds the validator 1 known-good + 6 known-bad envelopes and
a two-sided boundary check, and aborts if any pair fails to discriminate.

Measured while building, and documented in the README rather than smoothed over:
  * a fresh session removes the parent's VOICE but not slot-filling -- given a
    self-contradictory spec, a zero-context child invented a task, read the
    README and returned a well-formed envelope nobody asked for. Fresh context
    fixes continuation, not confabulation.
  * read_only is VERIFIED, not enforced: pi has no tool allow/deny list, so
    --no-extensions leaves core read/write/edit/bash in place.
  * a pointer is checked for presence, not checkability ("arithmetic fact" passes).
  * budget.usd is post-hoc; only wall_s is enforced.

Deliberately NOT wired into install.sh: per the 2026-09-06 decision, bake only
after the envelope has been beaten up on real work. First real run is committed
as examples/task-mempalace-pi-adapter.json.
2026-09-07 20:33:02 +02:00