Skip to content

feat(vault): default vault install to the published compmech vault - #9

Merged
SOSOVSKI merged 10 commits into
mainfrom
feat/vault-split
Sep 10, 2026
Merged

feat(vault): default vault install to the published compmech vault#9
SOSOVSKI merged 10 commits into
mainfrom
feat/vault-split

Conversation

@SOSOVSKI

Copy link
Copy Markdown
Contributor

akms-vault-compmech v1.0.0 is tagged, so akms vault install now needs no
argument: it fetches that release and installs the nodes (264 as per v1.0.0).
Pinned to the tag so that i can feel free to add nodes and revise existing ones if needed

SOSOVSKI and others added 9 commits September 10, 2026 16:56
The wheel shipped 302 nodes and 2.65 MB — roughly two thirds of the package —
that no user could reach. ``resolve_global_vault`` picks an explicit argument,
then AKMS_GLOBAL_VAULT, then ``global_vault`` from the propagation config, then
~/.claude/akms/nodes. The package directory is on none of those paths, and
nothing copied the corpus out of it, so every ``pip install akms`` carried a
corpus the runtime would never read. Verified directly against the published
0.3.0 wheel before removing anything.

Removing it drops the wheel from 1.3 MB to 320 KB. Every functional bundled
asset still ships: skills, qmd wrappers, hooks, agents, commands, the kernel.

``package-data`` is now enumerated rather than ``_bundled/**/*``. The blanket
glob is how the corpus came to ship in the first place, and how ``.DS_Store``
reached users; an enumerated list means a corpus dropped back into _bundled
cannot silently re-enter the wheel.

**akms vault status / install.** Removing the corpus exposes a gap that was
always there: installation.md tells a new user to ``mkdir -p
~/.claude/akms/nodes`` and no documentation says where nodes come from. Every
fresh install has had an empty vault. ``vault install`` takes a directory, a
.tar.gz, or an https URL.

Nothing in AKMS writes to the global vault on its own, and that stays true:
this is a foreground command that reports what it will do and refuses to
replace a populated vault without --force. It also refuses a source with no
nodes, a source whose markdown declares no ``akms_schema``, plain http, and
any archive member that resolves outside the destination. Content is staged
beside the vault and swapped in, so an interrupted install cannot leave a
half-written vault where a working one used to be.

**Test fixture.** The seed integration tests used the shipped corpus as their
fixture. They now run against a pinned 52-node subset under tests/fixtures/ —
the six skill-* nodes they assert over, the closure of every node their edges
target, and every content_ref payload involved. Self-consistent, no dangling
edges, 300 KB, and not packaged. All 27 of those tests pass unchanged.
Corpus-wide QA belongs with the vault.

One real bug surfaced while testing the tarball path: macOS ``tar`` writes
AppleDouble ``._name`` companions beside every file, they extract as files
ending in .md, and they were both counted as nodes and installed into the
vault, where the compiler would try to parse resource-fork data as v2 nodes.
Filtered on copy, with a regression test.

Verified: 767 tests pass, ruff format and check clean, no new pyright errors,
and an installed vault compiles to a 52-node, 165-edge graph end to end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
**Paths.** The docs referenced `Packages/AKMS`, `Packages/AKMS_nodes_gen` and
friends 37 times across 14 files. git tracks `packages/akms` and
`packages/akms_nodes_gen` — lowercase. Every one of those commands resolves on
a case-insensitive macOS filesystem and fails outright on Linux, including the
first command on the installation page. Rewritten and each resulting path
checked against the tree.

**Installation.** The page opened with "the documentation currently assumes a
repository checkout rather than a PyPI release", which stopped being true when
0.3.0 published. It now leads with `pip install akms` and keeps the source
checkout as the contributor path.

It also claimed the root workspace path-sources `mechdsl-core` and `algo2code`
from a sibling `../MechDSL` checkout, so a full sync "requires that layout".
That is a private-repo detail; the public root declares four workspace members
and nothing else, which is why CI runs `uv sync --locked --all-packages
--all-extras --all-groups` on every push. The warning could only have deterred
people from a command that works.

**The vault.** The page told a new user to `mkdir -p ~/.claude/akms/nodes` and
never said where nodes come from, so every fresh install has had an empty
vault. It now explains that vaults are distributed separately and documents
`akms vault status` and `akms vault install`.

**Code mirrors.** Made explicit that a mirror is generated from your own
repository rather than shipped — it is build output, and nothing arrives
pre-populated. The `repo2md` provider now says plainly that it needs the
external `repo-wiki` executable, which AKMS does not install, that `legacy` is
the in-process default requiring nothing, and that because
`mirror.fallback_on_error` defaults to false, configuring repo2md without the
binary fails loudly rather than silently reverting.

Verified on a strict build.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
A vault distributed as a git repository carries a README, a licence and CI
configuration. Copying those into the vault is not merely untidy: the graph
compiler treats any markdown in the vault lacking ``akms_schema`` as a schema
error and re-raises it regardless of strictness, so a README in the vault makes
every later build fail with SchemaValidationError. Every GitHub repository has
one, and so does Packages/Nodes_Vault — the corpus this vault is generated
from. Verified by installing a repository-shaped source and watching
build_graph refuse it.

The fix belongs in the installer rather than in a layout rule vault authors
have to remember. ``vault install`` now copies what a vault *is* — files
declaring ``akms_schema``, plus the ``content/`` payload tree they address
through ``content_ref`` — and reports the top-level names it left behind
instead of discarding them silently.

That single pass also subsumes the dotfile filtering, so AppleDouble ``._name``
companions and ``.DS_Store`` never reach the vault either.

Verified end to end: a source carrying README.md, LICENSE and .github/ installs
52 nodes, leaves the furniture behind, names it in the output, and compiles to
a 52-node, 165-edge graph. 769 tests pass; ruff clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
A vault's README documents the node format, so it contains the text
`akms_schema` inside a fenced example. The substring test that decided what to
install therefore classified the README as a node, copied it into the vault,
and the graph compiler aborted every subsequent build on it — the exact failure
the previous commit set out to prevent.

Found by generating the real vault repository and installing it: 303 nodes
instead of 302, then SchemaValidationError on README.md.

Detection is now structural. The file must open with a frontmatter fence, and
`akms_schema` must appear as a top-level key before that block closes. Prose
and fenced examples cannot satisfy that.

Verified against the generated vault: 302 nodes install, README, LICENSE and
scripts/ are named as left behind, and the result compiles to a 302-node,
1045-edge graph.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
check_public_tree walked the filesystem and knew nothing about .gitignore, so
any gitignored working directory failed it locally. A stray
`packages/.orchestra/session-state.json` — a file git cannot publish under any
circumstances — was enough to make the release preflight report a violation.

CI never saw it, because CI checks out from git. That is the problem: a gate
whose local runs are noisy and whose CI runs are clean teaches people to skip
the local one, which is where it earns its keep.

The audit now asks git what it would publish — tracked files plus untracked
files that are not ignored — and scans that. Untracked-but-not-ignored files
stay in scope deliberately: they are one `git add .` away from shipping.

When git cannot answer (no repository, no git, an extracted tarball) it returns
None and the walk runs as before, which is the conservative direction.

Verified quieter without being weaker:

  gitignored .orchestra file present            -> pass
  untracked, unignored `session-state.json`     -> fail, named
  secret in an untracked, unignored file        -> fail, named
  same secret inside a gitignored path          -> pass, out of scope
  outside a git repository                      -> falls back to the full walk

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
…AKMS

The private-repository pattern matched `SOSOVSKI/AKMS` literally, so every
other repository under that account passed the gate unnoticed.

That is not hypothetical. compmech-reference-pack — the next package queued for
publication — names `SOSOVSKI/MechDSL`, `SOSOVSKI/ConstKit` and
`SOSOVSKI/SymbolicFemWorkbench` in its shipped `domain_pack/source_packs/*.yaml`
and in `capabilities.py`. MechDSL is private and the other two are not publicly
resolvable, so porting that package as-is would have disclosed three private
repository names through a gate specifically built to prevent exactly that.

The pattern now matches any repository under the account. The current tree
still passes; a planted `SOSOVSKI/MechDSL` reference is now caught.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
The computational-mechanics companion adapter, ported from the private
workspace at 0.3.0 to join the lockstep release. It bridges akms-learn LSP
excerpts to the MechDSL executable backend; the compile path stays an optional
`mechdsl` extra so the backend is not pulled into a consumer's lock closure
unless asked for.

**Private references removed.** The package named three repositories that are
not public — `SOSOVSKI/MechDSL` (private), `SOSOVSKI/ConstKit` and
`SOSOVSKI/SymbolicFemWorkbench` (not resolvable at all) — in shipped source
packs and in capabilities.py.

MechDSL is repointed to `CEmM2/MechDSL`, the public repository that
mechdsl-core, algo2code and ti-runtime all declare as their source on PyPI.

The constkit and symbolic_fem_workbench source packs are dropped. Both were
`capability_status: planned` — no adapter exists — and both named
non-resolvable repositories, so shipping them would have advertised companions
nobody can obtain while disclosing private repository names. The domain pack
now declares only the mechdsl companion, and the test that asserted the two
planned roles asserts that instead. Restore them when those repositories and
their adapters are public.

Logic-Loom references now name `CEmM2/Logic-Loom` explicitly rather than
gesturing at an unqualified project.

Internal plan identifiers (`P2-1` … `P2-6`) are stripped from every docstring
and test. They mean nothing to a reader of the public package, and
check_public_tree's pattern requires a `Task ` prefix, so the bare
parenthesised form would have shipped unnoticed.

**Wiring.** Workspace member and uv source, `PACKAGE_FILES` in check_versions,
the pyright include list, a `compmech-reference-pack` build step and publish
matrix leg in release.yml, and the `pypi-compmech-reference-pack` environment
restricted to `v*` tags like the other four.

The lock gains mechdsl-core, algo2code and their scientific dependencies
(numpy, scipy, sympy, mpmath, opt-einsum, nrpylatex) from the all-extras
resolution. No Taichi.

Verified: 1752 tests pass, ruff format and check clean, pyright 0 errors with
the new package in scope, strict docs build, public-tree audit passes, version
audit passes, and all five packages' release globs claim exactly their own
wheel and sdist with all ten distributions accounted for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
I dropped both on the assumption that their source was unobtainable, having
found `SOSOVSKI/ConstKit` and `SOSOVSKI/SymbolicFemWorkbench` unresolvable.
The assumption was wrong: neither exists as a standalone repository because
both live in the public `SOSOVSKI/Teaching-materials` monorepo, under
`packages/constkit` and `packages/symbolic-fem-workbench`.

Both packs are restored, repointed at that repository with roots that match its
actual layout — verified against the published tree rather than transposed from
the old paths, so `docs`, `code`, `tests` and (for the workbench) `notebooks`
all name directories that exist. Neither declares an `examples` root it does
not have.

Both stay `capability_status: planned`, which now means what it should: the
adapter is unwritten. The source was always available; only my reading of it
was wrong.

A new test pins every source pack's repository to a known-public target, so a
future edit cannot quietly reintroduce a reference a reader cannot follow.

**Gate.** `SOSOVSKI/Teaching-materials` is public and legitimately referenced
from the public tree, so the blanket private-account pattern needed an
allowlist. The trailing `(?![\w.-])` in it is load-bearing: with a plain `\b`
there, a hyphen counts as a word boundary and `Teaching-materials-private`
would have matched the allowlisted prefix and been waved through. Caught by
testing the boundary rather than the happy path.

Verified across eight cases — `Teaching-materials` and paths beneath it pass;
`AKMS`, `MechDSL`, `ConstKit`, `Logic-Loom` and `Teaching-materials-private`
are all flagged. 1753 tests pass, ruff clean, pyright 0 errors, strict docs
build, public-tree and version audits pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
akms-vault-compmech v1.0.0 is tagged, so `akms vault install` now needs no
argument: it fetches that release and installs 264 nodes.

Pinned to the tag rather than a branch. A user typing four words has not
consented to installing whatever `main` happens to hold that day, and a moving
default would make the same command produce different content over time. A test
asserts the pin, so pointing it at a branch fails the build.

**Redirects.** Verified end to end against the real tarball, which surfaced a
hole: `urlopen` follows redirects on its own, so the https check on the URL the
user typed did not cover the hops after it. An https source could hand off to
plain http mid-flight and still be treated as trusted. Redirects cannot simply
be refused — GitHub's archive URLs redirect to codeload, which is the normal
path — so the handler follows them and refuses only the downgrade.

Verified: a bare `akms vault install` fetches the published v1.0.0 tarball,
leaves CITATION.cff, LICENSE, README.md and scripts/ behind, installs 264
nodes, and compiles to a 264-node, 909-edge graph. 1756 tests pass, ruff clean,
pyright 0 errors, strict docs build, public-tree and version audits pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
Comment thread packages/akms/src/akms/cli/vault_commands.py Fixed
CodeQL alert #7, py/tarslip, high severity, on the unfiltered fallback at
vault_commands.py:161. It was right, and this is not a suppression.

The previous shape validated every member and then called `extractall`,
falling back to an unfiltered call on interpreters whose tarfile predates the
`data` filter. That fallback was a genuine weak point rather than a false
positive: tarfile re-derives each destination path from the same member names,
so the validation above it was advisory, not load-bearing. Nothing connected
the check to the write.

Extraction is now explicit, member by member, so the path check is the only
route to disk. That also turns a blacklist into a whitelist: a vault is
directories and regular files, so symlinks, hardlinks, devices and FIFOs are
skipped rather than reasoned about, and archive permissions are not carried
over — nothing can arrive with an executable or setuid bit.

It removes the version-dependent behaviour too. There is no longer a filter
argument that may or may not exist, so every interpreter takes the same path.

No `extractall` and no security suppression remains in the package.

Verified: a hostile archive carrying a symlink to /etc/passwd and a 0o4777
mode installs two nodes and neither the link nor the mode lands; the traversal
refusal still fires; and a bare `akms vault install` against the published
v1.0.0 tarball still yields 264 nodes, 14 payloads and a 909-edge graph.
1757 tests pass, ruff clean, pyright 0 errors, public-tree audit passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcdE6eeSDBh8xrgKCoGrkw
@SOSOVSKI
SOSOVSKI merged commit cd42e22 into main Sep 10, 2026
13 checks passed
@SOSOVSKI
SOSOVSKI deleted the feat/vault-split branch September 10, 2026 16:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants