Seshat BI v1.0.0
Seshat BI -- v1.0 release candidate
This candidate records the changes merged after the frozen v0.8.2 release note.
It does not rewrite the historical claims captured in docs/releases/v0.8.md,
docs/releases/v0.8.2.md, or any earlier note.
The full per-entry detail (with spec and PR citations) lives in CHANGELOG.md;
this note summarizes the shape of the release.
The headline boundary: Studio ships as an INCOMPLETE PREVIEW
Read this before anything else in this note.
This release ships the first installable slice of Seshat Studio -- a
localhost analyst console (spec 139) -- and Studio is not finished. It is
published now, deliberately and knowingly, so the 86 commits behind it stop being
invisible to installed users; a second release follows when Studio completes.
What that means concretely, for anyone who finds the seshat-studio console
script in an installed 1.0.0:
- Studio has no approve, apply, or reject surface. By design, not by omission.
Spec 139 Phase 4 ships the read/observe half only. There is no handler in
src/seshat/studio/that grants an approval or applies a change. Approval
remains exactly where it was -- a human edit toreadiness-status.yaml, read
through the sameapproval_is_shape_validthe gate rule uses. - The Codex bridge has no production caller.
CodexBridgeand itshealth()
path are shipped, tested, and reachable only from tests; no CLI verb or
endpoint drives them. This is tracked as open issue #618, "wire CodexBridge
into startup, behind an off-event-loop turn drain," which this release does
not close. Do not read "Codex integration" into this release as a working
user-facing feature. - Studio is behind an optional extra and is absent from a base install. A
plainpip install seshat-biinstalls no FastAPI/Uvicorn. Running
seshat-studiowithout the extra prints a named diagnostic naming both install
lanes, not anImportErrortraceback (FR-006). - The published wheel carries NO Studio frontend, so
seshat-studiois not
usable from PyPI in this release (issue #623).release.yml's
build-validatejob builds the wheel without running
scripts/build_studio_frontend.py, andsrc/seshat/studio/static/is
gitignored generated output -- so there is nothing forpyproject.toml's
artifactsre-include to collect. Measured on a wheel built exactly the way
the release job builds it: 0studio/staticentries, while the
seshat-studioconsole script ships. This fails closed, not blank:
describe_missing_assetsreports the missing build with a recovery action
rather than serving an empty workspace. CI being green is not evidence against
this --ci.ymldoes build the frontend, so the wheel-content tests run and
pass there, while inrelease.ymlthey never run at all
(tests/unit/test_studio_frontend_build.py:39skips without the build output).
Tracked for the Studio completion release, when a working UI is the point.
Nothing in Studio grants an approval, moves a readiness stage, or emits a
confidence score. The governance boundary this kit exists to enforce is
unchanged by it.
Why this is a MAJOR release
Short version: two independent breaking CLI contract changes force the MAJOR
row -- see "TWO changes hit the MAJOR row" below, which is the section that
decides the number. The additive inventory in this section is what the release
adds; it is not what sets the version.
This candidate carries 86 non-merge commits across 27 merged pull requests --
31 feat, 26 fix, 14 docs, 10 refactor, and the remainder
build/test/style/ci.
The 27 is deliberately not the count of #-references in commit subjects, which
is 31: four of those references are issues, not pull requests (#618 among
them). Each of the 27 was confirmed to resolve as a pull request via the GitHub
API rather than inferred from the (#N) squash-subject convention.
Measurement cutoff: those totals are v0.8.2..18492bc -- from the commit
tagged v0.8.2 to the merge of PR #620, which was the head of main when this
inventory was taken. They deliberately exclude the release-preparation commits
that follow, including the commit that adds this note and the version
projection commits prepare-coordinated-release creates afterwards. A count
stated inside the release it counts cannot include itself, so the cutoff is named
here rather than left for an auditor to infer.
Two additive surfaces would, on their own, have put this on the MINOR row of
docs/operations/versioning-policy.md:
- A new console script,
seshat-studio. Deliberately outside the
seshat/retaildispatch chain, so it adds no subcommand to an existing verb
and changes no existing verb's behavior. That is the policy's "NEW CLI verb,
no change to existing verb behavior" row. - A new public subcommand,
seshat spec-status [--fix](64993f8),
registered insrc/seshat/cli/parser_core.pywith a production handler and a
capability entry. Absent atv0.8.2; same MINOR row.
The full verb inventory was diffed rather than sampled --
add_parser("...") across parser.py and parser_core.py goes from 40 verbs
at v0.8.2 to 41 at HEAD, with spec-status the only addition and nothing
removed.
No new seshat check rule id was registered in this release, and the rule-set
growth row does NOT apply. This is stated explicitly because the diff invites
the opposite reading: git diff v0.8.2..HEAD --name-status -- src/seshat/rules/
reports 44 files, every one M, zero A. Those 44 modifications are the
rule-coverage migration (#589, #590) and are declaration metadata only --
each adds a Requirement/corpus declaration and, where applicable, a
requires=(...) entry plus a ReportsItsOwnAbsence note, so the framework can
distinguish "this rule was silent because the repo is clean" from "this rule was
silent because it never found its input." No rule predicate was broadened, no
new failure mode was added inside an existing rule id, and no severity moved
from warning to blocking.
That distinction is load-bearing, so it was measured rather than assumed: the
policy's MAJOR row covers "a broadened predicate, a new failure mode inside an
existing rule id," and 44 modified rule files would otherwise be the shape of
exactly that. A repo that was green on 0.8.2 is judged identically by these 44
rules on 1.0.0.
TWO changes hit the MAJOR row -- and they are why this release is 1.0.0
Both were found by external review on the release PR (#622), not by the
automated gates. They are independent: a consumer can be broken by the second
without ever invoking the four verbs in the first.
Breaking change 1 -- four PBIR verbs gain two required flags
1ac28b5 makes --repo and --table newly REQUIRED on four verbs that
already shipped in published v0.8.2, and their handlers return exit code 2
when the new gate refuses. The verbs are pbir-apply-theme,
pbir-format-visual, pbir-set-page-background, and pbir-set-geometry.
Measured, not inferred:
- All four verbs are present in
v0.8.2:src/seshat/cli/parser.py. _add_pbir_authoring_gate_argumentsdoes not exist atv0.8.2-- the two
flags are new, and both carryrequired=True.- So a script that ran
seshat pbir-apply-theme --theme X --report Yagainst a
published 0.8.2 install fails with an argparse error after upgrading, and a
gate refusal now exits2where the command previously proceeded.
docs/operations/versioning-policy.md classifies this on the MAJOR row: "A
CLI verb's existing flag/output contract changes (e.g. ... a flag is removed or
renamed, an exit-code meaning changes) -- Breaking for any consumer/script
parsing that output."
Breaking change 2 -- pbi-mcp doctor exit code and JSON shape
seshat pbi-mcp doctor now exits 2 where v0.8.2 exited 0, whenever
prerequisites are unresolved. Measured:
- At
v0.8.2,src/seshat/cli/commands/pbi_mcp.pyended_run_doctorwith
return 2 if rec.blocked else 0. - On HEAD (line 146) it reads
return 2 if rec.blocked or rec.missing_prerequisites else 0. tests/unit/test_pbi_mcp_cli.pyrecords the flip directly -- an existing
assertion moves fromassert code == 0toassert code == 2, and a new
test_doctor_exits_two_for_unresolved_prerequisitespins it.
So seshat pbi-mcp doctor --intent published-query --json returns 0 on a
published 0.8.2 install and 2 on 1.0.0 for the same repository state. Any
automation treating this advisory command's zero exit as "success" breaks --
and it never touches the four PBIR verbs above.
The --json payload also gains six fields (target,
target_semantic_model_ready, dashboard_ready, dashboard_ready_tables,
dashboard_design_approval, official_report_skills). Additive for a tolerant
reader, but it is an output-shape change on the same MAJOR row, so it is recorded
here rather than left for a consumer to discover.
Migration: pbi-mcp doctor is advisory -- treat a 2 as "prerequisites
unresolved," not as a hard failure. Automation that gated on exit == 0 should
read the JSON payload's own status fields instead of the process exit code.
The exit-code sweep, so this list is complete
After the second finding, every changed return-code line under src/seshat/cli/
and src/seshat/*.py was diffed rather than sampled. Exactly one existing
contract moved -- the pbi-mcp doctor pair above. The other changed returns are
in new code paths: spec_status.py is a new verb (documented above), and
integrations.py only gains an optional --harness flag with no change to its
existing returns. No other verb's exit-code meaning changed.
Both are disclosed rather than buried, following the precedent
docs/releases/v0.8.md set for the HR1 gold_placement change. Unlike that
case, neither of these is a bug fix restoring documented intent -- the first adds
a new required approval gate to verbs that previously ran without one, and the
second changes what a zero exit means on an advisory command. That is the break
semver's MAJOR clause exists for.
The owner selected 1.0.0 on this basis (Ahmed Shaaban, 2026-08-13.) The
note was first drafted proposing 0.9.0 on the strength of the additive
surfaces above; that proposal accounted for neither breaking change. Both
were found by external review on the release PR (#622) after the version had
already been chosen -- 1ac28b5 first, and the pbi-mcp doctor contract in a
later review round, which is also what prompted the exhaustive exit-code sweep
recorded above. Presented with the measured breaks, the owner moved the release
to the MAJOR row rather than keeping the smaller number -- which is what the
policy asks for: ambiguity resolves toward the larger bump, and a bump class is
never skipped because a change is small.
Per Principle V and hard rule #9 the version is a named human decision; the
agent proposed and measured, and did not select.
Migration summary -- both changes:
- Scripting the four PBIR verbs: add
--repo <root> --table <table>to
every invocation ofpbir-apply-theme,pbir-format-visual,
pbir-set-page-background, andpbir-set-geometry. Without both flags the
command exits with an argparse error; when the authoring gate refuses the
mutation the handler exits2. - Gating on
pbi-mcp doctor's exit code: stop treating0as success.
It now exits2when prerequisites are unresolved. Read the--json
payload's status fields instead of the process exit code.
What 1.0.0 does NOT claim
A 1.0.0 is the semver consequence of the breaking flag change above. It is
not a statement that every surface in this kit is now stable and complete,
and this note does not make that claim:
- Studio remains an incomplete preview with no approve/apply/reject surface,
and its frontend does not ship in the wheel at all (see the boundary section
and issue #623). - The Codex bridge has no production caller (open issue #618).
- Live publish to a Power BI workspace is still the deferred F016 adapter.
The number communicates blast radius to a consumer pinning this package, exactly
as docs/operations/versioning-policy.md says it should. It is not a maturity
score -- hard rule #9 forbids assigning one.
What this release adds
-
Seshat Studio, Phases 2-4 (spec 139; #606, #607, #608, #609, #610, #611).
The largest feature in this release, and an incomplete preview -- see the
boundary section above. It lands the package and security skeleton (#606), the
workspace projection (#607), the deterministic endpoints (#608), the frontend
workspace and an offline build pipeline (#609), the Command Room journey and
agent health (#610), and the event stream, agent bridge, and conversation
(#611). Shipped behind the optionalstudioextra. -
A Codex protocol and process layer (#612, #613, #617). The protocol layer,
a version gate, and explicit auth selection (#612); the process layer and
session lifecycle (#613); and spawning and managing the real Codex app-server
process (#617). Reachable from tests only -- see #618 in the boundary
section. -
Official-first integration rationalization, phases 1-6 (#597). Plus a
closed-world plugin capability declaration and native plugin surface
validation, and the capability oracle split into three layered modules (#598). -
dbt execution evidence read into governance (spec 150, Phase 7; #599).
-
The Spec Kit template fork removed, and status governance externalized
(spec 151; #600). One less vendored fork to drift. -
Rule-coverage migration finished -- 80 undeclared rules to 0 (#589, #590).
Coverage declared for the five singly-scoped corpora, then the migration
closed out. -
Capability ownership fields (spec 142). An ownership vocabulary with
validation and a reader, four piloted wrappers proving FR-004 on real data,
and all 102 entries classified. -
Finance GL genericity proof completion (spec 137). Silver and Gold finance
GL SQL authored, and seven metric contracts authored and stopped at the gate.
What this release fixes
-
A dead stderr credential regex, revived (#616). A
0x08byte introduced
from a heredoc had killed the pattern instudio/codex_process.py, so the
regex matched nothing and credentials in Codex stderr went un-redacted. The
test that covered it was vacuous and stayed green. This was a fail-OPEN in a
redaction path. -
Studio turn-lifecycle correctness (#618, #619, #620). Turns are accepted
rather than drained inside the request; the turn pump is serialized and turns
nobody is polling are reaped; the thread ends when reaping and generators close
off the event loop; evicted threads are not resurrected, and close happens only
after the reader returns. The governing constraint: nothing bound to an event
loop survives its request, so there is no background task to lean on. -
Power BI gate hardening. Bounded PBIR mutations are gated on an approved
design, Power BI discovery and target gates are unified, and Power BI facts are
scoped to the exact table.
Dependency updates
actions/setup-node4.4.0 -> 7.0.0 (#614).dagster1.13.16 -> 1.13.17 in/orchestration/dagster(#615). Note that the
Dagster pin has two sites; both move together or two gates fail on one
defect.
Known limitations carried forward
Everything in docs/releases/v0.8.md's limitations section still stands unless
this note says otherwise. Specifically carried forward, plus what is new:
- Studio is an incomplete preview with no approval surface, and the Codex
bridge has no production caller (#618). Restated here because it is the most
likely thing to be misread in this release. - Live publish to a Power BI workspace remains the deferred F016 adapter.
publish_readyis a readiness/approval verdict, never a live push. seshat tmdl-doc-comment-lintis one rule, not a validator. Issue #494's
broader TMDL-validation gap stays open.seshat xrayandseshat model-diffare advisory. A clean X-Ray is not an
approval and not a statement that the model loads in Desktop.- Public Claude/OpenAI catalog submission is a separate, deferred owner
action. The Claude and Codex repository-plugin bundles and marketplace
metadata live in this repository and update when the release PR merges -- no
separate package upload is required for the repository-plugin path. The public
catalog listing is not part of this release.
Release mechanics (for whoever fires the dispatch)
-
Cut via the
prepare-coordinated-releasedispatch,version=1.0.0, from
main. It projects the version, freezes[Unreleased]into## [1.0.0], and
opens a draft PR onrelease/v1.0.0. -
The release PR must merge-commit, never squash. Squash and rebase are both
enabled on this repository, so the merge method has to be chosen explicitly --
either loses the projection commit that bundle provenance records. -
Bot-authored release PRs get
ci/dep-integritymissing entirely, not
withheld -- so a single green check can hide a BLOCKED state. Assert a check
count, never "1 green".gh run reruncannot fix this, and earlier guidance saying so was wrong.
prepare-coordinated-release.ymlcallsgh pr createwith
GH_TOKEN: ${{ github.token }}(line 344), and GitHub deliberately suppresses
the recursivepull_requestevents that bothci.ymlanddep-integrity.yml
are triggered by. There is therefore no run to rerun --gh run rerun
reruns an existing run by ID and has nothing to act on. An operator following
that advice would see no checks appear and could merge the release PR with
neither gate having run.Both workflows declare
workflow_dispatch, so the working remedy is to
dispatch each one explicitly against the release branch.ci.ymlhas a
requiredacceptance_modeinput (achoiceofclassify-fixturesor
validate-bundles);dep-integrity.ymltakes no inputs:gh workflow run ci.yml --ref release/v<version> -f acceptance_mode=classify-fixtures gh workflow run dep-integrity.yml --ref release/v<version>Use
classify-fixtures, notvalidate-bundles-- the names mislead.
Bundle validation runs unconditionally (ci.yml:306-311), while
Classify shared synthetic outcomeis gated on
inputs.acceptance_mode == 'classify-fixtures'(ci.yml:313). So
classify-fixturesruns both validations and is a strict superset;
passingvalidate-bundleswould silently skip a check that every ordinary
pull_requestrun performs -- which defeats the point of dispatching a
replacement for the missing PR gate.First bring the release branch up to date with
main, or the dispatched
dep-integrityrun answers the wrong question.gh workflow run --ref
resolves the branch tree, whereasdep-integrity.ymlis deliberately
pull_request-triggered because "the gate's question is does the PR's MERGED
result resolve" -- its own comment (dep-integrity.yml:13-17) records that a
bare branch-tree run "red-flags a PR whose conflict is already fixed on main,"
observed live pre-spec-135. Ifmainadvances afterrelease/v<version>is
cut, a dispatched run can therefore mislead in both directions: flagging a
conflict already fixed onmain, or missing one an intervening merge
introduced.So before dispatching, either merge current
maininto the release branch (and
re-dispatch after), or confirmmainhas not moved since the branch was cut:git fetch origin git rev-list --count origin/release/v<version>..origin/main # 0 => branch is currentA non-zero count means the dispatched runs are validating a tree that is not
the prospective merge result. This is the strongest argument for the durable
fix below -- creating the release PR under a credential that lets the real
pull_requestworkflows fire, so the merge ref is validated by construction
instead of being approximated by hand.Then confirm both appear and pass before merging. (The durable alternative is
to create the release PR with a non-github.tokencredential so the
pull_requestevents fire normally -- an owner decision about release
mechanics, not something this note settles.) -
The artifact inspector runs at dispatch, not post-tag, so a block costs a
dispatch rather than a version number. It needstwine; pre-flight in a
runner-like venv first. This release ships new Codex and redaction source, and
shipped source that spells a credential shape has blocked this inspector
before -- pre-flight it rather than discovering it after the tag exists. -
When dispatching
release-candidateto publish,candidate_refmust be the
literal stringrefs/tags/v1.0.0and the dispatch ref must be that same tag.
The publish job assertsgithub.ref == candidate_ref; a barev1.0.0passes
build-validate and then fails publication after the tag exists. -
release.ymlpublishes to PyPI and npm but does not cut the GitHub
Release; that is a separategh release create v1.0.0step. -
Verify all three packages independently --
seshat-bion PyPI,
@kemetra/seshat-bion npm, and the unscopedseshat-bialias. The publishes
are ordered but not atomic, so a green PyPI job is not evidence that npm
succeeded. -
v*tags are immutable under the repository ruleset: a tag that fails the
inspector can never be moved, and that version is permanently unpublishable
(this is why v0.7.0 does not exist on PyPI).