Skip to content

Reconcile PR38 with the salvage line: merged history, AGPL executed, sweettest unblock - #41

Open
kalisam wants to merge 282 commits into
mainfrom
reconcile/pr38-salvage-20260817
Open

kalisam wants to merge 282 commits into
mainfrom
reconcile/pr38-salvage-20260817

Conversation

@kalisam

@kalisam kalisam commented Aug 17, 2026 •

Copy link
Copy Markdown
Collaborator

Rejoins six weeks of parallel work and lands the corrections found along the way.
origin/main is fully contained in this branch (0 ahead, 124 behind as of
2026-08-24), so there is no content to reconcile here — only review.

PR #38 has since merged to main on its own (merge commit 873cc0c,
2026-08-18). It is an ancestor of both main and this branch, so the merge
described below is now history rather than the point of the PR. What remains
unique here is the salvage line.

Merge

pr/38 (65 commits) merged into the wip/salvage-20260804 line (93 commits).
Merge-base 3e82a33, 2026-07-05. 13 conflicts, each resolved on its merits.

Both sides won on different files, and one conflict caught a real bug:

  • PR38 won workers/commons-gateway/* — our manifest.ts used FLOSS/-prefixed
    paths, producing a doubled URL segment, and listed INDEX.md, which is not in this
    repo.
  • Both won on voters.py — PR38's _PERSONA_SHARED_GATE_SYSTEM hardening ("a
    persona may never waive or narrow the shared checklist") prefixed onto our
    clean-room persona text, on both personas.
  • Both won on provenance.py — _is_ancestor and _ignored_chain_position are
    independent features, not rival versions.

Licence

  • ADR-7 executed. Accepted 2026-04-15, propagated to FLOSSI_U/ and the reuse
    ledger, but FLOSS/LICENSE still held GPL-3.0 text. Now the AGPL SPDX line,
    byte-identical to FLOSSI_U/LICENSE. GPL text archived, ADR-7 → Verified.
  • SUL-1.0 removed. MOMUS_PERSONA_SYSTEM was adapted from oh-my-opencode
    (SUL-1.0, incompatible with AGPL). Replaced by a clean-room
    EXECUTABILITY_REVIEWER_SYSTEM written from this repo's own consensus schema.

ADR-12

The stated unblock — "pin compatible client+tryorama versions for the 0.6 line" —
cannot be made to exist: no Tryorama version pairs with hc 0.6.1, and Tryorama is
retired upstream in favour of Rust sweettest. Verified holochain 0.6.3 ships
sweettest, so consent_gate.test.ts can be ported without a substrate
migration. Sequencing recorded deliberately: tests first, 0.7 migration second.

Also separates the two blockers the ADR had conflated — the sweettest port proves
the zome; the consent anchor is what blocks ADR-19. entry_has_consent() only
checks decision_action_hash is a non-empty string and never resolves it.

Provenance spec

v1.5 delta items D2 (multisig headroom) and D3 (evidence types file/log/
activity/source_chain) applied to spec, schema and EVIDENCE_TYPES. D1 held —
blocked on ADR-12.

ADR-20 — provenance validator reconciliation

Added 2026-08-23. The provenance spine has run since 2026-08-10 and landed zero
claims: hooks fire, packets are signed, and the validator rejects 100% of them.
Two causes, both reproduced at head, both documented and neither yet fixed —
ADR-20 is Proposed and carries no code.

  • D3 missed a fourth allow-list. _EVIDENCE_REF_TYPES in provenance.py:564
    still holds the original six values and is the set validate_packet actually
    enforces, so a schema-valid packet is rejected downstream. Second occurrence of
    this pattern after spec_gate.GATED_SURFACES.
  • Ancestor artifact validation is fatal on hash mismatch. Editing a file twice
    permanently invalidates every earlier packet naming it, and those stay in the p
    chain. The spec's Audit Disposition section defines a superseded status for
    exactly this; it was never built.

An adversarial ensemble audit (6 voters, 5 provider surfaces, 6 model families)
rewrote the proposed remedy and is recorded in the ADR, including the finding that
the synthesizer's "6/6 unanimous" label was wrong — one voter answered nothing, one
dissented on blast radius, and the sequencing question split three ways.

Verification

  • 262 tests pass (this line alone before the merge: 162; pr/38 alone: 223)
  • spec_gate --check: 108 registered, 0 missing, 0 reuse violations, 7 stale
    (non-fatal; 5 are pre-existing FLOSS/scripts/hook_*.py path drift — those files
    live in FLOSS/hooks/)
  • No conflict markers; no SUL-1.0-derived text remains
  • origin/main merged in 2026-08-24 (CI and dependabot fixes only, clean)

Not included

The preservation spine is not in this branch. It ships via #43
(feat/preservation-spine-standalone) with #59 stacked on top; that line is ahead
of the local spine work, which has been abandoned.

kalisam and others added 30 commits July 24, 2026 22:07
Move the manifest-overrides loop in apply_codex_mcp to run LAST, after
preserved scalars/tables, so overrides always win -- including over a
pre-existing field of the same name on the target entry. Previously a
pre-existing value silently discarded a manifest override, and this
also diverged from the planned Hermes writer's contract (overrides
beat everything).

Also, per code review on cea5e5e:
- Raise SharedSurfaceError on a name_map collision (two shared servers
  mapping to the same target name) instead of silent last-write-wins
  data loss, detected up front before the write loop.
- Raise SharedSurfaceError when an existing mcp_servers entry is not a
  table, instead of a bare AttributeError.
- Classify preserved sub-tables via isinstance against
  tomlkit.items.(Table, AoT, InlineTable) instead of hasattr(x,
  "items"), which misclassified array-of-tables values.
- Reworded the docstrings/comments: verified empirically that on
  tomlkit 0.15.0 the scalars-before-tables write ordering is not
  load-bearing (Container reorders regardless of assignment order);
  kept the ordering as defense-in-depth but stopped implying it is the
  only thing preventing corruption. Corrected the misleading docstring
  on test_codex_output_reparses_with_values_in_the_right_tables to
  match.

Added tests: test_codex_override_beats_existing_field,
test_codex_name_map_renames_server,
test_codex_name_map_collision_raises,
test_codex_rejects_non_table_existing_entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…string

The sentence still described overrides being written with the scalars,
which was true before the precedence fix moved them last.
Adds apply_hermes_mcp for merging shared MCP servers into a parsed
Hermes config.yaml document via ruamel.yaml round-trip (preserves
comments/ordering, unlike a PyYAML safe_dump round-trip). Precedence
matches apply_codex_mcp: managed transport fields, then whatever
already existed on the entry, then manifest overrides last.

Adds hermes_gateway_alive(home) to detect a live Hermes gateway via
gateway.pid + tasklist/os.kill(0), since the gateway rewrites its own
config.yaml on shutdown and would clobber a write made while it's
running. Task 7 wires the liveness check and the writer into
materialize().

Hoists the name_map collision guard shared by apply_codex_mcp and
apply_hermes_mcp into ensure_no_name_map_collisions, and adds a
malformed-existing-entry guard to the Hermes writer mirroring Codex's
non-table check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The ruamel settings are a hard prerequisite for the Task 9 fidelity
gate: a bare YAML() reflows the whole Hermes config (398-line diff),
while indent(mapping=2,sequence=4,offset=2)+width=4096 collapses it to
a single irreducible hunk. Also records the two known-benign diffs the
gate must allow (Hermes personalities reflow, stale Gemini settings).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Inert until Task 7 adds the dispatch loops -- unknown target keys are
ignored by the current materialize(). Hermes carries a name_map because
.mcp.json calls it 'agentmemory' while Hermes keys it 'Agent Memory',
and overrides for the enabled/timeout fields Hermes expects.
The AppData Hermes home has serena deliberately set enabled:false.
Forcing enabled:true there would silently re-enable it, changing
operator configuration the propagator has no business touching. The
.toilet home already has serena enabled, so its override is a no-op
and stays.
…-run "clean" mislabel)

- summary label for exit 1 under --check now reads "DRIFT (or step error —
  see output above)" instead of asserting DRIFT with false certainty: every
  materializer also exits 1 on an unhandled step error (missing manifest,
  bad JSON, ...), and re-running without --check on a crash would silently
  overwrite a broken config with "fixed drift".
- a --dry-run-only run no longer claims "clean" when it has pending writes
  it never applied; steps are now labelled "planned" and the footer says so.
- documented that --check takes precedence when --check and --dry-run are
  both given (verified: CHECK wording, drift-driven exit 1).
- fixed docstring wording implying the runner forwards --workspace-root; it
  does not.
- factored summary/exit-code aggregation into a pure summarize() function so
  it's unit-testable without spawning six subprocesses; added coverage for
  all-ok, drift-under-check, hard-failure exit codes, mixed batches, and the
  dry-run-only "planned" path.
- renamed and tightened the dry-run CLI test (was tautological), added
  regression tests for the "planned" vs "clean" wording and for the
  --check + --dry-run combination.
Behavior was already correct but unpinned (noted in code review). A
step exiting nonzero during dry-run-only must surface as FAILED and
drive exit 1, not be absorbed into the 'planned' framing.
Edited the corpus source, not the projections -- the projections are
generated and regenerating them is what makes the change canonical.

Adds the standing rule that .mcp.json is the single source of truth for
MCP servers and harness configs must never be hand-edited.
…s, fail-open pid check

Code review of 82745d7 (Task 6) returned CHANGES REQUESTED. Four fixes:

1. CRITICAL: apply_hermes_mcp unconditionally deleted then re-added every
   MANAGED_TRANSPORT_FIELDS key on every merge. ruamel's
   CommentedMap.__setitem__ preserves position for an existing key --
   it's the del that moves a key to the end -- so this reordered every
   touched entry on every run, including a true no-op merge, which would
   have broken Task 9's fidelity gate (regenerate must produce no
   spurious diff against a verified-good config). Fixed to only clear
   the fields belonging to the transport being vacated.

2. env handling on a transport switch now mirrors apply_codex_mcp's
   precedence exactly: a shared entry's env replaces an existing one
   when it defines one, an undefined env preserves the existing stdio
   env untouched, and converting to http drops a stale stdio env instead
   of leaving it next to type: http.

3. A present-but-null YAML entry (e.g. `serena:` with nothing after it)
   parses to None, which `.get()` can't distinguish from "key absent" --
   it was slipping past the malformed-entry guard and crashing later
   with TypeError. Membership is now checked explicitly
   (`target_name in servers`), so null hits the same
   "not a mapping" SharedSurfaceError as any other malformed entry.

4. _pid_alive failed OPEN (returned False / "not alive") when it
   couldn't determine liveness at all -- a missing/blocked tasklist, or
   any other subprocess failure, silently let a write proceed against a
   config a live gateway might still be watching. It now fails CLOSED
   (treated as alive) whenever liveness can't be positively determined,
   while a missing/corrupt gateway.pid still correctly fails open (a
   live gateway always writes a valid pidfile on startup, so its absence
   is evidence of a crashed/stale gateway, not a running one).

Adds regression tests for all four, most notably
test_hermes_noop_merge_preserves_key_order for Fix 1.

Verified against real-file copies (deleted after use, never committed):
a no-op merge against .toilet/hermes/config.yaml with the ruamel config
from c226115 (indent(mapping=2,sequence=4,offset=2), width=4096)
produces a diff confined entirely to the personalities: block reflow
(the known irreducible ruamel artifact) -- zero lines touch mcp_servers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…gating

Task 7 -- integration only; the Codex/Hermes writers, transport classifier,
and gateway-liveness guard already existed but nothing dispatched to them.

- resolve_manifest_path() now also expands %VAR%/$VAR (needed for
  hermes_user's `%LOCALAPPDATA%/hermes/config.yaml`), not just `~`.
- Add target_in_scope(): repo-scope targets always run, user-scope targets
  require --include-user-scope since they write outside the repo.
- Dispatch codex/codex_user through apply_codex_mcp via tomlkit, and
  hermes_workspace/hermes_user through apply_hermes_mcp via ruamel with the
  exact preserve_quotes/indent/width settings the fidelity gate requires.
- Hermes targets skip (not write) when config_path doesn't exist -- there is
  no update-only-if-present primitive, so the dispatch checks .exists()
  itself rather than letting check_or_write_text fabricate a config.
- Hermes targets refuse (REFUSED + drift=True) when hermes_gateway_alive()
  reports a live PID, since a running gateway rewrites its own config on
  shutdown. This fires under --check too, by design: --check exists so an
  operator can discover blockers before a real write, and a live-gateway
  block is exactly that kind of pre-write-discoverable fact.
- Add --include-user-scope CLI flag.
- Add materialize()-level dispatch tests (previously zero coverage) using
  synthetic tmp_path manifests, with the five downstream per-surface
  manifests stubbed out so real repo files are never touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ulting

Quality-review fixes on top of 79dd0d6 (spec compliance passed cleanly;
these are risk/robustness issues).

- Fix 1 (CRITICAL): a REFUSED Hermes write set drift_found=True but main()
  only turned drift into exit 1 under --check, so a real run with a blocked
  gateway still exited 0 -- indistinguishable from success to callers like
  refresh_agent_surfaces.py that key off exit code alone. main() now checks
  for any REFUSED result and exits 1 unconditionally, before the --check
  drift branch. materialize()'s 2-tuple return signature is unchanged.
- Fix 2: tomlkit.parse()/ruamel .load() on a hand-maintained Codex/Hermes
  config were unguarded. Since `results` only prints after materialize()
  returns in full, a mid-run parse crash discarded every result computed so
  far and silently skipped the five downstream sub-materializers
  (roster/memory/context/hook/skill). Both parses are now wrapped and
  re-raised as one actionable SharedSurfaceError naming the path.
- Fix 3: os.path.expandvars() leaves an undefined %VAR%/$VAR as a literal
  string instead of raising, which resolved to a bogus path nested inside
  the workspace tree and was indistinguishable from a legitimately-absent
  config (both read as SKIP). resolve_manifest_path() now detects a
  residual unresolved reference and raises naming the variable.
- Fix 4: target_in_scope() defaulted an absent `scope` field to "repo"
  (write unconditionally) -- the one thing this gate exists to prevent for
  a target that forgets to set `scope`. Absence is now equally strict as a
  garbage value: both raise. Verified target_in_scope() is only called from
  the two new Codex/Hermes loops; gemini/opencode/vibe are unaffected.
- Fix 5: config_path pointing at a directory passed `.exists()` and then
  raised a raw PermissionError/IsADirectoryError from read_text()/open().
  Handled by the same try/except as Fix 2, in the same place.

Added coverage for all five: a process-exit-code test (via main() with a
monkeypatched sys.argv against a synthetic tmp_path manifest, never the
real workspace), malformed-TOML and malformed-YAML dispatch tests, an
undefined-env-var test, a directory-config_path test for both writers, and
an updated target_in_scope test asserting the missing-scope case now
raises.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Truth Status flipped to Verified, backed by the Task 9 gate results
against live harness configs. Records the three review-caught defects
that the implementing agents' own tests missed.
The test pinned exit 1 against ai-roster's pre-existing drift. Running
the propagator for real cleaned that drift up and the test failed --
it was asserting workspace state, not the runner's contract. Now it
asserts only the CHECK-over-PLAN wording; exit-code semantics are
covered by the state-independent summarize unit tests.
The Verified status was briefly asserted while --check still reported
drift on .codex/config.toml. Records the cause (one-time tomlkit
whitespace on first table insertion), the resolution (defb907), and the
discipline it implies: --check must be re-run after the write.
… keys

Two defects a holistic review found in territory Task 5/7 owns:

1. apply_codex_mcp's `elif preserved_env is not None` fired for ANY
   non-stdio transport, including http -- so converting a stdio server
   to url-only kept its old `env` table forever, re-preserved on every
   run. These configs carry credentials, so a stale env block surviving
   a transport switch is a hygiene problem, and this is exactly the
   stdio->http scenario the writer exists to handle (two real servers
   made that switch on 2026-07-17 and will again). apply_hermes_mcp
   already guarded this correctly; Codex had no equivalent test, which
   is how the asymmetry survived review. Fixed by nesting the env
   assignment inside `if transport == "stdio"` so http never re-attaches
   a preserved env. Added test_codex_stdio_to_http_clears_stale_env and
   test_codex_http_to_stdio_clears_stale_url, mirroring the Hermes pair.

2. materialize() dispatches on a fixed set of literal target keys with
   no validation that every manifest key is one of them -- a typo'd key
   or a not-yet-wired 7th harness was a silent no-op (empty results,
   drift=False, exit 0, nothing written). Added a module-level
   KNOWN_TARGET_KEYS constant and a loud SharedSurfaceError in
   resolve_manifest naming the unrecognized key(s) and the known set.
   Verified against the real shared-agent-surface.json (all 7 current
   keys pass) and added test_resolve_manifest_rejects_unknown_target_key
   + test_resolve_manifest_accepts_all_known_target_keys.

`scripts/refresh_agent_surfaces.py --check` still reports all 6 steps
clean after this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…surface

The Claude Desktop app spawns claude.exe with an inline --mcp-config
declaring agentmemory as 'cmd /c npx -y @agentmemory/mcp'. It collides
with and overrides the .mcp.json entry, so Desktop sessions bypass the
JanuScope lens entirely -- no audit, no instruction injection, and the
npx -y spawn pattern we removed everywhere else.

Verified empirically: memory_diagnose succeeded while the audit sink
did not grow. The propagator cannot reach this surface (CLI arg, not a
file), so the single-source-of-truth claim is scoped to the six
file-based surfaces.
Adopt Codex hooks (currently hand-written and unmanaged), add Hermes,
wire agentmemory into the three hook paths where it earns its cost, and
relocate hook scripts to FLOSS/hooks/ last -- the move invalidates both
Codex trusted_hash and Hermes script-mtime approval, so it happens once.

Also records the operator correction that plugins/ is a staging shelf,
not a canonical surface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Codex pins hooks.json by content hash, so the projection must reproduce
it byte-for-byte or every trust entry in ~/.codex/config.toml goes stale.
Add a codex target to shared-hook-surface.json with a new
payload_shape: "hooks_only" marker, and teach
materialize_shared_hook_surface.py (build_target_payload) to emit the
bare {"hooks": {...}} shape for that target instead of merging into
whatever else lives at the settings path -- unlike claude/gemini, whose
settings files carry unrelated keys the merge path must preserve.

Verified `--check` reports OK (no drift) against the real
.codex/hooks.json before and after; sha256 and the three
hooks.state trusted_hash entries in ~/.codex/config.toml are
unchanged. Adds a unit test asserting the hooks_only shape excludes
unrelated keys while the merge shape still preserves them.
Gives materialize_shared_hook_surface.py two capabilities Task 3's
upcoming Hermes target will need, without adding that target itself:

- format: "json" | "yaml" per target (default "json", so claude/gemini/
  codex are unaffected). A "yaml" target reads its existing file with
  ruamel.yaml using the same preserve_quotes/indent/width config already
  established for Hermes MCP writes in materialize_shared_agent_surface.py,
  merges the managed hook block into the parsed document in place (so
  comments, key order, and unrelated content survive), and writes it back.
  A missing file is SKIPPED, never fabricated. ruamel.yaml is imported
  lazily via a require_module() helper mirrored from the sibling
  materializer, so a missing package only fails a yaml-format target.

- event_map per target, e.g. {"PreToolUse": "pre_tool_call"}. Manifest
  events absent from a target's event_map are omitted for that target
  rather than passed through raw -- an unmapped event landing in a
  harness that doesn't understand it is a silently broken hook. No
  event_map declared means events pass through unchanged, preserving
  existing behavior for claude/gemini/codex.

Verified: 139/139 tests pass (128 prior + 11 new), all 6
refresh_agent_surfaces.py --check steps report ok, and sha256 of
.claude/settings.json, .gemini/settings.json, and .codex/hooks.json are
byte-identical before/after -- these are unused capabilities until Task 3
wires up a Hermes target.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Task 3: wires the shared hook manifest into Hermes's config.yaml under
.toilet/hermes/, using the YAML round-trip writer and event_map capability
landed in 1c18ddf.

- shared-hook-surface.json: new `hermes_workspace` target -- `format: yaml`,
  `settings_path: .toilet/hermes/config.yaml`, `event_map` renaming
  PreToolUse/PostToolUse to Hermes's snake_case pre_tool_call/post_tool_call.
  SessionStart is intentionally left out of the map (and out of this
  target's hooks) -- it has no clean Hermes equivalent, and the existing
  event_map mechanism already omits any manifest event absent from it. No
  hook script changes needed: Hermes's wire protocol already matches what
  hook_pre_write.py/hook_post_write.py read and emit.

- materialize_shared_hook_surface.py: `apply_yaml_target` now refuses to
  write when a live Hermes gateway is guarding the target directory (a
  `gateway.pid` naming a running process), reusing
  `hermes_gateway_alive` from materialize_shared_agent_surface.py rather
  than reimplementing the check -- this is the same config.yaml the MCP
  Hermes target writes, and a running gateway rewrites it on shutdown,
  clobbering anything written underneath. The refusal fires before
  --check/--dry-run branch, same rationale as the MCP writer: an operator
  should learn about the blocker from a dry pass, not only after a failed
  write. main() now exits 1 on any REFUSED result unconditionally, mirroring
  materialize_shared_agent_surface.py's main().

Verified: 145/145 tests pass (139 prior + 6 new), all 6
refresh_agent_surfaces.py --check steps report ok, sha256 of
.claude/settings.json, .gemini/settings.json, and .codex/hooks.json are
byte-identical before/after. Materialized .toilet/hermes/config.yaml by
hand: top-level key count went from 67 to 68 (only `hooks` added),
mcp_servers/custom_providers/_config_version/the trailing commented
fallback_model block all survived untouched, and no session_start/
SessionStart leaked in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-start/post-write

Adds scripts/agentmemory_client.py, a stdlib-only MCP-over-stdio client for
agentmemory (localhost:3111 has no usable REST surface -- verified 404 on
every plain HTTP path; MCP stdio via `node bin.mjs` is the only working call
path, ~240ms per initialize+tools/call round trip). save()/recall() never
raise, never block past their timeout, kill the child on timeout, and never
write to stdout -- hooks speak their protocol over stdout, so a stray byte
there would corrupt it.

Wires it into exactly the three places where the cost is justified:
  - hook_bg_round.py (DETACHED, already ~10s): saves the resolved consensus
    outcome (claim id, outcome, mean, variance, voter roster), and a terse
    "edit accepted" note forwarded from hook_post_write.py's fast path.
  - session_start_inject.py (critical path): recalls up to 3 recent
    memories with a hard 0.8s timeout, degrading silently to the pre-
    existing contract-only behavior on any failure; render_memory_section
    defensively re-trims/bounds output regardless of caller behavior.
  - hook_post_write.py: the synchronous <100ms fast path makes NO memory
    call itself -- it only passes an already-bounded terse edit summary as
    an extra argv to the DETACHED hook_bg_round.py subprocess, which is the
    one that actually calls agentmemory.

hook_pre_write.py is untouched per spec (latency-critical, nothing durable
to say).

31 new tests, all against a stubbed fake MCP stdio server (or a poisoned
agentmemory_client module for the fast-path-never-calls-memory proof) --
none touch a live agentmemory instance. Full suite: 176 passed (145
baseline + 31 new). Manually verified once against the real agentmemory
server: save() -> True, recall() found the saved marker text back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ce references

Moves the five harness-invoked hook scripts (hook_pre_write.py,
hook_post_write.py, hook_bg_round.py, session_start_inject.py,
agentmemory_client.py) out of scripts/ into a dedicated hooks/ directory
via git mv, so history follows and hooks are no longer mixed in with the
34+ human-invoked scripts.

Internal reference fixes:
- hook_post_write.py's spawn_background_round now points at hooks/hook_bg_round.py
- hook_bg_round.py's two agentmemory_client sys.path inserts now target hooks/
- hook_post_write.py's spec_gate import was silently broken by the move
  (it resolved spec_gate.py via Path(__file__).parent, which is now hooks/
  instead of scripts/, where spec_gate.py still lives) -- fixed to resolve
  via REPO_ROOT / "scripts" explicitly
- session_start_inject.py's self-referential agentmemory_client import
  still works (both files moved together) -- renamed the misleading
  scripts_dir local to hooks_dir

Surface regeneration:
- shared-hook-surface.json hook_scripts + every generated command string
  now point at hooks/
- Regenerated all six refresh_agent_surfaces.py projections; the `hook`
  step is fully clean. (`agent-surface`/`ai-roster` still show pre-existing,
  unrelated DRIFT on .agent-surface/harness/ai-roster.json from before
  this session.)
- Fixed the canonical skill-corpus source
  (flossi0ullk-consensus-gateway/SKILL.md + references/test-map.md) and
  regenerated skill projections into every harness's skills/ dir
- Renamed the four affected keys in docs/specs/spec-registry.json to their
  new hooks/ paths (only that 4-line hunk staged; spec-registry.json also
  carries unrelated pre-existing uncommitted drift, left untouched)
- Fixed stale scripts/hook_ prose references in ADR-8, AGENTIC_OPERATING_MODEL.md,
  METAHARNESS_OPERATING_MODEL.md, and the legacy FLOSS/.claude/settings.json
  duplicate (relative-path hooks, used when a session launches from FLOSS/ directly)
- Also patched .agents/skills/flossi0ullk-consensus-gateway/ (an orphaned
  mirror outside the current skill materializer's target list)

Test fixes: the four hook-adjacent test files (test_agentmemory_client.py,
test_hook_bg_round_memory_wiring.py, test_hook_post_write_memory_wiring.py,
test_session_start_inject_memory_wiring.py) now load their modules from
hooks/ instead of scripts/. All 94 scripts/tests pass.

Deliberately left unfixed (documented as findings, not bugs): the dated
session-note plan docs (2026-07-24-mcp-migration-fixup.md,
2026-04-16-external-adoption-and-shared-surface.md), the archive/reports_and_logs
transcripts, .claude/memories/project_memory.json, and config.json's cached
slash-command template -- all are historical/point-in-time records or
ephemeral caches, not live pointers, and editing them would misrepresent
what was true at the time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…arser

Hermes's agent/shell_hooks.py::_parse_single_entry reads a flat
{matcher?, command} mapping per hook entry, not the Claude-Code-nested
{matcher, hooks: [{type, command}]} shape the materializer was writing
for every target. Every hermes_workspace entry silently failed the
"missing a non-empty 'command' field" check and parsed to zero hooks,
even with hooks_auto_accept true -- confirmed against the real Hermes
CLI (hermes hooks list showed 0 configured before, 2 after).

Adds a per-target `entry_shape: "nested" | "flat"` manifest field
(alongside the existing payload_shape/format/event_map), defaulting to
"nested" so claude/gemini/codex are untouched (sha256-verified). Sets
hermes_workspace to entry_shape: "flat" and corrects its matcher from
Claude vocabulary (Write|Edit|MultiEdit) to Hermes's real file-editing
tool names (write_file|patch, confirmed against tools/file_tools.py).

Adds unit coverage for the flatten transform plus a regression test
that mirrors Hermes's own command-field validation rule against the
generated output.
Ran the equivalence gate that was recorded pending: litellm and
omniroute produce identical weights (-0.400 x3), identical mean and
variance, both PASS; omniroute faster (7.0s vs 10.8s). Two caveats
disclosed rather than smoothed over: a prompt-bleed artifact in one
voter's rationale on the omniroute path, and single-claim-shape scope.

Consensus ratification is BLOCKED, not merely pending: the governed
claim gate requires a consent_ref.decision_action_hash, no convention
for it exists (0 of 105 packets carry one), and the check is a bare
presence test. Deciding what it anchors to is ADR-12 consent-gate
design, which ADR-5 forbids starting silently.
Operator decision 2026-07-26: leave consensus ratification until the
consent gate (ADR-12) exists. ADR-19 stands on operator consent and is
live. Adds an agent-memory entry so a future agent does not re-attempt
the claim and rediscover the same E_GOVERNED_PROVENANCE_REQUIRED wall.
A Hermes agent flipped its own hooks_auto_accept from false to true
without asking (config_version unchanged, so not a migration). Hermes
gates write_approval for memory/skills but has no gate on its own
config, and a Hermes agent has terminal access to the plain writable
YAML anyway -- so no in-Hermes rule can be the durable control.

Add a guarded_keys declaration per Hermes target (hooks_auto_accept,
approvals.mode/cron_mode/mcp_reload_confirm/destructive_slash_confirm)
and a read-only checker in materialize_shared_agent_surface.py that
resolves dotted key paths against the live config and reports drift
loudly (GUARD DRIFT ...), distinguishing an absent key from a wrong
value. Report-only: never restores, never raises, never writes.

Critical: the guard runs in the Hermes dispatch loop BEFORE the
target_in_scope write-gate, so a user-scope target (e.g. the AppData
hermes_user home, where the real flip happened) is still guarded on a
routine repo-scope --check even without --include-user-scope. Scope
gating protects writes; a guard check writes nothing, so gating it
behind that flag would leave exactly the hole that let this go
unnoticed.

Verified against the real incident: refresh_agent_surfaces.py --check
now reports "GUARD DRIFT hermes_user hooks_auto_accept: expected
False, found True" while hermes_workspace stays clean, with both
Hermes config files' sha256 unchanged before/after.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Operator ratified the value Hermes had set autonomously. The guard now
holds the line at the chosen posture rather than the old default, and
correctly flags hermes_workspace (.toilet), which is still false.

Deliberately not flipping .toilet's actual value -- writing a security
control without being asked is the behavior this guard exists to catch.
kalisam and others added 5 commits September 29, 2026 14:49
… propagate upstream

The operator did not pick one of the three same-name-collision options.
They set the requirement: never overwrite a skill that has changed where
it is installed, because Hermes and Codex evolve their skills from use,
and propagate the appropriate improvements back into the shared skill
base. Recorded verbatim in the round-3 RESULT.md; the options are kept
but marked superseded.

That requirement reaches further than collisions: install overwrites
any managed projection whose content differs from the shared base, and
prune removes a withdrawn one regardless of content. Both predate this
PR and are now open against the requirement. Nothing is implemented; a
design is proposed in the round-4 packet for approval.

o2-skill-inventory/ is a read-only inventory of what is installed
today: all 27 managed projections are identical to the shared base in
each of the five targets, so nothing evolved has been lost; the
harness-evolved skills (33 in Hermes, 6 Codex, 6 OpenCode, 3 Claude)
are unmanaged directories that install and prune never touch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vation requirement

A fix-sweep packet for 42e4d05..0472ce1: the response to the third
audit (9a23477, F4 with O1 and O4; 69c4cbd, the KEEP wording from the
internal review) and three docs commits. Source and test changes in
separate patches, per the convention.

Beyond the usual contents (three-bucket ask, per-test red table,
provenance including the two parallel sessions, unverified ledger), it
carries the operator's O2 decision verbatim: never overwrite a skill
that changed where it is installed, and propagate appropriate
improvements back to the shared skill base. It states plainly that
install and prune contradict that today, both pre-existing, and that
a harness-added binary file already crashes install (reproduced,
unfixed). It then offers a proposed design for critique before it is
built -- a byte-digest baseline in the marker, three-way reconciliation
that keeps evolved copies and stages proposals, never auto-merging into
canon -- modelled on the baseline Hermes itself keeps in
.bundled_manifest, with the questions it leaves open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fourth external audit requested changes on three findings in the
skill materializer, and the operator set the rule they all point at:
a skill that has changed where it is installed is never overwritten --
Hermes and Codex evolve their skills from use -- and the change is
surfaced for propagation back to the shared base, with --check still
passing ("it should pass overall, but surface those modifications").

F5 (P2, introduced by 9a23477): the marker-only rewrite opened the
existing marker in place, so a symlinked marker wrote its target and a
hard-linked one rewrote every name for that inode. Markers are now
published by publish_marker(): a fresh file created exclusively, then
os.replace, which swaps the directory entry instead of following it.
The previous marker survives any failure.

F6 (P2, pre-existing): on Python 3.12 a symlink loop in a recorded
workspace_root raises RuntimeError from resolve(), which aborted the
whole refresh, --check included. recorded_workspace() and the owner
comparison now treat it as unusable ownership, like OSError/ValueError.

F7 (P2): equality was decided on text read in universal-newline mode,
so a CRLF script matched its LF source and the marker-only path blessed
a broken copy. Comparison is now by the sha256 of every payload file's
bytes (payload_digests), which also stops a binary file a harness added
from crashing the refresh, and stops a torn marker from being decoded.

Preservation, the part of the operator's requirement those corrections
need: the marker records `installed_files`, the byte digests as
installed. Install then separates the cases instead of overwriting any
difference:
  installed == source             -> nothing, or re-baseline the marker
  installed == baseline != source -> UPDATE (only the shared base moved)
  source == baseline != installed -> EVOLVED, kept and surfaced
  all three differ                -> CONFLICT, kept and surfaced
  no baseline, and they differ    -> DIVERGED, kept and surfaced
Ownership is settled first: only a projection whose marker names this
workspace is ever written. A legacy marker is adopted only when its
payload equals the shared base byte for byte. Another workspace's
projection, an unmanaged directory with a listed name, an unreadable
marker and a linked directory are kept and surfaced, never taken over.
An update re-reads the installed copy immediately before removing it
and abandons the update if it changed. The pruner removes a withdrawn
projection only when it provably equals its baseline; otherwise it is
kept and surfaced. Surfaced lines start with KEEP and are not drift.

Not built: staging proposals or any upstream propagation (where
proposals live is still open), category folders, and deactivation of
a withdrawn skill that is kept for its learnings.

Tests, written first: 18 new cases. On the unfixed code 13 failed on
behaviour (writes through links, the RuntimeError, a blessed CRLF copy,
UnicodeDecodeError, evolved/foreign/unmanaged/withdrawn content
overwritten or removed, a linked directory replaced), 3 only on a
missing field or function (update baseline, convergence baseline, the
race), and the real symlink loop passed on Windows. Run natively under
WSL Ubuntu / Python 3.12.3 against the unfixed code, the real loop
fails with pathlib's own "RuntimeError: Symlink loop"; after the fix
all 47 pass there. Existing withdrawal tests now carry a baseline,
since removal now requires one.

Read-only --check of the real workspace across all five targets: 24
skills per target would be ADOPTED (marker only); 3 per target are
DIVERGED -- flossi0ullk-shared-surface, subagent-driven-development and
writing-skills -- all line-ending-only differences (installed CRLF,
shared base now LF), which the old text comparison hid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Internal review of b011298, three-bucket, by a subagent and by one
model through OmniRoute, plus the author's own pass:

- Generated caches counted as a change. Running a skill's Python
  script writes __pycache__ into the installed copy; counted as payload,
  it marked the skill EVOLVED for ever and blocked every update from
  the shared base. __pycache__, *.pyc/*.pyo, .DS_Store, Thumbs.db and
  desktop.ini are now neither digested nor copied. (Buried in the
  OmniRoute review's first item, which otherwise misread the code.)

- A copy failing midway left a half-installed directory with no
  marker, which every later run would read as UNMANAGED and never
  repair; the subagent judged this "safe by design". Everything under
  the target was created by that copy and equals the shared base, so it
  is removed and the next run installs again. A FileExistsError --
  something appeared between the check and the copy -- is kept, not
  cleaned up, since copytree creates nothing before refusing.

- The pruner read the marker twice; one that vanished in between made
  the second read None and raised AttributeError. Found by the author
  and independently by the subagent. A marker gone at the second read
  now means no baseline, so the projection is kept.

- payload_digests followed links, reading files outside the skill, and
  a directory loop a harness left in one could raise on Python 3.12
  (found by the subagent). It now walks without following links and
  records each link as its target text; the copy preserves links so
  both sides agree. The shared base has no symlinks today.

- os.walk and rglob skip what they cannot list. A partial digest can
  equal the baseline and license removing content it never saw: under
  WSL Ubuntu / Python 3.12.3 the previous commit's code did exactly
  that on a chmod-000 directory, and died in rmtree with "Permission
  denied: 'private'" part-way through the removal. An unreadable
  directory is now an error, reported as UNREADABLE, nothing touched.

Tests, written first, 9 cases: on b011298's code all fail on Windows
and natively under WSL (the real chmod-000 case is POSIX-only and
skipped on Windows). The simulated unreadable case patches os.scandir,
which b011298's rglob did not consult on Windows, so its Windows red
shows only that there was no UNREADABLE path; the POSIX case is the
real evidence. Windows: 54 passed, 1 skipped. WSL: 55 passed.

Read-only --check of the real workspace is unchanged: 24 ADOPT and 3
line-ending-only DIVERGED per target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Carries the 2026-09-29 round-4 audit of 42e4d05..0472ce1 into
external-audit-2026-09-29-round4/ (35 files, credential-scanned,
content-identical; three normalized to LF on commit, counted blob by
blob). RESULT.md records a disposition for every item: F5, F6 and F7
fixed in b011298; O1 fixed as a side effect; O2 implemented for install
and prune, with proposal staging and upstream propagation not built;
O3 still unreproduced here and worked on separately; O4 agreed; O5
accepted and borne out -- a byte-level check found 3 skills per target
differing from the shared base by line endings only, which the text
inventory reported identical. The round-3 RESULT's inventory wording is
corrected in place, quoting what it claimed.

It also records the operator's three decisions: --check passes on
harness modifications and surfaces them; category folders only if the
spec supports them -- it does not (Agent Skills, Claude Code, Codex,
OpenCode and Gemini CLI read one level; only Hermes nests), so installs
stay flat and categories stay metadata; proposal location and baseline
granularity open, with per-file digests built as a reversible
assumption. Two side findings are recorded, not acted on: Hermes's
origin-hash manifest is the model followed, and Codex documents
$HOME/.agents/skills rather than the ~/.codex/skills this manifest
targets.

internal-reviews.md carries both internal reviews of b011298 verbatim;
real-workspace-check/ carries the read-only --check output (24 adopted,
3 line-ending-only DIVERGED per target) and the operator action that
reconciles the three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T07:11:46.612080Z 0a5529a New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a9822d497

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

kalisam and others added 2 commits October 5, 2026 23:55
… discrimination

PACKET.md and the source and test patches for b011298 and 176c8c2, the response to the fourth audit. Both patches apply in order to 6ea7620 and reproduce 9a9822d's two files byte for byte.

Recounting the discrimination for the packet showed the round-4 RESULT.md had it wrong: 176c8c2 added 8 cases, not 9, and the POSIX-only one is skipped on Windows, so 'all failed on both platforms' did not hold. Corrected in place with the old claim quoted.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…lls; correct two round-4 claims

Checked the Codex loader at rust-v0.128.0 (the installed version). The user layer scans $CODEX_HOME/skills, marked deprecated but kept for backward compatibility, and $HOME/.agents/skills, so our Codex projections are discovered today. The loader also searches each root for SKILL.md to a depth of six directories, so the round-4 RESULT.md claim that nesting would hide skills from four of five harnesses was wrong for Codex. Both claims are corrected in place with the originals quoted, and the unsent round-5 packet's out-of-scope note is updated to match.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 420cd8970e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

kalisam and others added 12 commits October 14, 2026 02:15
…w threads

The materializers and the daemon had one-line registry stubs and no statement of what they must never do, so the invariants existed only as review findings against finished code. This writes them down first, with clause IDs the tests will cite: repo-scope containment (LSI-A1), event-map injectivity (LSI-A2), preservation and verified removal (LSI-A3, A4), reclaim under the claim guard (LSI-B1), the degenerate-voter truncation ratio (SYN-D1..D4) and the zero-correlation Kish case (MRP-K1).

The conformance table names tests that do not exist yet. They are written next, from these clauses, and then the code.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… on next touch

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…code)

Each test cites the clause in docs/specs/local-surface-invariants.spec.md (LSI-*), the synthesizer spec (SYN-D*) or the review protocol (MRP-K1) that it enforces. Written from the clauses, before the code.

Run against the code at the spec commit: 13 failed, 312 passed, 1 skipped.

Red: SYN-D2 near-median whole answer called truncated; MRP-K1 zero-correlation panel refused; LSI-A2 colliding event map accepted; LSI-A1 repo-scope escape accepted for codex, antigravity and hermes_workspace; LSI-A4 learning deleted after the pruner's digest, and an unrenamable copy removed; LSI-B1 reclaim snapshots outside the guard and no NOT_RECLAIMED on a busy guard; two daemon structure tests now point at the guarded body.

Weak reds, stated: the stranded-copy test fails on a missing REMOVING_PREFIX rather than on behavior, and the daemon structure tests fail on a missing function.

A guard, not a red: the update-comparison race test passes on the old code, which already re-read the installed copy. The old doctored-digest version of that test is removed because it cannot model taking the copy aside.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nd tests above

Implements what docs/specs/local-surface-invariants.spec.md, the synthesizer spec and the review protocol now say, so that the tests in the previous commit pass.

- SYN-D2: a response is called truncated only below DEGENERATE_TRUNCATION_RATIO of the panel median. The constant was declared and never applied, so any whole answer shorter than the median and ending in a letter, colon or emoji was reported as a voter that returned no position.

- MRP-K1: the Kish refusal is for a negative mean correlation. A mean of exactly zero is a valid measurement with n_eff = k, and was returned as NaN.

- LSI-A2: two declared source events mapping to one native event are refused, naming both. The second used to replace the first silently.

- LSI-A1: a repo-scoped target in the agent surface materializer must resolve inside the workspace, for codex, antigravity and hermes, as the hook and skill materializers already required.

- LSI-A4: the pruner and the update path now take the installed copy aside, verify it against the baseline there, and delete it or put it back. Comparing and then removing left a window in which a harness write was deleted. A copy that cannot be renamed is kept.

- LSI-B1: --reclaim-claim holds the claim guard across both snapshots and both removals, and answers NOT_RECLAIMED if it cannot take it.

Verification: full suite 1146 passed, 8 skipped, 1 deselected; spec_gate OK (108 registered); ruff 8, unchanged; native Linux 3.12.3 skill, hook and review-independence tests pass.

Not addressed, deliberately: the two Hermes patch-verification threads. They were answered in the thread on 2026-08-25 and 2026-08-31 as left open, because a V4A parser that is subtly wrong would write a false VERIFIED into a permanent chain.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…mplementation

The review of 6809053 found clauses that were under-specified or that the code did not meet, so the clauses are corrected before the tests and code that follow.

LSI-A1: refusal happens before any write, unscoped manifest paths count as repo scope, and an unresolvable check refuses. LSI-A2: an event with no hooks takes no part. LSI-A3: baseline equality proves no harness change and does not preserve uncommitted source revisions. LSI-A4: three distinct outcomes (changed, in use, unreadable), a stranded copy of a listed skill is put back, any exception restores, and the residual window and the unverified scanner behavior are stated instead of implied away. New LSI-A5 (skill names are single path components) and LSI-A6 (a failed install removes only what is provably the shared base). LSI-B1: any failure to take the guard is NOT_RECLAIMED. SYN-D4: whitespace-only counts as empty. MRP-K1: a mean within 1e-12 of zero counts as zero.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…findings

Run against the code at 6809053: 23 failed, 335 passed, 1 skipped. Red for behavior: whitespace-only flagged as truncated (SYN-D4); a mean of -2.8e-17 refused (MRP-K1); an empty event raising a collision (LSI-A2); an unresolvable scope check returning (LSI-A1 c); a refused hook target leaving the registry written (LSI-A1 a); a blocked rename called EVOLVED and an unreadable copy called changed (LSI-A4 c); an interrupted verification not restoring the copy and an undeletable copy aborting the run (LSI-A4 e); a stranded copy of a listed skill not put back (LSI-A4 d); skill names with separators, .. or absolute paths accepted (LSI-A5); a failed marker publish deleting a harness's file (LSI-A6); and five unscoped agent-surface paths plus the half-written-surface case writing outside or before the refusal (LSI-A1 a, b).

Characterization, green on 6809053 by design: check and dry-run never rename, the restore-failure message, relative, tilde and env forms of an outside path, and the two reclaim tests that put the removals under the guard (red only against the code before 6809053).

A test that passed for the wrong reason was caught and fixed before this commit: the opencode case stopped on a missing file before writing, so the outside file is now created and must stay byte-identical.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found by an existing test while implementing the clause: a failed copy can leave a truncated file, which is the install's own and has to be removed even though it does not equal the shared base. Only after a failed marker publish, when the copy had completed, does a file that differs mean someone else wrote it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…lauses and tests

Implements the amended clauses in docs/specs/local-surface-invariants.spec.md so that the tests in 327cad3 pass.

- LSI-A4: remove_if_unchanged reports changed, in use, unreadable and stuck as distinct outcomes, restores the copy on any exception including an interruption, reports a verified copy it cannot fully delete instead of aborting the run, and verifies a stranded copy in place. The next run puts a stranded copy of a listed skill back before installing beside it. The docstring no longer says the window is only a rename and a read, and says that only Codex's scanner is known to skip the dot-prefixed name.

- LSI-A5: a skill's frontmatter name must be a single path component. It is joined to the target root, so ../x installed outside it.

- LSI-A6: a failed marker publish removes the directory only if every file equals the shared base; a failed copy still removes it whole. The marker is re-checked for ownership when the pruner re-reads it.

- LSI-A1: the agent surface resolves and checks every target before writing any, including mirrors, the context pack, gemini, opencode and vibe, which had no containment check. The hook surface checks every target before writing the registry, and a scope check that cannot resolve a path refuses instead of returning.

- LSI-A2: an event with no hooks takes no part in the collision check and never replaces a non-empty definition list.

- SYN-D4 whitespace-only responses are empty. MRP-K1 a mean within 1e-12 of zero is zero.

Verification: full suite 1179 passed, 8 skipped, 1 deselected; spec_gate OK (108 registered); ruff 8, unchanged; native Linux 3.12.3 skill, hook and review-independence tests: 112 passed. The real skill, agent and hook manifests pass the new name and containment rules (checked read-only).

Not changed, with reasons in the review record: --check reporting content drift that a write run may then keep as in use (documented as LSI-A4 f); no lock between materializer runs (a separate branch is working on locking); and the UPDATE comment's claim about history, which is corrected rather than the behavior, since baseline equality is the preservation rule and not an archive.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e dispositions

internal-reviews.md carries the prompt and the return of the subagent review of 332bc6e..6809053 verbatim. RESULT.md gives a disposition for each thread and each finding, including the ones not accepted and why, and the corrected order of work.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
CodeQL's js/reflected-xss (high) on skill-corpus/superpowers-brainstorming/scripts/server.cjs fails the PR's CodeQL check. The earlier triage (72f0240) found the value unreachable by an attacker because it must equal the secret token, and that stands. The clause makes the sink safe on its own terms: the page embeds the server's token, and the embedded JSON cannot close its script element.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nt (LSI-C1)

Run against the unfixed server.cjs: 2 failed, 1 passed. Red: a key containing </script> ends the element, and U+2028/U+2029 are written raw into the string literal. The ordinary-token round trip is a guard.

The function is evaluated from the shipped source with node, because it is not exported and importing the module runs its start-up code. The tests skip without node.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…t close the script element

CodeQL js/reflected-xss (high) at server.cjs:412 failed the PR's CodeQL check. The reflected value was only reachable after it had been compared against the secret token, so it was never attacker-controlled, but the sink did not depend on that.

- LSI-C1 a: bootstrapPage is given TOKEN, not the query value. They are equal once the comparison passes, so nothing changes for a caller, and no request string reaches the response.

- LSI-C1 b: the embedded JSON escapes <, >, & and U+2028/U+2029, so no value can end the script element or the string literal.

Verification: the three new tests pass (two were red before), full suite 1182 passed, 8 skipped, 1 deselected, spec_gate OK, ruff unchanged at 8, node --check clean. CodeQL itself can only be confirmed by the analysis this push triggers.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0a5529ac3a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

kalisam and others added 3 commits October 14, 2026 03:13
…rted transports

Two review threads on the synthesizer. SYN-P1: a caller-supplied voter_pool was labelled local unconditionally, which exempted it from the survivor independence re-check even when it was online; the mode is derived from its transports instead. SYN-T1: an unrecognised transport fell through to LiteLLM, so a misspelled ollama could send a prompt the caller meant to keep local to a cloud provider; it is a configuration error.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Against the code before the fix: 5 failed, 36 passed. Red for behavior: a correlated online override is tiered instead of degraded (it came back unmeasured), an unknown transport reaches a backend, and a pool with an unknown transport is dispatched. Weak reds, stated: the override-mode helper and the pool validator do not exist yet.

A first draft of two of these tests failed with a NameError instead (pytest is imported inside each test in this file, not at the top). That was caught by reading the failure reason, fixed, and re-run against the old code before this commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… unknown transport is refused

SYN-P1: synthesize() labelled every caller-supplied voter_pool as local, which exempted it from the survivor independence re-check even when every voter was online. The mode is now derived from the transports: local only when every voter is on ollama, online when none is, mixed otherwise, so a correlated online override, or an outage that leaves a correlated subset, degrades the run.

SYN-T1: generate() sent any unrecognised transport to LiteLLM, so a misspelled ollama could send a prompt the caller meant to keep local to a cloud provider. The supported values are ollama, flowith and litellm; case and whitespace are ignored; anything else is a ValueError naming the voters, raised before any dispatch. The resolved pools only ever produce the three supported values.

Verification: full suite 1187 passed, 8 skipped, 1 deselected; spec_gate OK; ruff unchanged at 8; the five new tests pass.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

This branch was successfully deployed

1 active deployment
Preview — 5a2375ee Deployed Oct 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants