Skip to content

Releases: keith-nielsen/2026-AI-Value-Memory-Mining

v0.1.57

Choose a tag to compare

@keith-nielsen keith-nielsen released this 24 Oct 07:47
363912f

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.yaml when 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 now ACMR.
    access-control gains 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

Choose a tag to compare

@keith-nielsen keith-nielsen released this 17 Oct 20:30
0b7c87d

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, and terminal or gate:. SessionStart in both roots
    names it as its first output line, inside the preview, and session-bootstrap-loader step 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.
  • reconcile detects git hooks that are deployed but unregistered (#129). When
    99-Operations/hooks/ holds hooks but core.hooksPath does not point at it, reconcile names the
    fix and counts it as drift. It never sets core.hooksPath itself (INV-3: detect, never auto-fix).
  • The pre-flight checks its own interpreter first (#137). If the jobs' interpreter lacks a
    distribution named in tests/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.com and github.com in
    sandbox.network.allowedDomains, plus WebFetch(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.Z comment, 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.py enforces both. actions/setup-node and actions/setup-python
    moved from 6 to 7 just before the pins (#46, #47).
  • /vmm-repo-github is removed (#135). Most of its 17 items were already held by a source of
    record. Bootstrap step 5 now points repo work at AGENTS.md, CONTRIBUTING.md and
    docs/version-control-legal-moves.md when FRAMEWORK_ROOT is declared, and AGENTS.md takes 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 protected value-pipeline spec 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 leading source <file>; before a cd now 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

Choose a tag to compare

@keith-nielsen keith-nielsen released this 22 Sep 09:20
a8f3d82

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 gh write set is one sanctioned allowlist, evaluated by method, and REST-first
    (#121, ADR-0038/0045). Both guards import a single write-endpoint set; gh api is evaluated by
    HTTP method with gh api graphql excepted 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 Stop hook byte-checks the next.sh line the agent relayed against the
    driver's relay-line.txt sidecar 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.py now hands its tag-push and
    release-create over as clean operator steps — a saved next.sh plus 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.
  • gh mutations moved to gh api + explicit REST paths across the drivers and the workflow, and
    pr-state.py dropped its GraphQL fallback entirely (#121).
  • md-lint reached Phase A = 0 findings across the corpus (#118).

Fixed

  • Three local controls now report on what they actually check, not a proxy for it — the shared
    root cause behind a cluster of driver-subject defects (#119).
  • vault-render.py resolves every operand from one root, correcting a gate that had misdescribed
    the relationship it enforced (#120).

v0.1.54

Choose a tag to compare

@keith-nielsen keith-nielsen released this 26 Aug 18:18
2c48725

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-0045 gh allowlist, 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-file no longer reads as optional in the landing flow. Step 0 was
    written tools/preflight.py . [--body-file PATH]; the brackets say "optional", and when omitted
    STEP 7 prints SKIP 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 on Scope review
    for two undeclared dep: entries the lockfile added — a gate that reproduces perfectly offline.
    The scope block's dep: prefix was already documented in AGENTS.md and 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 in CONTRIBUTING.md, AGENTS.md and 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 makes git log --oneline --merges the
    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-audit before its first push.
  • md-lint becomes a real audit-mode gate with a declared exit condition, and the frozen
    archive leaves its scope.
    The job's || true was 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 is audit → enforce
    (cf. Gatekeeper dryrun, Kubernetes PSA audit, SELinux permissive); 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 the exit 0 in 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 || true hid: 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 rebase is emitted by pr-flow.py on 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 dropped rebase — 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, the gh allowlist and the outbound guard
    all key on outward mutation while the rulesets protect main and 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 that CONTRIBUTING.md remains authoritative for the process.
    ⚠ Also corrected: a driver-emitted git rebase onto a just-merged branch is measured to be a
    no-op
    , not destructive, and the behaviour is already documented in tools/pr-flow.py:544 as
    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 its why: line, and stop if it is false.

  • markdownlint-cli is pinned into the lockfile, and CI moves off an end-of-life Node. The
    md-lint job ran npm install -g markdownlint-cli — global and unpinned, so it bypassed
    package-lock.json entirely and floated to @latest, in a repository that otherwise pins
    @fission-ai/openspec exactly and maintains a lockfile of 81 packages. That float had already
    broken something: since 0.49.0 (2026-06-17) @latest declares node >=22, while all three
    setup-node sites declared node-version: "20". npm warns EBADENGINE and installs anyway,
    and the job's || true made a crash indistinguishable from a clean lint — the live check-run on
    4937b8f reports success either way. Node 20 reached EOL on 2026-04-30, so CI was also
    building on an unpatched runtime. Now: markdownlint-cli pinned to an exact 0.49.1 in
    devDependencies + lockfile (81 → 152 packages, 0 vulnerabilities), installed by npm ci and
    invoked as node_modules/.bin/markdownlint — the pattern openspec-validate already used — and
    Node raised to 22 in all four places (ci.yml ×3, openspec-canary.yml ×1), matching the
    maintainer's live v22.23.1. Verified: npm ci exits 0, both pinned tools resolve after a clean
    install, openspec validate --all --strict passes 6/6, and the lint reports its usual 3142
    findings while || true keeps the job green — CI's outcome is unchanged; only the install is
    fixed
    . npm deliberately stays at 10.9.8. ⚠ Removing || true is not part of this change:
    Markdown lint is 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 — MD018 rewrites a line-initial #66 pull-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.sh ran python3 "$BIN/vault_naming.py" >/dev/null and, with
    set -uo pipefail and no -e, discarded the exit code — line 48's ok printed
    unconditionally, a shell-printed verdict string that constitution.md §3 Gate 3 names as not
    evidence. The status is now captured and the ok line cites it. Measured after the fix:
    vault_naming.py genuinely 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.json now
    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.md claimed main had no required status checks; it has 16. The paragraph read
    "a red check does not block a merge" — measured false against ruleset 19666243
    (enforcement: active, 16 required contexts), exactly as `co...

Read more

v0.1.53

Choose a tag to compare

@keith-nielsen keith-nielsen released this 26 Aug 06:14
4937b8f

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

  • gh invocation form is governed by an allowlist in its own hook
    (gh-invocation-form-allowlist, ADR-0045). A second Bash PreToolUse hook permits only
    gh api <REST path> and gh auth status; gh api graphql is 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-file exit 1 with the body unchanged),
    2026-08-04 (F21·3, gh pr edit --base silently no-opped), and 2026-08-24
    (seed-auto-memory-store, "changed nothing, while reporting nothing actionable"). AGENTS.md
    already mandates a re-read after every gh/GraphQL mutation; that standing workaround is the
    channel's sunk cost.

    ⚠ The rationale was corrected mid-change, after being measured false. It originally argued
    from 401/UNAUTHENTICATED. A session rooted at $FRAMEWORK_ROOT carries no sandbox block,
    reaches the keyring, and authenticates with repo + workflow scopes — 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
    that gh 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 fleet gh call 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.deny entries 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/*.py matched their
    notes — render/reconcile govern note → deployed vault, template-parity governs 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, so template-mirror.py does not carry settings.json; mirroring and
    rendering alone deploy a guard that nothing loads, while template-parity and reconcile both
    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-loader gains a step: with work in
    flight in FRAMEWORK_ROOT, invoke tools/pr-flow.py --plan and 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.sh is 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 of next.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.md advertised v1.4.1 in three places — the badge, the
    intro paragraph and the OpenSpec Layer section — while package.json had pinned 1.6.0 since the
    archived relocate-override-template-openspec-16 change. The front page was six weeks and four
    upstream releases stale. The badge is now a shields.io dynamic/json badge querying
    $.devDependencies['@fission-ai/openspec'] from package.json on main, 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.md now contains no hardcoded OpenSpec version — the pin is stated in exactly one
    place, package.json, which is where CI installs from.

v0.1.52

Choose a tag to compare

@keith-nielsen keith-nielsen released this 25 Aug 07:05
3a7a868

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.env states the interpreter resolution it changes (document-venv-path-shadowing). The
    PATH prepend that puts the vault venv first is correct and deliberate, but it silently changes what
    python3 means 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, bare pytest → 9.1.1. The tool was
    on PATH the 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. Two maintenance
    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.example is SEED, so the delta is merged into a live vault's
    instance-owned config.env by hand — never copied over, which would destroy VAULT_ROOT,
    FRAMEWORK_ROOT and PILLARS.

v0.1.51

Choose a tag to compare

@keith-nielsen keith-nielsen released this 25 Aug 05:11
ee63313

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 at 10-Logbook/vmm-working-memory/, activated by
    one autoMemoryDirectory key. 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; a4deb6e added the
    .example to copy from and recorded itself as NOT YET GOVERNED. This is that governance.
    vault-template/.gitignore gains the store rule the reference vault already had — a divergence that
    would have made a fresh deployment track its own working memory. vault-structure declares the
    directory optional and harness-owned: the framework owns no artifact in 10-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 a startswith check 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.py executes the shipped note text
    rather than a reimplementation; 9 tests, 6 of which were observed to fail with the guard removed.

v0.1.50

Choose a tag to compare

@keith-nielsen keith-nielsen released this 19 Aug 07:53
3837064

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/ into 99-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 ~/bin walked
    straight past Standalone-vault lint (F15) for months
    , because that job only checked for
    framework-repo paths. Two vaults sharing one ~/bin made version skew unrepresentable: the last
    render silently won, with no way to tell.

    The defect was that ~/bin was not reliably on PATH. ~/.profile adds it and is read only
    by login shells; ~/.bashrc is 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-FOUND
    

    The fleet was invisible to cron, systemd user units, ssh host 'cmd', and any plain bash -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 and render stays 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. The vault-/vault_ prefix — the sole reason the estate has zero name
    collisions across every directory on PATH — 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/reconcile for note → deployed,
    template-parity for 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.md enforces INV-7, shipped 2026-07-28 under ADR-0036,
    reconciled clean, and appeared zero times in openspec/specs/maintenance/spec.md — ungoverned
    for 21 days. README.md simultaneously 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.json files, and behavioural coverage for vault-orphans.py and
    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.md and the ADRs. §6.1 was worse than dangling: docs/diagrams.md claimed the
    per-area R/W matrix "lives in the build PRD" while it sits in the Area Access Matrix
    Requirement of a protects:-tagged capability spec. §13 (acceptance tests) was orphaned with
    no successor
    — CLAUDE.md instructed 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 to project.md is
    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 in tests/, .github/ or tools/. 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 a schedule: 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

Choose a tag to compare

@keith-nielsen keith-nielsen released this 17 Aug 09:03
dec7a01

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 status measures a
    credential; the row said gh mutations, while the runbook had said gh credential since
    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
    , where OK (dry-run) was attempted while UNAVAILABLE was never attempted
    — a precondition had been inspected.

    The credential row now reports AUTHENTICATED / UNAUTHENTICATED / ABSENT. UNAVAILABLE is
    retired, not narrowed: a token reused with a tighter meaning makes every previously-emitted
    transcript ambiguous. ABSENT describes the tool, so the credential state is unknown — not a
    synonym for UNAUTHENTICATED.

    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> or inspected. 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 --json emits 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, so history | grep next.sh returned 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_invocation stripped 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 with unrecognized 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.py emitted cwd-dependent commands where pr-flow.py has 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 a cd is 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

Choose a tag to compare

@keith-nielsen keith-nielsen released this 17 Aug 05:41
3a82732

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, including CHANGELOG.md, ci.yml and constitution.md itself — 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–05 remain ungated; closing that is a separate
    change to hash the five principle sections.

    Also corrected here: the constitution-override template 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 turned main red while the same suite passed on the pull
    request that merged it. actions/checkout presents two geometries: on a pull_request run main
    does not exist, so the test skipped — correct by accident; on a push run the checkout creates
    the branch ref, so it resolved, and git log --merges -25 returned 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.