Repository navigation
Releases: keith-nielsen/2026-AI-Value-Memory-Mining
Release list
v0.1.57
The freeze release. Value Memory Mining is splitting: the Claude- and GitHub-specific governance
tooling moves into its own project, RatchetHub, and Value Memory Mining becomes a simpler package
that depends on it. From this release onward the repository is frozen to fixes only: the
protocol and the harness take no new features.
- Still accepted: security fixes, correctness fixes, and Dependabot security updates.
Dependabot version updates keep opening PRs (.github/dependabot.yml); merging one is a
fix-only judgement each time. - Security reports are still reviewed (SECURITY.md), and the fork quick-start in
the README remains the supported setup. - v0.1.57 is the reference both successors cite for "the governance as it stood at the split".
Covers the four pull requests merged since v0.1.56: one archived change, two bare docs changes and
one Dependabot bump. Dated in UTC; the archived-change directory carries the UTC day it was applied.
| PR | Change |
|---|---|
| #122 | @fission-ai/openspec 1.12.0 → 1.13.1 (Dependabot) |
| #141 | The Phase 0 freeze brief for the split (docs/phase-0-freeze-brief.md) |
| #142 | The README carries the planned-split notice naming RatchetHub |
| #143 | The commit gate's secret scan covers renamed files |
Added
- The Phase 0 freeze brief (#141,
docs/phase-0-freeze-brief.md): re-checks the three route
findings for the split, reviews freeze practice, and sets out this release's route. - The planned-split notice (#142): a note under the README's badge row naming RatchetHub and
stating the freeze.
Changed
- OpenSpec CLI 1.12.0 → 1.13.1 (#122). It now writes
openspec/config.yamlwhen a change is
created; this repository does not track that file.
Fixed
- The INV-7 secret scan covers renamed files (#143, change
secret-scan-acmr). The commit gate
read--diff-filter=ACM, so a file moved and edited in one commit was staged as a rename and never
scanned; a test showed a HIGH-tier token committed as a 96% rename. The filter is nowACMR.
access-controlgains the scenario "A renamed file is scanned", and ADR-0036 carries a dated
amendment. A pure rename of a file that already holds a credential now blocks the commit too;
measured exposure at release was 0 HIGH matches in the vault and the repository.
v0.1.56
Covers the twelve pull requests merged since v0.1.55: eight archived changes, one bare driver fix,
two Dependabot action bumps and one bare docs change. Mostly control correctness (the outbound
guard, hook registration, the pre-flight interpreter), supply-chain pinning, and the standing-conduct
runbook. Dated in UTC; the archived-change directories carry the UTC day each was applied.
| PR | Change |
|---|---|
| #128 | The vault template seeds the GitHub read-hosts (hardening item 38) |
| #129 | reconcile detects deployed-but-unregistered git hooks (hardening item 35) |
| #131 | The outbound guard judges commands, not prose, and never fails open (hardening item 33) |
| #132 | A foreign-branch saved plan is guarded by its object, not the checkout (hardening item 42) |
| #46 | actions/setup-node 6 → 7 (Dependabot) |
| #47 | actions/setup-python 6 → 7 (Dependabot) |
| #133 | Every action is pinned to a commit SHA; update cooldowns are declared |
| #134 | The standing-conduct runbook, delivered by a SessionStart pointer in both roots |
| #135 | /vmm-repo-github is eliminated; its three unique items move into existing guidance |
| #136 | The conduct runbook's cold-start check is agent-measured, not operator-observed |
| #137 | The pre-flight refuses an interpreter that cannot run the suite |
| #139 | The living docs use the full name "Value Memory Mining" |
Added
- The standing-conduct runbook (
96-Runbooks/agent-conduct-standing-rules.md, #134): 27 rules in
the runbook format, each with its cost, date, andterminalorgate:. SessionStart in both roots
names it as its first output line, inside the preview, andsession-bootstrap-loaderstep 2 points
to it. #136 corrects its §Verification: the cold-start preview check is agent-measured, because
the preview is delivered to the agent and never shown to the operator. reconciledetects git hooks that are deployed but unregistered (#129). When
99-Operations/hooks/holds hooks butcore.hooksPathdoes not point at it,reconcilenames the
fix and counts it as drift. It never setscore.hooksPathitself (INV-3: detect, never auto-fix).- The pre-flight checks its own interpreter first (#137). If the jobs' interpreter lacks a
distribution named intests/requirements.txt, it prints REFUSED, runs nothing, gives no verdict and
exits 3. An environment defect is no longer reported as a defect in the change.maintenance
gains one requirement. - The vault template seeds the GitHub read-hosts (#128):
api.github.comandgithub.comin
sandbox.network.allowedDomains, plusWebFetch(domain:github.com). A test asserts the seed
default, because parity cannot compare a SEED file.
Changed
- Supply chain (#133): every
uses:is pinned to a full commit SHA with a# vX.Y.Zcomment, so
a re-pointed tag can no longer change what runs. Cooldowns are declared (npm 14 days, github-actions
and pip 7), npm updates move to monthly, and action updates are grouped into one PR.
tests/test_supply_chain_pins.pyenforces both.actions/setup-nodeandactions/setup-python
moved from 6 to 7 just before the pins (#46, #47). /vmm-repo-githubis removed (#135). Most of its 17 items were already held by a source of
record. Bootstrap step 5 now points repo work atAGENTS.md,CONTRIBUTING.mdand
docs/version-control-legal-moves.mdwhenFRAMEWORK_ROOTis declared, andAGENTS.mdtakes the
two facts only the command held.- The living docs use the full name "Value Memory Mining" (#139), 26 lines in 12 files. The
constitution's CONST-01 title, the protectedvalue-pipelinespec and the historical records keep
the short form.
Fixed
- The outbound guard judges commands, not prose (#131, hardening item 33). Message arguments
(-m,-F) are blanked before matching, so a commit message that names a publish command no longer
trips it. A leadingsource <file>;before acdnow resolves the right target. With the
environment unset, the guard no longer fails open. - Dependabot PRs no longer refuse every operator step (#132, hardening item 42). A saved plan for
a branch with no local copy is guarded by its pinned PR and head SHA, not by comparing the checkout,
which can never match.
v0.1.55
Covers the ten archived changes since v0.1.54 — a GitHub-platform hardening cluster
(the outbound rail, the operator handoff, and the drivers), plus lockstep/prime reach and
several control-correctness fixes. Dated in UTC; the archived-change directories carry the
prior UTC day (2026-09-21), when each was applied.
| PR | Change |
|---|---|
| #115 | Lockstep extended, and a repo-work prime for framework work from a vault-rooted session |
| #117 | A markdown content-preservation check — proves a large formatting sweep moved only whitespace |
| #118 | md-lint Phase A fix sweep — 0 findings |
| #119 | Driver subject resolution: three local controls report on what they actually check |
| #120 | render-root resolution: every operand resolved from one root |
| #121 | Prefer REST over GraphQL/subcommand gh forms; the ruleset control plane excluded, with reasons |
| #123 | The operator handoff is emitted as a copy-whole relay block (hardening item 39) |
| #124 | INV-14 hard-DENIES irreversible outbound; only a branch push keeps the ASK (hardening item 37) |
| #125 | A relay-conformance Stop hook byte-checks the relayed handoff (hardening item 40) |
| #126 | One shared operator-handoff module; ship-release hands its irreversible steps over (hardening item 41) |
Added
- The
ghwrite set is one sanctioned allowlist, evaluated by method, and REST-first
(#121, ADR-0038/0045). Both guards import a single write-endpoint set;gh apiis evaluated by
HTTP method withgh api graphqlexcepted back into deny; the outbound rail learned the REST
spelling of a publish. The ruleset control plane (branch-protection PUT/PATCH/DELETE) is
explicitly excluded from the write set with a machine-checked disjointness test and a recorded
justification — a restriction kept tight on the authority axis, not the capability axis. - INV-14 hard-DENIES irreversible outbound on the agent's channel, in every permission mode
(hardening item 37, #124). A version-tag push,remote add/set-url, repo create/make-public, and
a release publish/edit/upload (subcommand and REST) are operator-only; only a branch push keeps the
ASK (its ref is reversible; disclosure to a non-vault remote is the accepted residual). Closes the
measured auto-mode ASK silent-proceed gap for the irreversible half (determinism Site F31). - The operator handoff is emitted as a copy-whole relay block (hardening item 39, #123):
a delimited, paste-ready unit whose command is byte-identical to the saved-plan form, so relaying it
verbatim is the lazy path — aligning the efficiency incentive that drove the F43 relay recidivism. - A relay-conformance
Stophook byte-checks thenext.shline the agent relayed against the
driver'srelay-line.txtsidecar and blocks a drift at most once per emission — bounded against
a loop independently of any platform safeguard, and fail-open on every error (hardening item 40, #125). - A markdown content-preservation check makes a corpus-wide formatting sweep reviewable by proving
only whitespace moved, not content (#117). - Lockstep coverage extended, and a repo-work prime so a vault-rooted session doing framework work
loads the repo's governance rather than none (#115).
Changed
- Both lifecycle drivers emit irreversible outbound as operator handoffs through one shared module
(tools/driver_handoff.py, hardening item 41, #126).ship-release.pynow hands its tag-push and
release-create over as clean operator steps — a savednext.shplus the copy-whole relay block,
byte-checked by the item-40 hook — instead of a bare command the agent hits the item-37 DENY on. ghmutations moved togh api+ explicit REST paths across the drivers and the workflow, and
pr-state.pydropped its GraphQL fallback entirely (#121).- md-lint reached Phase A = 0 findings across the corpus (#118).
Fixed
v0.1.54
Covers the two changes merged since v0.1.53:
| PR | Change |
|---|---|
| #110 | Workflow hygiene: ceremony step 0, the legal-moves map, pinned markdownlint-cli, Node 22, md-lint audit mode |
| #111 | Legal-moves map: state its scope, enumerate local-destructive commands, rename |
Dated in UTC. ⚠ Same UTC day as v0.1.53 — both were cut on 2026-08-26 UTC, the second at
16:17 UTC (local 2026-08-27T00:17+08:00). Two releases sharing a date is correct, not a
duplicate: the date records when the release was cut, and git tag is the ordering authority.
Added
- The legal GitHub move set is enumerated, barred paths first
(docs/version-control-legal-moves.md). Built by measurement, not recall: the four layers that
can refuse (permissions.deny, the ADR-0045ghallowlist, the outbound guard, and the
server-side rulesets read live from the API), the permitted forms with who runs each and under
whose authority, the forms barred by ceremony though the platform allows them
(gh pr merge --delete-branch,gh pr edit --base), the three ceremonies end to end, and a
closing section naming what has no guard at all. Every table carries the command that
re-measures it, because the document is prose and prose goes stale silently — which its own
writing proved (see below).
Changed
preflight.py's--body-fileno longer reads as optional in the landing flow. Step 0 was
writtentools/preflight.py . [--body-file PATH]; the brackets say "optional", and when omitted
STEP 7 printsSKIP no --body-file given— a SKIP that reads exactly like a PASS. Both halves
of the partial-view failure in one line. PR #110 was pushed that way and went red onScope review
for two undeclareddep:entries the lockfile added — a gate that reproduces perfectly offline.
The scope block'sdep:prefix was already documented inAGENTS.mdand the PR template; the
defect was not a missing rule but a check that looked run when it had been skipped.- Branch names are re-read against what the branch delivers, before the first push. Scope drifts
during work; names do not. The rule is now written inCONTRIBUTING.md,AGENTS.mdand the
interaction map: before pushing, rename the branch if it no longer describes its content
(git branch -m— free and history-preserving while unpushed, effectively frozen once the PR
exists, since renaming then rewrites the remote ref and orphans the open PR). This is mechanical,
not cosmetic: the merged name is recorded permanently in
Merge pull request #N from <owner>/<branch>, which makesgit log --oneline --mergesthe
estate's searchable precedent record and the branch prefix its class marker. A misnamed branch
corrupts that record for everyone who queries it later — the same defect as a stale document,
written into history where it cannot be corrected. Demonstrated on this branch, which was opened as
docs/ceremony-shape-query-and-untruncated-scope, grew to carry a dependency pin, a Node bump, two
silent-success fixes and an audit-mode conversion, and was renamed to
fix/workflow-hygiene-and-md-lint-auditbefore its first push. md-lintbecomes a real audit-mode gate with a declared exit condition, and the frozen
archive leaves its scope. The job's|| truewas never a phase — it had no ADR, no threshold
and no exit, so it was not a flag but a permanent downgrade, on one of the 16 required status
contexts. It now runs in Phase A (audit): it reports the finding count and a per-rule
breakdown, and exits 0 on findings only. Industry vocabulary for this isaudit→enforce
(cf. Gatekeeperdryrun, Kubernetes PSAaudit, SELinuxpermissive); the job name is
deliberately unchanged, because it is a required check context and renaming one deadlocks merges.
Exit condition, declared up front: findings reach 0 and hold across 5 consecutive merged PRs,
after which Phase B removes theexit 0in its own governed change.
openspec/changes/archive/is excluded — it is the frozen, Gate-4-signed historical record,
and reformatting it for cosmetics would rewrite governance history. That alone takes the corpus
from 3142 findings across 216 files to 1131 across 44, of which 92% are three auto-fixable
cosmetic rules — leaving roughly 94 that need judgement.
A tool failure is no longer audited away, which is the defect|| truehid: an unrunnable
binary exits 127 (measured) and now fails the job. ⚠ And a second hole found while building
it: markdownlint exits 1 — not >1 — when it cannot read its config, warning once and silently
linting against DEFAULT rules. An exit-code guard alone would have audited that away, so the step
also greps the report for the fallback warning.
Fixed
-
The legal-moves map states what it does not cover, and enumerates the local-destructive class.
It read as exhaustive and was not:git rebaseis emitted bypr-flow.pyon a documented path and
was absent from the first version of the page. Root cause, and it is the page's own disease: the
map's stated re-measure command does surface"git -C {root} rebase {base_ref}", but the command
actually run while writing it piped that through an extra keyword filter
(push|merge|api|create|delete|tag|switch|branch) which silently droppedrebase— a scoped query
excluding the record sought, inside the document written to prevent exactly that. New section 2a
enumerates the commands no control sees —rebase,reset --hard,branch -D,
checkout -- <path>, force-push — because the deny list, theghallowlist and the outbound guard
all key on outward mutation while the rulesets protectmainand tags, not the working tree.
A scope statement now says plainly that absence from the page is not evidence a command is
outside the workflow, and thatCONTRIBUTING.mdremains authoritative for the process.
⚠ Also corrected: a driver-emittedgit rebaseonto a just-merged branch is measured to be a
no-op, not destructive, and the behaviour is already documented intools/pr-flow.py:544as
hardening item 26 (measured on PR #76) — "every earlier defect in this family printed a false
ALARM; that printed a false INSTRUCTION." The surviving rule is narrow: verify the premise the
driver prints in itswhy:line, and stop if it is false. -
markdownlint-cliis pinned into the lockfile, and CI moves off an end-of-life Node. The
md-lintjob rannpm install -g markdownlint-cli— global and unpinned, so it bypassed
package-lock.jsonentirely and floated to@latest, in a repository that otherwise pins
@fission-ai/openspecexactly and maintains a lockfile of 81 packages. That float had already
broken something: since 0.49.0 (2026-06-17)@latestdeclaresnode >=22, while all three
setup-nodesites declarednode-version: "20".npmwarnsEBADENGINEand installs anyway,
and the job's|| truemade a crash indistinguishable from a clean lint — the live check-run on
4937b8freportssuccesseither way. Node 20 reached EOL on 2026-04-30, so CI was also
building on an unpatched runtime. Now:markdownlint-clipinned to an exact0.49.1in
devDependencies+ lockfile (81 → 152 packages, 0 vulnerabilities), installed bynpm ciand
invoked asnode_modules/.bin/markdownlint— the patternopenspec-validatealready used — and
Node raised to 22 in all four places (ci.yml×3,openspec-canary.yml×1), matching the
maintainer's livev22.23.1. Verified:npm ciexits 0, both pinned tools resolve after a clean
install,openspec validate --all --strictpasses 6/6, and the lint reports its usual 3142
findings while|| truekeeps the job green — CI's outcome is unchanged; only the install is
fixed.npmdeliberately stays at 10.9.8. ⚠ Removing|| trueis not part of this change:
Markdown lintis one of the 16 required contexts, so that converts an always-green required check
into one that can block every merge on top of 3142 findings (452 after--fix, which additionally
corrupts content —MD018rewrites a line-initial#66pull-request reference into a level-1
heading). That takes a proposal. -
Two silent-success checks now verify what they claim (
validate-scripts.sh, hook
registration).validate-scripts.shranpython3 "$BIN/vault_naming.py" >/dev/nulland, with
set -uo pipefailand no-e, discarded the exit code — line 48'sokprinted
unconditionally, a shell-printed verdict string thatconstitution.md§3 Gate 3 names as not
evidence. The status is now captured and theokline cites it. Measured after the fix:
vault_naming.pygenuinely exits 0, so the check was vacuous while the thing it checked was
sound — no CI behaviour changes today, and a future regression can no longer pass silently.
Separately, hardening item 32: a hook file and its registration in.claude/settings.jsonnow
imply each other, closing the seam one layer out from F29. A hook present but unregistered is dead
code the harness never loads; a hook registered but absent makes the harness defer silently, so
the failure reads as success. Both were met on 2026-08-26. Red-first: the detector stubbed clean
fails the two detection cases and correctly leaves the confirming case green. Bound stated in
the test: it governs this repository only — CI has no deployed vault, so the live vault's
registration remains unguarded, and a check quietly covering one root while reading as covering
both would be the very shape being refused. -
CONTRIBUTING.mdclaimedmainhad no required status checks; it has 16. The paragraph read
"a red check does not block a merge" — measured false against ruleset19666243
(enforcement: active, 16 required contexts), exactly as `co...
v0.1.53
Covers the three changes merged since v0.1.52:
| PR | Change |
|---|---|
| #106 | The OpenSpec badge reads the pin live instead of restating it |
| #107 | The cold-start prime reports route state from the driver, not from recall |
| #108 | gh invocation form is governed by an allowlist in its own hook (ADR-0045) |
Dated in UTC. ⚠ Unlike v0.1.52, this release's date does NOT match its archive directory
stamp — 2026-08-25-gh-invocation-form-allowlist was stamped when openspec archive ran, and the
release was cut the following UTC day. The stamp records when the change was archived; this date
records when the release was cut. They coincide only when both happen inside one UTC day, and
reading the stamp as the release date is the same predict-instead-of-read error that forced a
correction on seed-auto-memory-store. #106 and #107 shipped bare (no change directory), per
the documented CONTRIBUTING.md exception for defect fixes to maintainer surfaces.
Added
-
ghinvocation form is governed by an allowlist in its own hook
(gh-invocation-form-allowlist, ADR-0045). A second BashPreToolUsehook permits only
gh api <REST path>andgh auth status;gh api graphqlis excepted back into deny, and every
other form is refused by default rather than permitted by omission. An enumerated deny list
cures the forms already known to have failed and admits every subcommand GitHub ships next —
enumeration drift, which a longer enumeration reproduces rather than cures.GraphQL is prohibited for non-determinism, not for authentication. Its failure signature is a
silent no-op: the mutation reports nothing actionable and changes nothing, so a caller trusting
the exit status proceeds on a false belief. Four recorded instances, all resolved by moving to
REST — 2026-07-18 (add-ship-ceremony-tools, "a GraphQL mutation can fail silently where REST
succeeds"), 2026-07-19 (F21,gh pr edit --body-fileexit 1 with the body unchanged),
2026-08-04 (F21·3,gh pr edit --basesilently no-opped), and 2026-08-24
(seed-auto-memory-store, "changed nothing, while reporting nothing actionable").AGENTS.md
already mandates a re-read after everygh/GraphQL mutation; that standing workaround is the
channel's sunk cost.⚠ The rationale was corrected mid-change, after being measured false. It originally argued
from401/UNAUTHENTICATED. A session rooted at$FRAMEWORK_ROOTcarries nosandboxblock,
reaches the keyring, and authenticates withrepo+workflowscopes — so GraphQL is reachable
exactly where the guard matters most. Credential reach follows the project directory, not the
platform. It was also a regression against this corpus's own record, which had already written
thatgh pr *is denied "precisely for" the silent no-op. Corrected in place per the ADR-0042
precedent rather than deleted. This strengthens the result: the live proof ran in a session
holding real mutation capability, so the guard refused a channel that genuinely worked.Limits, stated rather than left to be discovered. It binds the agent's typed channel only
— every fleetghcall is a Python subprocess the hook cannot see, which is why landing it breaks
no live caller (verified across all four call sites). It is a text matcher, so composition and
indirection defeat it: a tripwire for a cooperating agent, where the threat model is the agent
forgetting, not evading. It fails open on malformed input, which is why the enumerated
permissions.denyentries are retained alongside rather than replaced by it. And it does not
govern prose — a denied command may still be proposed to the operator in writing.Also closes F29: nothing verified that this repository's own
.claude/hooks/*.pymatched their
notes —render/reconcilegovern note → deployed vault,template-paritygoverns template →
live vault, and neither reaches these copies. It bit twice in one day. A parity check now covers
it, failing closed on a hook with no note and on a note with no implementation block, with the
comparison extracted into a pure function so both failure branches are exercised against fixtures
instead of by mutating the live tree.⚠ Known gap, recorded not fixed: nothing verifies hook registration in either root.
.claude/is not lockstep, sotemplate-mirror.pydoes not carrysettings.json; mirroring and
rendering alone deploy a guard that nothing loads, whiletemplate-parityandreconcileboth
report 0 drift. A vault instantiated fresh from the template is unaffected — the template carries
both registrations — so this bites only incremental deploy-down of an existing vault. -
The cold-start prime reports route state from the driver, not from recall
-
The cold-start prime reports route state from the driver, not from recall
(bootstrap-route-state-priming).session-bootstrap-loadergains a step: with work in
flight inFRAMEWORK_ROOT, invoketools/pr-flow.py --planand report the CURRENT step;
with nothing in flight, say so and run nothing. It states the standing rule that the
operator's entire side of the lifecycle is one invariant command,
bash "$FRAMEWORK_ROOT/.git/pr-flow/next.sh", and names the hazard that has no structural
guard:next.shis a single mutable slot — re-running the driver overwrites a step still
pending the operator, and the wrapper catches a stale command but not a substituted one.
Validated three times by the 2026-08-25 session before the content was read: the prime
reported "nothing in flight" while two branches held unmerged work; the agent hand-composed
a browser URL in place ofnext.sh; and the agent rewrote the slot while a step was pending.
Fixed
- The OpenSpec badge reads the pin live instead of restating it
(openspec-badge-live-version).README.mdadvertisedv1.4.1in three places — the badge, the
intro paragraph and the OpenSpec Layer section — whilepackage.jsonhad pinned1.6.0since the
archivedrelocate-override-template-openspec-16change. The front page was six weeks and four
upstream releases stale. The badge is now a shields.iodynamic/jsonbadge querying
$.devDependencies['@fission-ai/openspec']frompackage.jsononmain, so it derives from the
pin rather than mirroring it; the two prose occurrences are de-versioned rather than re-pinned,
because a live badge above literal prose is a self-contradicting page on the next bump.
Measured both directions: correct query →spec-driven: OpenSpec v1.6.0(HTTP 200); bogus
query →spec-driven: query not supported. The badge reads the file; it does not display a baked
string.README.mdnow contains no hardcoded OpenSpec version — the pin is stated in exactly one
place,package.json, which is where CI installs from.
v0.1.52
Covers the one change merged since v0.1.51:
| PR | Change |
|---|---|
| #104 | config.env states the interpreter resolution its PATH prepend changes |
Dated in UTC, consistent with v0.1.51 and with the openspec archive stamp on this release's own
archive directory 2026-08-25-document-venv-path-shadowing.
Added
config.envstates the interpreter resolution it changes (document-venv-path-shadowing). The
PATHprepend that puts the vault venv first is correct and deliberate, but it silently changes what
python3means for the rest of the shell, and the file said nothing about it. Measured 2026-08-20
in one shell:python3 -m pytest→No module named pytest, barepytest→9.1.1. The tool was
onPATHthe whole time; only the interpreter had changed, and an agent read the first result as
"pytest is not installed" and reported a Gate-3 blocker that did not exist. Twomaintenance
Requirements: a shipped environment file states the resolution it changes, at the point that causes
it rather than in a header read before the behaviour happens; and a shadowed import failure is not
evidence of a missing tool until the bare-name form has been tried, with both results reported when
they disagree. The genuinely-absent case is a scenario of its own, so the rule cannot be read as
"never report a tool missing". Honest strength: this constrains what a report may claim, not what a
shell may do.config.env.exampleis SEED, so the delta is merged into a live vault's
instance-ownedconfig.envby hand — never copied over, which would destroyVAULT_ROOT,
FRAMEWORK_ROOTandPILLARS.
v0.1.51
Covers the one change merged since v0.1.50:
| PR | Change |
|---|---|
| #102 | the agent working-memory store is governed, and its declared path is guarded |
The entry records the mechanism rather than the outcome. What this change cost to get right, stated
because the route is the reusable part: a placeholder path is a comment asking an installer to act, so
the seed had to travel with a check that refuses it — and the case that decided check over
install-time prompt was the one that looks like success, a declared path resolving outside the
vault, which works perfectly while silently merging one deployment's memory with another's.
Added
- The vault's agent working-memory store is governed (
seed-auto-memory-store). A deployed vault
may keep a Claude Code auto-memory store in-tree at10-Logbook/vmm-working-memory/, activated by
oneautoMemoryDirectorykey. The key cannot live in the tracked.claude/settings.json— its value
is a machine-specific absolute path (the setting expands no variables) and that file is reconciled
against the template, so the path would report as drift on every deploy-down. It therefore belongs in
the git-ignored.claude/settings.local.json, which the mirror cannot seed;a4deb6eadded the
.exampleto copy from and recorded itself as NOT YET GOVERNED. This is that governance.
vault-template/.gitignoregains the store rule the reference vault already had — a divergence that
would have made a fresh deployment track its own working memory.vault-structuredeclares the
directory optional and harness-owned: the framework owns no artifact in10-Logbook/(ADR-0032), so
the linter now skips the store's notes rather than validating them, which it had been doing. - The linter refuses an unresolvable memory-store path (
maintenance, ADDED Requirement). The seed
ships a placeholder that cannot exist, and a placeholder is a comment asking an installer to act —
CONTRIBUTING.md:92: a rule which cannot refuse does not bind. Three cases, reported distinctly
because each has a different remedy: the path does not exist · it is not a directory · it resolves
outside the vault root. The third is why a check beat an install-time prompt: an escaping path
works, so nothing else would ever notice it, and it silently merges one deployment's memory with
another's or with a user-global store. Symlinks are resolved before the containment test — the
case astartswithcheck waves through. An absent file or absent declaration passes: the store is
optional, and a check that demanded it would invent a requirement the specification does not make.
The guard reports the declared path verbatim, never a verdict string (F20), and never reads, edits
or reports on the notes inside.tests/test_memory_store_guard.pyexecutes the shipped note text
rather than a reimplementation; 9 tests, 6 of which were observed to fail with the guard removed.
v0.1.50
Covers the three changes merged since v0.1.49:
| PR | Change |
|---|---|
| #97 | fleet enumerations checked against ground truth |
| #98 | the rendered fleet lives inside the tree it serves |
| #100 | the PRD is retired as a referent — a Site is not an authority |
Added
-
The rendered Layer-0 fleet moved from the user-global
~/bin/into99-Operations/bin/
(relocate-fleet-in-tree-bin, PR #98, ADR-0044). Eleven deploy targets moved; the two git hooks
and the harness guard were already in-tree.Four grounds of principle and one measured defect. The principle was already codified — F15's
"a deployed vault is standalone" — and there is a CI job named for it, yet~/binwalked
straight pastStandalone-vault lint (F15)for months, because that job only checked for
framework-repo paths. Two vaults sharing one~/binmade version skew unrepresentable: the last
render silently won, with no way to tell.The defect was that
~/binwas not reliably onPATH.~/.profileadds it and is read only
by login shells;~/.bashrcis read only by interactive ones, and the two never both run:bash -lc (login, non-interactive) → /home/administrator/bin/vault-lint.py bash -ic (interactive, non-login) → NOT-FOUND bash -c (neither) → NOT-FOUNDThe fleet was invisible to cron, systemd user units,
ssh host 'cmd', and any plainbash -c.
The corpus had flip-flopped on this — "not on PATH" corrected to "IS on PATH" — because both
readings are half-right and neither states the condition.The fleet moved INTO the tree; it did not become agent-writable.
99-Operations/bin/sits in a
protected silo andrenderstays operator-only. Containment and permission are independent axes,
and the agent must not be able to rewrite its own guards.Sibling modules now resolve from the executing file's own location, extending ADR-0023 from root
to module resolution. Render output is git-ignored and deliberately not a lockstep prefix:
parity compares hand-maintained scaffold, render/reconcile compares generated output against the
note that produced it. Thevault-/vault_prefix — the sole reason the estate has zero name
collisions across every directory onPATH— is finally written down as a rule rather than left an
accident, together with the requirement that the path contribution be appended and idempotent
and come from the vault configuration alone, never a shell profile. -
Conformance checks for the corpus's fleet enumerations (
add-fleet-inventory-conformance,
PR #97). Three checks governed the fleet —render/reconcilefor note → deployed,
template-parityfor template → live vault — leaving one seam open: nothing governed spec →
note. A script could ship, deploy and enforce an invariant while absent from the specification
that governs it, indefinitely, with every build green.Not hypothetical:
secret-scan-script.mdenforces INV-7, shipped 2026-07-28 under ADR-0036,
reconciled clean, and appeared zero times inopenspec/specs/maintenance/spec.md— ungoverned
for 21 days.README.mdsimultaneously headed its table "Operational Scripts (13)" above 10
rows, for a fleet of 14. These survived because an absence has no string to match: only
an enumeration compared against ground truth finds a missing row.Both enumerations are now checked in both directions — a note with no row, and a row with no
note, since a check that detects only omissions passes on a table naming a script deleted last
month. Added alongside: cadence conformance, resolution checking for every script path in both
.claude/settings.jsonfiles, and behavioural coverage forvault-orphans.pyand
vault-reprospect.py, which had none in either harness. Fleet coverage 9/11 → 11/11. -
The PRD is retired as a referent (
retire-prd-referent, PR #100). A planning document that was
never under version control was cited as a live authority in twelve files — including
openspec/constitution.md, which attributed the criticality bands to "PRD §5", and
openspec/project.md, which claimed its own invariant list was "extracted from the PRD" in the
same sentence that called itself authoritative. Searched all history: never committed, never
deleted, never there.The framing is not a new rule. It is CONST-01 — the Value Mining Metaphor, Tier-1, ADR-0002 —
applied to the operation's own design process: the PRD was a Site, it was dug, what proved
valuable was refined and banked into the corpus, and the husk went to Spoil. A Site is not
an authority; the Treasury is. Some seams were slagged rather than dumped — retained and
re-minable — and carry their own records: n8n (ADR-0005) and Hermes (ADR-0006) remain
Accepted (build deferred).Three fates, not one. §5 (criticality bands) and §14.1 (deferred build) were superseded by
project.mdand the ADRs. §6.1 was worse than dangling:docs/diagrams.mdclaimed the
per-area R/W matrix "lives in the build PRD" while it sits in theArea Access Matrix
Requirement of aprotects:-tagged capability spec. §13 (acceptance tests) was orphaned with
no successor —CLAUDE.mdinstructed every fork to run tests that were never written, and the
constitution-override template asked each Tier-0 override to cite them, an unsatisfiable step in
the ceremony for the most consequential changes in the operation. The template now names the real
suite.A constitution cannot delegate its authority to a document hanging open. It is self-founding;
citing an ungoverned document makes that document a de facto higher authority and opens an
unamendable back door — edit the cited file and constitutional meaning shifts with no override
protocol and no record. The first fix in this change was only half of one: it swapped an ungoverned
source for a governed one while leaving the grammatical slot unchanged, so the sentence still read
as attribution. The ordering is now asserted as constitutional, the pointer toproject.mdis
marked a locator, not a source, and precedence on disagreement is stated explicitly.Also removes the temporal build-order block from
vault-template/CLAUDE.md. A file auto-loaded
into every session should carry only statements that stay true when the project changes phase,
ships, or is forked — phase numbering is not one, and it shipped to every fork from the initial
commit.Nothing mechanical caught any of this. No check reads prose citations, and
constitution.md
sits outside the constitutional diff gate's subject set — confirmed on the platform by this very
pull request, which edited the constitution while that gate reported not applicable. It survived
44 ADRs and 50 releases.
Fixed
-
A harness exclusion could name an artifact that no longer existed, silently.
excludedCommands
appeared nowhere intests/,.github/ortools/. An exclusion is an exact string match:
when its artifact moves the entry does not error, it stops matching, and the resulting refusal is
indistinguishable from a genuine deny — the false negative the SE-5 probe was built to detect. Now
verified, and compared by path rather than basename: the first cut compared basenames and was
measured false-negative on exactly the mismatch a relocation produces. -
Five statements that contradicted the tree, and three ADR-0028 cadence stragglers advertising
schedules that nothing installs and instructing readers to edit aschedule:field nothing reads.
Notes on what these changes cost to get right
Recorded because the mechanism recurs. The ADR-0040 archive mutates things after they are written
— it moves the change directory and applies each capability delta. That broke three separate things
across the two changes: the declared scope (PR #97's Scope review gate), a requirement that had
been applied to the live spec by hand (the archive aborted, cleanly), and a relative link that was
correct at authoring depth and one level too shallow afterwards (PR #98's Link check). PR #98
pre-declared its scope and passed that gate first time; the other two were not anticipated.
Two claims were corrected rather than quietly patched: the assertion that deleting ~/bin would
cause hard ImportErrors was false (the interpreter already places a script's own directory on
sys.path, so the obvious test passes before and after and proves nothing — the real dependency
appears only under python3 -P or when a member is imported), and the claim that PR #98 would be the
first change able to trip the settings check was false while that check still compared basenames.
v0.1.49
Added
-
A controlled vocabulary for the capability probe, and a machine-readable report
(probe-vocabulary-and-json, PR #94). The probe is the instrument the session prime instructs
every cold start to trust before making any capability claim, and its report was more
precise-looking than it was precise.Four defects, measured: a row named for what it infers (
gh auth statusmeasures a
credential; the row saidgh mutations, while the runbook had saidgh credentialsince
2026-08-06 — the instrument had drifted from its own spec); three conditions collapsed into two
tokens, so "tool absent" and "no usable credential" — different remedies — shared one word; no
controlled vocabulary at all, with eleven ad-hoc literals inlined at their print sites, three
containing whitespace, and a new state creatable by typo; and two epistemic kinds under
look-alike tokens, whereOK (dry-run)was attempted whileUNAVAILABLEwas never attempted
— a precondition had been inspected.The credential row now reports
AUTHENTICATED/UNAUTHENTICATED/ABSENT.UNAVAILABLEis
retired, not narrowed: a token reused with a tighter meaning makes every previously-emitted
transcript ambiguous.ABSENTdescribes the tool, so the credential state is unknown — not a
synonym forUNAUTHENTICATED.The rule this settles, declared where the constants live: a state names what was found in this
process; it never names what is possible in the world. Test a candidate by asking whether it could
be falsified from outside the process that emitted it.Every row now carries evidence —
attempted:<channel>orinspected. Naming the channel is
the point: a probe measuring a subprocess while the agent's real command crosses a shell-matching
guard can diverge silently, and the evidence field makes that visible in the output rather than only
in the source.--capabilities --jsonemits the rows machine-readably, from the same accumulated
data as the table, so the two renderings cannot drift apart. -
The saved-plan hand-off carries a shell-history annotation (PR #95). Every saved-plan
invocation is byte-identical, sohistory | grep next.shreturned indistinguishable lines and the
operator had been appending the annotation by hand at the moment of a mutation. The driver holds
every field it needs, so it emits
bash …/next.sh # <mnemonic> step:<step> -> PR #<n>.
Fixed
-
Emitted commands name their subject; the argv strip drops values too (PR #93). Two defects found
by running the v0.1.48 ceremony rather than reading it.verify_invocationstripped option names but not their values, so a saved plan written by a
post-mutation verify tail carried orphaned positionals. On PR #92 the merge succeeded and the
verification then died withunrecognized arguments: pr /tmp/pr-flow-evidence.…— an alarm printed
directly beneath a successful mutation. It fires only when two operator-owned steps run
back-to-back, which is what following the driven path exactly produces.ship-release.pyemitted cwd-dependent commands wherepr-flow.pyhas always emitted
git -C <literal>. An agent's shell resets its working directory between calls, so the emitted
string could not be run verbatim — and prefixing acdis a modification, which is precisely what
the outbound guard resolves differently. More consequentially, a command that cannot be run as
emitted can never match its own emission record, so this silently disabled ADR-0043's mechanism
for the release driver.
v0.1.48
Added
-
The constitutional diff gate — the enforcement §5 said was owed (
constitutional-diff-gate,
PR #89, ADR-0042). PR #85 removed the claim that the Informed-Upheaval Protocol was "mechanically
enforced (CI fails without the ceremony)" and recorded why: restoring it means building the gate,
not rewording the paragraph. This is that build.It closes an inversion named by
constitution.md§2's own tier table: Tier-0 elements are held by
the sandbox, hooks, CI runners and rulesets — all of which can refuse — while Tier 1
(CONST-01–05+ INV-12), described as "what the constitution most exists to protect", was held
by paperwork that nothing checked. The layer treated as most precious had the least resistance.The gate refuses silence, not work. It cannot judge whether a diff overrides a principle — that
is the human judgement §5 reserves — so it establishes only that the question was answered in
writing and that the answer is in version control. Smaller than "mechanically enforced", and
described that way everywhere it appears.Measured, not assumed: the subject set is 6 files by frontmatter; a substring implementation
would fire on 23, includingCHANGELOG.md,ci.ymlandconstitution.mditself — a bootstrap
deadlock. It fires on 4 of the last 25 merges (16%), and all four already carried a change
directory to hold the declaration. The declaration lives in the tree, not the PR body, because a
body can be edited after its checks report green.Phase A is report-only; the flip is its own change after burn-in (5 consecutive PRs touching a
protected spec). Until then a green gate means a declaration exists, never that the ceremony was
followed —constitution.md§4 says so.CONST-01–05remain ungated; closing that is a separate
change to hash the five principle sections.Also corrected here: the
constitution-overridetemplate still carried three of the six claims
PR #85 retracted — in the file an author reads while performing the ceremony. -
A driver's emission downgrades the outbound prompt; it never creates a refusal
(emission-record-downgrades-ask, PR #91, ADR-0043). The driver prints the exact command and the
agent retypes it wrong — stage 1 of the vault's newly-adopted failure class 10, unauthorized
deviation from a codified route, which had been carried unnumbered since 2026-07-01, invoked eight
times and tallied never.Each emitted command is recorded to
.git/pr-flow/emitted.json. An outward command byte-identical to
a live record for the current branch is allowed without a prompt; anything else raises the usual
confirmation and prints the difference.The invariant: the record may only DOWNGRADE a confirmation to an allowance. It may never create a
refusal. Absent, expired, wrong-branch, unparseable and comparison faults all fall through to the
prompt raised today, so the change cannot make the guard stricter than it was — which is why it
ships without a burn-in phase. An earlier draft made "no record" a denial; it was withdrawn for
manufacturing exactly the over-denial that trains readers to route around guards.This inverts the incentive: running the emitted command verbatim becomes the frictionless path, and
deviating is the one that stops with a diff. It also reduces operator interrupts, since
driver-emitted pushes stop prompting.Stated as a requirement, not a footnote: the record is forgeable by the agent it governs. It
addresses error, not intent — a mangled retype does not also produce a matching record — which is
consistent with ADR-0018's posture of a tripwire for a cooperating agent. An allowance means
"matched a record", never "authorised".A second, independent diagnostic was added after a test exposed the gap: a denial caused by a
redirect the guard could not resolve now prints what was written and what it resolved to. That works
with no record at all, and it is what the motivating incident needed and did not get.
Fixed
-
The replay guard checked ref existence, not history (
fix/replay-test-shallow-and-snapshot,
PR #90). The post-merge run of PR #89 turnedmainred while the same suite passed on the pull
request that merged it.actions/checkoutpresents two geometries: on apull_requestrunmain
does not exist, so the test skipped — correct by accident; on apushrun the checkout creates
the branch ref, so it resolved, andgit log --merges -25returned 0 from a depth-1 clone.The precondition was never ref existence; it is sufficient history. Fixed to skip on truncated
history with a stated reason, never a vacuous pass. A second defect was found while fixing the
first: the test asserted a snapshot (len(fired) == 4) against a window that slides with every
merge, and was replaced with an invariant that cannot rot — the frontmatter classifier and the known
protected-path set must agree over real merge diffs.Pre-flight could not have caught either: it runs in a full clone, and both failing geometries
exist only on CI. Recorded against the pre-flight scorecard's known bounds.