Synaptixs-spine

Latest version: v3.32.0

Safety actively analyzes 1006753 Python packages for vulnerabilities to keep your Python projects secure.

Scan your dependencies

Page 1 of 8

3.32.0

Removed

- **The `sdlc` scope alias.** 3.31.0 accepted the pre-tier `sdlc` scope as all three
tier scopes for one release, with a warning. That release has shipped. A bearer
token carrying only `sdlc` now has no tier scope and fails the server's
`spine:read` floor; grant `spine:read spine:plan spine:run` (or the subset the
token should have). `ORCHESTRATOR_MCP_REQUIRED_SCOPES` is taken verbatim.

Added

- **Every MCP tool advertises what it returns.** A type per tool
(`plugin/outputs.py`) with no required key, attached to the registered function
so the SDK derives an output schema and validates the result while the tool
keeps returning a plain dict. A host reads `found`, `matches[].where`,
`uncovered_elsewhere` before it calls. The types allow extras — pydantic would
otherwise drop an undeclared key from the structured result silently — and the
test suite's drift guard wraps every tool and fails on any returned key its type
does not declare. A tool without a type does not register.
- **Per-principal audit on the MCP server's HTTP transport.** Every run-scope call
and every scope denial is recorded against the token's principal in the
registry's audit log through a new `POST /v1/audit` (the actor and tenant come
from the authenticated principal, never from the body): the principal, tool,
scope, the argument *names* and a digest of the values — never the values — and
the outcome. An unreachable registry degrades to a log line; the audit never
fails the call. Stdio records nothing: a local subprocess acts for one user.

- **Every comprehension tool that can answer across repositories now does.**
`explain_symbol`, `regression_gaps`, `localize` and `docs_for` take `repos=` (a
`.spine/repos.yaml`) like `blast_radius` and `investigate`, with the same
contract and `standing` block. `regression_gaps` reports `uncovered_elsewhere` —
a change reaching a *different* service nothing tests — which needed the plan
to follow the one hop that crosses a service boundary (handler → endpoint →
consumer); the endpoint itself is the wire, not an item. `localize` says which
repository each frame landed in, and a frame two services could own is reported
as ambiguous with its candidates instead of the first match winning silently.
`docs_for` answers each repository on its own rather than merging, because a
document describes the repository it lives in.

- **The long MCP tools report progress.** `sdlc_feature` per stage in the runner's
own order (spec, layout, design, implement, tests, refine, judge, PR),
`sdlc_remediate` per task, `sdlc_address_review` at checkout / respond / done,
`understand_repo` at extract / write, `audit_repo` at start / done — whenever the
host sends a progress token; a client that sends none sees what it saw before.
The phases are the engines' existing log prefixes mapped to ordered steps
(`plugin/progress.py`), so the CLI and the plugin describe the same stages; the
bar is monotonic, and an unknown line rides on the current step as its message.
The tools' schemas are unchanged: the SDK context arrives through an optional
parameter the scope wrapper passes through and the schema never shows.

- **MCP prompts and resources — the workflow and the documents, for hosts that are
not Claude Code.** Five prompts carry the `understand-codebase` skill's "which
tool, in which order" through the protocol (`orient`, `investigate-ticket`,
`triage-bug`, `plan-then-approve`, `whats-waiting-on-me`), so Codex, Claude
Desktop and claude.ai get the same ordered guidance a Claude Code skill gives; the
prompts module is the source, because the skill directory is not in the wheel,
and a test holds the skill to the same tools. Five `spine://` resources make the
committed `episteme/` bank, the build documents under `.spine/plans` (with their
approval state) and the current-state report readable by URI and attachable as
context. Resources address the default repository — the working directory, or
`SPINE_REPO_ROOT` — because a URI segment cannot carry a path; the tools keep
taking `repo_path`.

3.31.0

Added

- **The gated half of the pipeline's back half, as MCP tools — and with it, every
gap in the plugin's design record is closed.** `sdlc_address_review` addresses
the human review comments on an open PR and pushes a fix to its branch;
`sdlc_complete` closes the tracker issue for a merged PR; both have no local
mode and need `confirm=true` on every call. `sdlc_remediate` turns a drift
report into remediation runs, safe by default, `live` gated like
`sdlc_feature`. `audit_repo` runs the codebase-auditor persona and reports
findings anchored to real `file:line` — writes nothing, spends tokens, so it is
read-only for the host and run scope for the token. The clone-and-checkout and
the merge→Done logic moved from the CLI into the engine (`checkout_pr_worktree`,
`orchestrator.sdlc.complete`), so the CLI and the plugin share one
implementation.

- **The free half of the pipeline's back half, as MCP tools.** `understand_repo`
builds a repo's `episteme/` knowledge base — or, with `check=true`, verifies the
committed one still matches the code, naming the missing, stale and orphaned
pages — so an assistant can bootstrap a repo that has no bank and then
`read_memory_bank` it. It refuses a build on a git URL unless `out` is an
absolute directory, because the clone vanishes. `profile_repo` (languages,
framework, database, test runner, task type), `design_change` (a grounded
design with blast radius and unverified references, for the same `spec` object
`sdlc_plan` takes; `use_llm` opt-in) and `sdlc_baseline` (the agent-corpus
gate and run metrics) join it. All deterministic, no credentials. There is no
`state` tool because `map_repo` already is it.

- **Scopes follow the tiers on the MCP server's HTTP transport.** `spine:read`
(comprehension, observing a run), `spine:plan` (`sdlc_plan`, `sdlc_approve`),
`spine:run` (anything that spends money or writes where it cannot be taken
back). Every registered tool is wrapped in a guard that reads the verified
bearer token at call time and refuses — naming the scope it needed and the
scopes the token has — when the tier's scope is missing; the SDK only checks
scopes server-wide, so the per-tool check lives in the plugin. Over stdio there
is no token and no check. `ORCHESTRATOR_MCP_REQUIRED_SCOPES` now means what the
static token *carries* (default: all three, so a self-host behaves as before),
and `spine:read` alone makes it a read-only token. The legacy `sdlc` scope
expands to all three for one release, with a logged warning, then goes.

- **Operator tools in the MCP plugin — the terminal UI's successor.** `registry_runs`,
`registry_approvals`, `registry_trace` and `registry_decide` answer "what is
running, what is waiting on me" and decide a gate, over HTTP to the registry
(`orchestrator up`) with the same `ORCHESTRATOR_API_URL` / `ORCHESTRATOR_API_KEY`
every other client reads — so the plugin process needs no database or Temporal
access, the registry scopes results to the key's tenant, and the audit log
records the key as the actor. `registry_trace` is bounded: the newest `tail`
entries plus a `truncated` count. A registry that is down returns `error` + a
`hint` rather than failing the call. Deciding is annotated destructive, because
a rejection ends the run.

- **The MCP server's tiers are metadata a host can act on.** Every plugin tool is
registered with MCP tool annotations derived from its tier — read-only,
destructive, idempotent, open-world — so a host that confirms before destructive
calls confirms before `sdlc_feature`, `sdlc_start_run` and `sdlc_decide_gate`,
and not before `map_repo`. The tier table is total by construction: a tool
registered without one is refused, and a test says so first. Design record:
`docs/specs/mcp-plugin-surface.md`, which also fixes the extension order — the
six known gaps before any new surface.
- **`doctor` says which install is answering.** The tool and the CLI both report
the package version, interpreter, MCP SDK version and the extras present. The
case it exists for: a host launched an `orchestrator-mcp` console script left
behind by an older checkout's venv (Spine 3.9.3, no `mcp` module) and the only
symptom was "Connection closed".

Fixed

- **`__all__` in `orchestrator.plugin.server` exports every registered tool.** Four
(`sdlc_plan`, `sdlc_approve`, `docs_for`, `pkg_joins`) were registered but not
exported; a test now holds the two in step.

- **An empty codegen submission gets a second correction — a bad draw is not a
loop.** The corrective retry allowed one correction per failure kind, on the
reasoning that a kind failing twice is looping rather than fixing. That holds for
a deterministic failure (the same bad edit anchor, the same unparseable output)
and not for an empty submission: codegen samples at the provider default, so a
run that answered `summary='placeholder'` and no files drew badly rather than
reasoned badly, and the remedy for a bad draw is another draw. `empty` now gets
two corrections (`_CORRECTIONS_PER_KIND`), every other kind keeps one, and the
overall attempt cap grows by exactly one so the allowance is spendable. Follows
154, which routed placeholder-only submissions into this retry.

Removed

- **The terminal UI (`orchestrator tui`) and the `tui` extra.** Every action it
offered — watch runs, clear gates, delegate a run — is the web inbox or a CLI
command over the same `/v1` API, so it carried an optional `textual` dependency
and a mypy carve-out for no capability the other surfaces lacked. The
assistant-facing surface remains the MCP plugin, which already drives the `sdlc`
pipeline. `pip install 'synaptixs-spine[tui]'` now fails on an unknown extra;
drop it from your install line.

3.30.0

Added

- **A reusable comprehension workflow — G4 Phase 5a.** A repository adds three
lines and gets a pull-request comment saying where its own change lands in its
own code, with `file:line` and caller counts:

jobs:
spine:
uses: synaptixs/spine/.github/workflows/spine-comprehension.ymlv3.30.0

**It declares no `secrets:`.** `investigate` is deterministic and model-free;
the only token is the caller's own `GITHUB_TOKEN`, used to post the comment, and
`comment: false` writes nothing at all. No container image — a cold install with
every language extra measured **21 seconds**, so the usual reason for one does
not hold. The comment is updated in place rather than appended, and the
pull-request title reaches the shell through `env:` rather than inline
interpolation, because a title is attacker-controlled.

**Note on the pin:** this snippet is the first that works. The one published
on `develop` before this cut pinned `v3.29.1`, a tag that predates the file —
copying it produced "workflow not found". Fixed here, where the snippet and the
tag land together.

- **A secrets seam.** `core/secrets.py`: `get_secret(name)` with the environment
as the default provider, `ORCHESTRATOR_SECRETS_PROVIDER` to select another, and
`register_provider` for anything behind an extra. `Settings`, Temporal, MCP auth
and the object store all read credentials through it, **byte-identically** —
under the default it *is* `os.environ.get`. A vault is now a plug rather than a
rewrite, and a fresh interpreter importing the seam loads no client. The first
provider, when an operator wants direct fetch rather than injection, will be
HashiCorp Vault / OpenBao behind `[vault]`; a file provider was considered and
rejected as a static secret with a new attack surface.

- **The read-only path is now guaranteed to need nothing.** A test runs `state`,
`understand`, `investigate` and `pkg extract` in a process holding only `PATH`
and `HOME`. This is the property the developer-adoption story rests on — no
key, no account, no service — and until now nothing defended it. With a fake
requirement injected, four of five tests fail; that is the point.

Changed

- **RBAC was already built and already opt-in**, and `STATE-OF-SPINE` §8 read as
though it were not. Separated from the secrets work it was bundled with: roles
are enforced on the decide path, runs and approvals are tenant-scoped, and with
no `principals` map the single API key resolves to today's behaviour. What
remains — quorum, the untenanted tables, OIDC — is named and waits on a buyer.

3.29.1

Changed

- **`orchestrator --help` is grouped into six panels.** It printed 26 entries in
registration order — the order features were added, which is a changelog, not a
map. They now read top to bottom as a workflow: *Get started* (init, doctor, up,
models, tui), *Understand a codebase* (profile, understand, state, audit),
*Investigate & design a change* (investigate, design, localize, rca,
regression), *Plan & build* (ingest, backlog, openspec, sdlc), *Knowledge graph*
(pkg, media), *Registry & integrations* (template, contract, task, mcp,
catalog). The top-level blurb describes Spine rather than a "registry client",
and internal roadmap codes are gone from user-facing help. A test pins the panel
order and fails if a command is left unpanelled. (306)

- **`cli.py` is a package.** The single 4,300-line module is `orchestrator/cli/`,
one module per help panel with `sdlc` and `pkg` on their own, `_app.py` holding
the root app and `_common.py` the shared helpers. Mechanical: no function body
changed, and `--help` for all 67 entries is byte-identical before and after. The
console entry point `orchestrator.cli:app` is unchanged. Scripts that counted
commands by reading `cli.py` now read the package. (307)

No behaviour, command name, path or option changed in this release.

3.29.0

Added

- **`Endpoint` nodes for TypeScript (Express) and Go (Gin).** Only Java and C
emitted them among the tree-sitter front-ends, and that had a consequence
nobody had stated: the multi-repo `http` joiner matches a consumer's calls
against the **provider's** endpoints, so **a Node or Go service could not be a
provider in a cross-repo join at all.** Multi-repo comprehension has shipped
since 3.22.0 and worked only when the provider was Java, C or Python.

Single-repo, it closes the wrong answer `python_routes.py` exists to kill:
nothing *calls* an HTTP handler — the framework does, at runtime — so a route
handler had zero callers and `impact_of` answered *"safe to refactor"* about a
public endpoint. That was live for every Go and TypeScript service.

Mounts compose: `app.use("/v1", router)` and `r.Group("/v1")`. Three precision
rules, each pinned by a control in the corpus: **a computed path yields
nothing** (a template literal, or a path in a variable); **an inline handler
yields an endpoint but no `EXPOSES`**, because there is no named symbol and
inventing one is fabrication; and **`net/http`'s verbless `HandleFunc` is
deliberately unread**, since the joiner matches on verb equality.

typescript Endpoint 3/3, EXPOSES 2/2 neither existed
go Endpoint 3/3, EXPOSES 2/2 neither existed
multirepo CONSUMES 2/3 -> 3/4

- **Prose that names a file by its stem now binds.** `typescript_extractor` means
`typescript_extractor.py`; `comprehension_labels` means the YAML beside it. 74
edges, and **98 fewer drift findings** — the larger half, since prose naming a
real file was being reported as prose naming code that does not exist.

Restricted to snake-shaped tokens, and the restriction is the rule: matching
any stem binds `bug`, `enhancement`, `invention` and `validity` to modules that
happen to share a filename, when the prose meant the English words.

Changed

- **The doc-binding gap is 51%, and the headline is misleading.** Of the 809
unbound mentions in sections that bind to nothing, **603 are not code claims at
all** — `OpenSpec`, `TypeScript`, `GitHub` — which the tier's own rule already
refuses. **81% of those sections contain nothing bindable**, and at most 60 of
1,621 hold a mention a smarter tier could reach. That is the population a model
tier would chase, against a determinism cost on every gated surface, and the
measurement argues **against** building one.

- Two roadmap blockers cleared, both by scoping rather than building: **G4 gains
Phase 5** (central adoption, split into a reusable workflow and a deployment
image — measured: a cold install with every language extra is **21 seconds**,
so the workflow needs no image), and **the RBAC row is separated from the
secrets seam it was bundled with** — RBAC is built and already opt-in; the
seam does not exist in any spec.

3.28.0

Fixed

- **`pkg extract --repos` and `investigate --repos` could place a `CONSUMES`
edge from a node the graph does not contain.** Both pass one extractor for
every repository, and `unresolved_calls` — the cross-repo join candidates —
accumulates on the **front-ends**, which are built once and reused.
`ClientState.clear()` preserves `unmatched` on purpose, so clearing the
extractor's own list left the previous repository's calls in place for the
next one to inherit. The joiner scopes a candidate to the repository it is
examining, so web's call became `py:billing...` — a fabricated edge, which is
the one thing this graph exists to refuse.

`RepoCodeExtractor.reset_unresolved()` clears the front-ends too, and the merge
loop now resets before each repository rather than trusting a fresh object.

**Live in 3.27.0.** `pkg joins` was unaffected — it builds a fresh extractor
per repository — which meant the joins report and the extracted graph could
disagree with each other.

The corpus could not have caught it: `pkg/accuracy.py` builds a fresh extractor
per root, so the multirepo fixtures score a different code path from the one
the CLI runs.

Added

- **A document citing a path now binds to the module built from that path.**
`src/orchestrator/pkg/store.py` resolves, maps to exactly one `Module` node by
the extractor's own provenance, and drew no edge — the anchor was recorded and
discarded. **534 `MENTIONS` edges, and 55 `Doc` sections that bound to nothing
now bind**; sections binding to nothing went from 55% to **52%**. Exactly one
matching file and exactly one owning module, or no edge.

Changed

- **What the doc-binding gap actually is, measured.** "52% of sections bind to
nothing" reads as half the documentation failing to bind. Of the 809 unbound
mentions in those sections, **603 are not code claims at all** — `OpenSpec`,
`TypeScript`, `GitHub` — which the tier's own rule already refuses. **82% of
those sections contain nothing bindable**, and at most **60 sections of 1,602**
hold a mention a smarter tier could reach. That is the population a model tier
would chase, against a determinism cost on every gated surface.
`scripts/classify-unbound-mentions.py` re-derives it.

- **`CONTRIBUTING.md` lists all four of CI's generated-artifact checks**, not
one. A release failed on a diagram nobody had re-rendered while `mypy`, `ruff`
and the tests were green. The three things that trip a `develop → main`
promotion are written down beside them.

- **A two-page multi-repo walkthrough** — declare, extract, merge, join, read —
beside the roadmap's 624-line design record.

Page 1 of 8

© 2026 Safety CLI Cybersecurity Inc. All Rights Reserved.