OpenUP lifecycle governance for GitHub Spec Kit — with gates that actually stop the run.
SpecUP turns specup.md, a design document, into a working
Spec Kit bundle: an extension, a preset, and four
phase workflows that enforce an Eclipse OpenUP-style
lifecycle with a seven-level WBS, an executable risk register, and bi-directional
traceability.
Status: 0.1.2 released under BUSL-1.1 — source-available, not open source; read Licensing before you adopt it. Verified against spec-kit 1.0.6 and 1.0.8.dev0, and installed end to end from the published catalog into a clean project — see Quick start. Spec Kit's
defaultcatalog carries only components vendored into its wheel, so a self-hosted catalog is the supported route for any third-party project, not a workaround.
| Guide | What SpecUP is, the data model, the lifecycle, the commands |
| Using SpecUP | The operating manual — every command, every check id, every config key |
| New project | Greenfield adoption |
| Existing project | Brownfield adoption, where intent has to be recovered |
| Worked example | A governed program that passes Inception and fails Elaboration, and why each failure is correct |
| Release notes 0.1.2 | BUSL-1.1, the two new check families, and the numbers from governing itself |
| Release notes 0.1.0 | What ships, what is enforced, what is not |
| Publishing runbook | Cutting a release and keeping the catalog honest |
| Taskfile | The task runner, and the boundary it must not cross |
| Licensing | BUSL-1.1 from 0.1.2 — what the grant covers, and what the change costs |
An AI agent can produce plausible, well-structured output whose only provenance is a chat log. Most "AI governance" answers this with more prose — templates and instructions the model is asked to follow, which it may follow, and which nothing checks.
SpecUP's position is that governance is only real if it is machine-checkable and consequential. Concretely:
| Escape route | What closes it |
|---|---|
| State lives in the conversation | Every governed artifact is a version-controlled file |
| Action with no governing intent | A task must resolve to a WBS node, requirement and criteria — or the agent stops |
| Agent invents the missing link | resolve, or stop — never infer, plus validators that reject unresolvable ids |
| Agent declares itself done | Definition of Done is computed from the graph, not asserted |
| LLM opinion as a gate | Gates are Python predicates over the filesystem; a failing one halts the workflow |
| Coverage that means nothing | Every traceability edge declares provenance; coverage is reported next to it |
The last row is the one most governance tooling misses. A project can be 100% "traced" while every edge is an unverified agent claim. SpecUP reports both numbers, always.
Spec Kit has four composition mechanisms. They differ in one way that decides the whole design — only one of them can actually stop anything.
| Mechanism | Provides | Real enforcement? |
|---|---|---|
| Preset | Overrides/composes existing commands and templates (replace/prepend/append/wrap) |
No — prompt-level |
| Extension | New namespaced commands, templates, scripts, config, hooks | No — hooks are LLM-interpreted |
| Workflow | Step sequence with shell, gate, if, fan-out, state and resume |
Yes — a non-zero shell step halts the run |
| Bundle | Composes the above into one pinned, installable unit | Distribution only |
So SpecUP is three layers, each doing only what its mechanism can actually do:
workflows/ ← ENFORCEMENT. shell steps run validators; the engine
openup-{phase}/ branches on the real exit code and halts or pauses.
extensions/openup/ ← CAPABILITY. New /speckit.openup.* commands, the JSON
schemas/ scripts/ schemas, and the validators everything else calls.
commands/ templates/
presets/openup-governance/ ← DISCIPLINE. The only layer that can reach Spec Kit's own
templates/ commands/ constitution, spec, plan, tasks and /implement.
bundles/specup/ ← COHESION. Not a fourth layer — the only place Spec Kit
bundle.yml install.py lets "these three, at these versions" be stated at all.
That last one is load-bearing rather than convenience packaging. The preset instructs an agent to run validators the extension installs. It declares that dependency, so Spec Kit warns when the extension is absent — but a warning is the whole of what Spec Kit offers here: it installs nothing and refuses nothing. Installed alone, the preset still reads as fully authoritative while every check it names silently does not run.
A useful consequence: the path an agent runs and the path a workflow runs are the same
string. Extension commands get no {SCRIPT} substitution, so they reference
.specify/extensions/openup/scripts/python/<validator>.py literally — exactly what the
workflow shell steps invoke. There is no second thing to keep in sync.
# 1. A Spec Kit project
specify init --here --integration claude
# 2. Register SpecUP's catalogs — genuinely one time, per machine.
# `specify catalog add` cannot write here; it writes per project. These files
# also keep Spec Kit's own catalogs, which the per-project route silently drops.
mkdir -p ~/.specify
BASE=https://raw.githubusercontent.com/anvigo12/specup/main/catalog/user
for f in extension preset workflow bundle; do
curl -sSL -o ~/.specify/$f-catalogs.yml $BASE/$f-catalogs.yml
done
# 3. All three layers, in dependency order, each archive verified against its digest
specify bundle install specup
python3 -m pip install -r .specify/extensions/openup/requirements.txt
# 4. Scaffold the governance tree, and generate the documents derived from the checks
python3 .specify/extensions/openup/scripts/python/init_openup.py --program "My Product"
# 5. See where you stand (it will FAIL — an empty plan is not a valid plan)
python3 .specify/extensions/openup/scripts/python/audit.pyStep 3 installs the extension, then the preset, then the four workflows. The order is not cosmetic: the preset's guidance calls validators the extension installs.
Do not skip the pip install. Without PyYAML every validator exits 2; without the two
schema libraries, five of the eleven do — audit.py among them. A workflow then halts on its
setup-fault branch rather than passing a gate it could not evaluate. That is the designed
behaviour, and a confusing way to discover a missing dependency.
Then drive a phase:
specify workflow run ./workflows/openup-inception/workflow.yml \
--input idea="..." --input program="My Product"There is a worked program in examples/. It passes the Inception gate
and fails the Elaboration gate on five conditions, which is the state a real program is in on
its first day:
python3 extensions/openup/scripts/python/evaluate_gate.py \
--gate GATE-LIFECYCLE_ARCHITECTURE --root examples/my-program # exit 1, 5 of 7 failedEvery one of those five has a one-minute repair that turns the gate green and the record
false, and examples/README.md names each one. That is not a gap in the tooling — it is the
boundary of what a filesystem check can establish, and the reason the audit prints the
provenance mix beside every coverage figure.
To install this repository instead of a release — developing SpecUP, needing an unreleased
change, or working offline — use
bundles/specup/install.py in place of
steps 2 and 3.
specup.md describes identifiers three mutually inconsistent ways. Everything here is frozen
in ID-GRAMMAR.md and enforced by four JSON
Schemas, because IDs are graph edges and retrofitting them is the one genuinely expensive
migration.
Identifiers — zero-padded, regex-enforced: REQ-AUTH-0014, WBS-1.2.3.4.1.1.2,
RISK-0007, AC-AUTH-0014-0003, ITER-E-02. Source files have no synthetic id: the
repo-relative path is the identity, so traceability survives a validator rebuild.
Relations — a closed set of 14, stored once in active voice. The inverse is derived at load time and never written. Two hand-maintained directions are two things that can disagree, and disagreement is what traceability exists to prevent.
Provenance — on every edge, and the honest part of the model:
| Value | Meaning | Trust |
|---|---|---|
derived |
Recomputed from the filesystem by a named rule — and the rule is re-run to check | Machine-checkable |
asserted |
Claimed by an agent or author | Claim only |
approved |
Asserted, then signed off by a named human | Governance-grade |
This exists because the agent writing the code otherwise also writes the proof it was traced
— auditor and audited collapse into one process. An approved edge is bound to its endpoints
by hash, so editing either one downgrades it rather than silently keeping the sign-off, and a
derived edge is only counted as evidence once the rule it names reproduces it
(below).
Seven WBS levels — L1 Program → L7 Executable Task, where the number of dotted segments
is the level. specup.md §18 demands every leaf be L7, which collides with its own §65
anti-bloat rule; the default semantic policy lets a leaf terminate early when it declares a
terminal_reason. strict mode implements §18 literally.
Fifteen CLIs. Each emits a JSON verdict on stdout and exits 0 pass / 1 fail / 2
could-not-evaluate. That dual contract is what lets one script serve both an agent and a
workflow step.
| Script | Checks | Covers |
|---|---|---|
init_openup.py |
3 | what the scaffold created, what it left alone, and whether the views rendered |
validate_wbs.py |
12 | level/id agreement, parentage, single root, depth policy, reference resolution, dependency cycles |
validate_risk.py |
8 | exposure arithmetic, residual reduction, mitigation and verification for high risks, both-ends agreement |
validate_trace.py |
14 | endpoint resolution, relation type legality, duplicates, cycles, forward/backward coverage, orphans, provenance floor, derivation reproducibility, approval binding, scenario coverage |
validate_done.py |
6 | the Definition of Done, computed from the graph rather than declared |
validate_approvals.py |
6 | the approval matrix names somebody, approvers are named in it, and a declared commit exists and carries a signature this project trusts |
validate_context.py |
4 | the AGENTS.md / index.md / SKILL.md hierarchy, and that every id a context map names resolves |
validate_docs.py |
7 | a docstring is the working context for its unit — anchored, bounded, honest about what it claims to have cross-checked, and finite |
derive_edges.py |
4 | what the derivation rules recover; --write regenerates the machine-owned store |
render_views.py |
2 | the generated Markdown views are present and match their sources; --write regenerates them |
approve_edge.py |
— | records a human approval, bound by hash to the content approved |
impact.py |
— | what a change to one artifact reaches, in both directions (§52) |
select_work.py |
3 | Definition of Ready, risk-first ordering |
evaluate_gate.py |
23 conditions | every condition name declared in config |
audit.py |
— | the aggregate §33 report |
Exit 2 is not exit 1. A graph that fails to load is a setup fault, not a governance failure. Collapsing them would tell someone their project failed its milestone when the truth is a malformed YAML file.
provenance: derived claims a rule recovered an edge mechanically, and the audit counts it as
machine-checkable on that basis. The schema can only require derived_by to be present — so
until TRC-010, relabelling an assertion derived was free and moved the very ratio the audit
reports. An agent grading its own traceability had a one-word bypass.
derivers.py implements the rules, and
TRC-010 re-executes the one an edge names:
| Rule | Produces | |
|---|---|---|
gherkin-tag-scan |
SCEN-* → executes → AC-*, from @ tags in .feature files |
implemented |
test-file-naming-convention |
test artifact → tests → the file its source is named after |
implemented |
wbs-iteration-field |
WBS node → belongs-to → iteration |
implemented |
openapi-operation-scan, evidence-manifest-scan |
— | not implemented |
An edge an implemented rule does not reproduce fails (TRC-010). An edge a rule produces that
no store declares fails (TRC-011) and is fixed by derive_edges.py --write. An edge naming
one of the two unimplemented rules is counted as derived_unverified — reported, never
trusted.
The same defect sat one level up. approved_endpoints_hash was required by the schema and
read by nothing, so approved was a string anyone could type — and traceability_final
counted it as evidence. On the reference fixture, relabelling the eleven asserted edges
approved moved the verifiable share from 38% to 81%.
TRC-013 now recomputes that hash from the endpoints as they stand. An approval binds the
content it was given for: edit either endpoint and the edge drops out of
approved_verified, which is the count the gate scores. Record one with
approve_edge.py — the hash cannot be produced by hand, and shipping the check without the
tool would have recreated the original mistake in a new place.
traceability_final scores reproduced derivations plus verified approvals, so a graph
cannot buy its way past by renaming its own claims at either level.
§56–57 argue this approach scales because an agent navigates
directory → AGENTS.md → index.md → the one artifact it needs, instead of loading the
repository. init_openup.py scaffolds that hierarchy — an operating contract at the root,
.specify/ and src/, a context map in every governed directory, and six capability
contracts under skills/ — and validate_context.py checks it is true:
| Check | ||
|---|---|---|
CTX-001 |
every governed directory has an index.md |
WARN |
CTX-002 |
every identifier an index.md names resolves in the graph |
FAIL |
CTX-003 |
an AGENTS.md is reachable at or above every governed directory |
WARN |
CTX-004 |
every skill a WBS node names exists | FAIL |
The split is deliberate. A missing map is a gap, and a project adopting SpecUP should not fail
its first audit over absent documentation. A map that misleads is a defect: an index.md
naming a deleted requirement sends an agent looking for something that is not there, and an
agent willing to infer will fill the hole itself — the exact failure the rest of the model
exists to prevent.
AGENTS.md and SKILL.md are kept apart on purpose (§7 vs §8): what rules must I obey
versus how do I perform this activity. Merged, you get a document followed for neither.
Two of §8's eight skills — contracts and microcks — are deliberately not shipped: nothing
here parses a contract document, and a capability contract for a capability that does not
exist teaches an agent to claim it.
Nothing that can be computed is written by hand — including the documents. render_views.py
regenerates all six from their canonical source, and without --write it reports any that are
missing or stale (VIEW-001), so a workflow fails on a drifted view rather than shipping one:
| Document | Generated from |
|---|---|
.specify/wbs/wbs.md |
wbs.yaml |
.specify/risks/risk-register.md |
risk-register.yaml |
.specify/traceability/traceability.md |
the relation stores |
.specify/traceability/coverage.md |
validate_trace.py — coverage and the provenance mix |
.specify/governance/definition-of-ready.md |
select_work.py |
.specify/governance/definition-of-done.md |
validate_done.py |
.specify/governance/quality-gates.md |
openup-config.yml + evaluate_gate.py |
The last three matter most. A Definition of Ready that disagrees with select_work.py, or a
gate description that overstates what evaluate_gate.py checks, is worse than no document at
all: people follow the document while the machine applies the code. approval-matrix.md and
change-control.md are the exceptions — who may approve what is a decision about your
organisation, so those are authored from templates and never generated.
init_openup.py seeds three more governance documents, and these are neither generated nor
organisational. They come from published specifications, and they bind the agent through the
root AGENTS.md and Principle VIII of the constitution addendum:
| Document | Binds | Standard |
|---|---|---|
.specify/governance/language-rules.md |
every governed document | ASD-STE100 Simplified Technical English |
.specify/governance/coding-rules.md |
every change to code | Railway Oriented Programming, RFC 9457 problem details, cloud-native data patterns |
.specify/governance/security-practices.md |
design, code, and the security gate evidence | the rules the security review applies |
The first two close a gap the structural checks cannot reach. A requirement written in a 40-word sentence with two readings still registers, still traces, and still passes every check, while the implementation, the test and the gate each answer a different question. A failure thrown as an exception carries no type and no id, so it cannot be bound to an acceptance criterion or counted as evidence. Both are the same defect — meaning left in someone's head — and neither is visible to a validator.
None of the three is machine-checked. They are enforced at review, each says so in its own
closing section, and an agent's claim to have followed them is asserted. Adding a checker
would be a real feature; pretending one exists would be the exact defect these documents are
about.
§65 says "Do not manually maintain traceability documents." Asking an agent to transcribe YAML into Markdown is still manual maintenance, with an extra failure mode: it is non-deterministic, every regeneration is a fresh diff, and nothing stops it from quietly disagreeing with the canonical data.
Four milestone gates, 23 condition declarations over 22 implementations, defined in
openup-config.yml:
| Gate | Phase | Asks |
|---|---|---|
GATE-LIFECYCLE_OBJECTIVES |
Inception | Is the scope, ownership and initial risk picture real? |
GATE-LIFECYCLE_ARCHITECTURE |
Elaboration | Is the architecture baselined and are the high risks handled? |
GATE-INITIAL_OPERATIONAL_CAPABILITY |
Construction | Is it built, covered, free of orphans, and actually done? |
GATE-PRODUCT_RELEASE |
Transition | Can it be released and operated? |
Two rules the implementation holds to:
- Absence of evidence is not evidence. A missing test report or review record fails its condition. A gate that passed because a file was never written would be worse than no gate.
- Conditions fail closed. A condition declared in config with no implementation, or one that raises, fails. It is never skipped.
Every phase workflow ends with the same block:
- id: evaluate-gate
type: shell
run: "python3 .specify/extensions/openup/scripts/python/evaluate_gate.py --gate <GATE> --json --out <evidence>"
continue_on_error: true # so the branches below are reachable
- id: halt-if-unevaluable # exit 2 → setup fault, halt hard
type: if
condition: "{{ steps.evaluate-gate.output.exit_code == 2 }}"
- id: enforce-gate # exit 1 → governance failure, human decides
type: if
condition: "{{ steps.evaluate-gate.output.exit_code == 1 }}"
then:
- id: gate-failed
type: gate
options: [reject, override]
on_reject: abortcontinue_on_error: true looks like a weakening and is the opposite. A non-zero shell step
halts the whole run by default — which enforces, but makes the human branch unreachable and
leaves the operator with no verdict to read. Recording the exit code and branching explicitly
keeps the failure consequential and legible.
An override writes the failing verdict to .specify/evidence/overrides/<run_id>.json,
because an undocumented override is indistinguishable from a gate that never ran.
Run against spec-kit 1.0.6 with the real engine, using tests/fixtures/good:
| Graph state | Result |
|---|---|
| Healthy | Status: completed — runs to the final step |
| High-exposure risk stripped of its mitigation | Status: paused at [gate-failed] — final step never reached |
wbs.yaml corrupted so the graph cannot load |
Status: failed, exited with code 2 |
And the bundle, into a clean specify init project.
Re-run at 0.1.2 against the published catalog, in a throwaway project, using the
project-scoped catalog add route — and against spec-kit 1.0.8.dev0, not the 1.0.6 the
engine rows above were run on. That is the first evidence behind bundle.yml's
speckit_version: ">=1.0.0,<2.0.0" at anything other than its floor:
| Step | Result |
|---|---|
specify bundle install specup |
✓ Installed 'specup' (6 added, 0 already present) |
specify bundle list after that |
specup v0.1.2 (6 components) — a non-zero count is the proof the install was real |
| Published archive digests | all 7 assets fetched from the v0.1.2 release: HTTP 200, digest equal to catalog/, tag matching the version |
specify extension list |
openup (v0.1.2), 9 commands, 4 hooks, priority 10 |
specify preset resolve spec-template |
[append] openup-governance v0.1.2 composed onto core |
specify workflow list |
four openup-* workflows, all v0.1.2 |
9 speckit-openup-* skills |
registered under .claude/skills/, beside spec-kit's own 10 |
init_openup.py once |
scaffold and the generated governance views |
audit.py on the fresh scaffold |
exit 1 — initial_risks_registered, wbs_levels_1_to_3_valid and requirements_have_owners all FAIL, correctly. Exit 1 rather than 2 is also what proves the Python dependencies resolved |
specify bundle build |
specup-0.1.2.zip, 3 files, fixed timestamps |
| Clean rebuild of all 7 archives | byte-identical digests — reproducible |
Not re-run at 0.1.2, and recorded as such rather than carried forward silently. These were exercised at 0.1.1 against spec-kit 1.0.6 and nothing since is known to have changed them:
| Step | Result at 0.1.1 |
|---|---|
catalog/user/*.yml copied to ~/.specify/, no catalog add at all |
all four resolve from user scope; default and community survive — specify extension search still finds 173 |
install.py --project <clean project> |
6 components installed in manifest order, pins checked |
init_openup.py re-run |
reports 0 created, 35 preserved |
A pin bumped to 0.2.0 in bundle.yml |
install refuses before touching the project |
The three single-component routes were exercised separately against the published 0.1.1
release. The community-catalog submissions on github/spec-kit attest to the same routes at
0.1.0, the version they were filed for; that release is still published and its assets
still resolve at the digests those issues cite:
| Step | Result |
|---|---|
specify extension add openup --from <release url> |
exit 0, 9 commands registered |
specify preset add --from <release url> |
exit 0, spec-template resolves to [append] openup-governance v0.1.1 |
specify bundle install ./specup-0.1.1.zip (downloaded artifact) |
6 added, 0 already present |
specify bundle validate --path bundles/specup |
✓ specup is well-formed and valid |
preset add into a project with no openup extension |
warns openup is not installed, names specify extension add openup, installs anyway |
One gap in that record, stated rather than glossed: the verification host already had PyYAML
and jsonschema present, so the pip install -r requirements.txt step was not exercised from
a genuinely bare interpreter. The exit 1 above is a real governance verdict rather than a
masked setup fault — the audit named WBS-004, TRC-000 and three failing gate conditions —
but the missing-dependency path itself is covered only by the validators' own exit-2 contract.
The preset was likewise installed into a real specify init project: all four core templates
gained their [append] layer, the composed speckit-tasks skill had zero literal
{CORE_TEMPLATE} placeholders with pre/core/post in order, {SCRIPT} resolved identically to
an unwrapped command, and specify preset remove left nothing behind.
specup.md the original design document (unchanged)
extensions/openup/
extension.yml manifest: 9 commands, 18 templates, 17 scripts
schemas/ ID-GRAMMAR.md + 4 JSON Schemas
scripts/python/ the validators (~4,800 lines)
templates/ starter WBS, risk, traceability, vision, index,
architecture + MADR ADR, the three binding standards,
6 capability contracts
openup-config.yml thresholds, perimeter, gate definitions
presets/openup-governance/ 4 append addenda + 2 wrap overlays
workflows/openup-{phase}/ the four phase workflows
bundles/specup/ bundle.yml + the working-tree installer
catalog/ the four published catalog documents (generated)
tools/ archive + catalog generators
examples/ a worked program + the deliverable it governs
tests/ 407 tests + fixtures
.specify/ SpecUP's OWN governance tree — it governs itself with
itself, fails its own Construction gate, and says why
.specify/ is the project tree, not a second copy of the product. It holds SpecUP's
requirements, WBS, risks and traceability, plus a config that is deliberately separate from
the one under extensions/openup/: that one is the default every installing project
inherits, this one is how SpecUP governs itself, and conflating them would mean re-tuning
every new project's starting point whenever SpecUP tuned its own. Start at
.specify/traceability/index.md, which records the real
figures and the seven defects that running the tool on itself exposed.
The design document is the source of intent, not of fact. Building against the real spec-kit surfaced errors in it, recorded here because they are easy to repeat:
- §34's command names cannot exist.
/speckit.wbs,/speckit.risk,/speckit.auditare two-segment. Extension commands must match^speckit\.[a-z0-9-]+\.[a-z0-9-]+$. They are/speckit.openup.*here; two-segment names are core-only and reachable solely by a preset. - §17's own WBS example is malformed. It declares
id: "1.2.3.4.1.2"withlevel: 7, but that id has six segments. CheckWBS-001catches exactly this. - §19's
TEST-PERF-022is outside the ID grammar the same document defines in §48. - §48 defines eight prefixes it never uses, while §21/§66/§67 use three overlapping relation vocabularies. Both are now closed sets.
- §18 contradicts §65 — "every leaf must be L7" versus "do not create seven levels of
narrative". Resolved with the
semanticdepth policy. - Exposure is never defined. §19 shows
0.6 × 0.9 = 0.54by example, and §19/§32/§63 all gate on "high" without saying what high is. Now a declared threshold. - Hooks are not enforcement. §30's gates cannot be built on
extension.ymlhooks: those render as instructions into a prompt and an agent can decline them.
Two things were added that the document does not have, because without them its central claim is circular: provenance on every edge, and the derived/asserted/approved ratio in every audit.
python3 -m pip install pyyaml jsonschema referencing pytest
python3 -m pytest tests/ -q # 399 passed, 8 skippedThe 8 skips are the engine-validation tests. To run them, install spec-kit:
uv venv .venv && uv pip install --python .venv/bin/python specify-cli==1.0.6 pytest
uv pip install --python .venv/bin/python -r extensions/openup/requirements.txt
.venv/bin/python -m pytest tests/ -q # 407 passedOr with Taskfile, which wraps both suites and the release pipeline:
task test:both # the pair is what proves the skips are honest
task release:check # everything that must be green before taggingtask is a contributor convenience and never a runtime dependency — see
the boundary, which a test enforces.
They skip rather than fake when spec-kit is absent: a green test that did not run the engine would be worse than an honest skip.
| Suite | Covers |
|---|---|
test_validators.py |
One deliberately-broken fixture per invariant |
test_derivers.py |
The derivation rules, and that derived cannot be claimed without them |
test_extension_manifest.py |
Spec Kit's documented manifest rules |
test_installed_layout.py |
The extension in its real .specify/extensions/ layout |
test_workflows.py |
Structure, shell-injection rule, engine validation |
test_preset.py |
Preset schema, wrap contract, frontmatter preservation |
test_bundle.py |
Manifest schema, version-pin drift, the preset→extension pairing |
test_taskfile_boundary.py |
No workflow, command or manifest may reach the task runner |
Tests are mutation-checked. Neutering WBS-001, RISK-001, TRC-010, TRC-011,
TRC-012, the fail-closed path, the shell-injection rule, the wrap frontmatter rule, a
bundle version pin, and the bundle's extension entry each makes the corresponding tests
fail. Reverting traceability_final to scoring the asserted label instead of reproduced
evidence fails two.
specify bundle installcannot install an unreleased working tree. Spec-kit resolves component ids only against assets shipped in its own wheel or a published catalog, and there is no--devfor bundles. That is fine for a release — the catalog route in the Quick start is exactly this — but it means an edit you have not released is unreachable through it.install.pycovers that case, and is also the only offline route. See Why the two cannot be collapsed.- Nothing validates a contract document.
MICROCKS-TEST-*ids and thevalidatesrelation are reserved in the grammar so nothing has to be renamed later, but no contract checking runs at all:contracts.microcks.enabled,openapi_globandasyncapi_globare read by no code, andcritical_contracts_definedonly asserts that each registeredCONTRACT-*artifact's declaredsourcefile exists on disk — the OpenAPI/AsyncAPI document itself is never parsed. Mocking and conformance isspecup.md's own Maturity Level 4; static schema validation is not built either. - Two of five derivation rules are unimplemented.
openapi-operation-scanandevidence-manifest-scanare named inspeckit.openup.trace.mdbut have no code behind them, so an edge claiming one of them cannot be reproduced.TRC-010reports those edges as unverified rather than counting them as machine-checkable. - An approval binds a human only when it is witnessed. Since 0.1.2 an approval has two
levels, and the difference is the whole of what can be claimed. Witnessed: the approval
names a commit, the commit exists, and its signature verifies against
.specify/governance/allowed-signers— a trust root kept in version control, so the answer does not depend on the machine the check runs on (APV-001,APV-002). Claimed: a name and a date, and nothing tying them to a person —approve_edge.py --by product-owneris still something an agent can run, and a claimed approval proves exactly as much as it did in 0.1.1.APV-004warns on every claimed approval and the audit prints the witnessed/claimed split, so the gap is visible rather than assumed away;approvals.require_witness_at_or_aboveis the ratchet that makes it fail. Two further limits stand: the trust root is only as good as the keys listed in it, andapproved_endpoints_hashstill proves only that an edge matches what was signed off (TRC-013), not that the signer read it. - The three binding standards are unenforced by design, for now.
language-rules.md,coding-rules.mdandsecurity-practices.mdare prompt-level rules: the rootAGENTS.mdand Principle VIII of the constitution bind an agent to them, and a human reviewer is the only thing that checks. Parts of all three are mechanically decidable — sentence length, the non-approved word table,application/problem+jsonon every failure response, the RFC 9457 member set — and a validator over that subset is the obvious next increment. It is not written, so do not read a passing gate as evidence of conformance to any of them. - No CI enforcement yet. The validators are CI-ready by construction — JSON out, exit codes — but no pipeline is authored (§62–63).
python3in shell steps. Windows hosts normally havepython; adjust therun:lines or provide a shim.- Governance overhead is measured once, by its author, on itself.
specup.md§65 warns that process can outgrow its value, and §57 implies ~10 artifact fetches per task against §33's example of 421 tasks. There is now one data point: 0.1.2 governs SpecUP with SpecUP, and setting the tree up took a single sitting and produced 18 requirements, 13 WBS nodes, 4 risks and 74 edges over a 17-file perimeter. That is not a measurement. It was done by the person who wrote the tool, on a repository whose layout the tool was designed around, with no second reviewer and no delivery to compare the cost against. Treat it as an existence proof that the process terminates, and nothing more. The figures are in.specify/traceability/index.md. - SpecUP's own audit fails, and the numbers are not flattering. Backward coverage is 100%
over a perimeter its author chose, and 84% of its graph is
asserted— one person's judgement, which no check can confirm. Its owntest-file-naming-conventionrule reproduces exactly one of its edges, because SpecUP names test files after the behaviour under test rather than after the module.RISK-0003records that; the Construction gate fails onacceptance_scenarios_passing, and it is not being repaired by hand.
Business Source License 1.1 from 0.1.2, with an Additional Use Grant that permits production use and a Change License of MIT that takes effect four years after each version is published.
It is source-available, not open source — the OSI does not approve BUSL, and this README is not going to call it something it is not.
| Yes | Govern your own software with it, at any scale, commercial or not |
| Yes | Run agents against repositories you or your organisation control |
| Yes | Read, fork, modify and redistribute under these same terms |
| Yes | Sell consulting, integration or development services around it |
| No | Offer SpecUP's functionality to third parties as a commercial product or service |
Two things a reader deciding whether to depend on this should know, both of which cut against the project's own interest:
- 0.1.0 and 0.1.1 remain MIT, permanently. MIT is irrevocable for copies already
distributed, so the
v0.1.1tag is a permissively licensed governance engine that anyone may fork and continue, forever. This relicense protects work that has not shipped yet — mostly the agent runtime — and nothing before it. - It costs SpecUP its listing in Spec Kit's community catalogs. Their publishing guide requires an "Open source license file (MIT, Apache 2.0, etc.)", which BUSL is not. Those catalogs are discovery-only — installation goes through SpecUP's own catalog and is unaffected — but the listing is gone, and that is a real cost of this decision rather than a detail.
docs/dev/licensing.md has the full position: what the grant covers,
why the Change License is MIT rather than Apache-2.0, and what checks any of it.