Every item below was found by running the project-state migration on a live
repository rather than by reading reckon's own code for defects. That matters for
priority: these are the things that bite an operator mid-session, measured on a project
carrying 22 plans, 11 sprints and 149 flat resources.
The migration itself succeeded and is not in question — 13 resources, source
b9fc8216ed3b, composed parity 46b93fe68018, byte-identical
snapshot, marker published last. What it exposed is the surrounding surface.
§2 — A sprint cannot span repositories, and the fix is narrow
The asymmetry is exact. Dependency refs are already
project-qualified. _schema.py:172-190 defines
_PLAN_REF_RE with an optional project: segment and states the
contract plainly: a bare slug is local, a qualified ref is external into another mounted
project, both travel in the same comma-separated meta lists, resolution of external refs
is the server's job, and files stay portable because nothing in the HTML depends on
another checkout being present. read_plan already resolves
depends_on into a deps list carrying scope,
found, status and impl per ref.
Sprint items got none of it. A migrated sprint item is
{slug, why_now, done_when} with a bare slug and no project segment. So a
sprint — the unit that means "this is the work we are doing now" — is confined to one
repository, while the dependency graph it orders is not. That is backwards: a topic
routinely splits across repositories, and the sprint is precisely the object that should
hold it together.
What already works, so it is not rebuilt./reckon-ship <project>:S1 selects which project's sprint to execute,
and the external-prerequisite stop already refuses to implement another project's plan in
this checkout — it surfaces /reckon-ship <project>:<slug>
instead. The execution side understands cross-repository work. Only sprint
membership does not.
The requirement is bidirectional, and that is the part a qualified ref alone
does not give. Executing another project's sprint already works. What is missing
is two things together: attaching plans owned by different repositories to one
sprint, and reaching that sprint from any participating repository. A sprint is
the object that says "this is the work we are doing now", so a person working in either
repository must arrive at the same sprint and see all of its members — not a local subset
with the rest invisible. A one-way outward ref from a single owning repository fails the
second half: the other repository has no path to the sprint at all.
Deliverable, and it is two changes rather than one. First, a sprint
item's slug accepts the same optional project: qualifier as every other ref,
resolved through the existing external-ref machinery rather than a second implementation.
Second, plan linking gains the return path: a plan states the sprint it
belongs to in a form that resolves across repositories, so the sprint is discoverable from
the member rather than only the member from the sprint. Today plan-sprint
holds a bare id (S9) whose meaning is local by construction, which is exactly
why a shared sprint cannot be reached from a second checkout.
Done when a sprint holding items from two mounted projects reports
per-item scope, found, status and impl the way deps already does;
roadmap orders across both; the sprint resolves from either
repository with the same membership; a qualified item whose project equals the owner reads
as local; and an unmounted or dangling qualified item is an audit finding rather than a
silent omission.
Where the two instances land, and why this is issued once. There are
two reckon deployments — the ITER host and WSL — but plan state is version-controlled by
the repositories themselves, not by the server, so a link or tag change is a commit like
any other and propagates by pull. The instances read; they do not own. So this is authored
once and both see it. The only per-instance state is mounts.json and the
state-dir symlink, which reckon-sync owns exclusively and which decide
which repositories a given instance can resolve — meaning a cross-repository
sprint renders fully only where both members are mounted. That is a mount question, not a
content question, and it is why an unresolved member must report explicitly rather than
vanish.
Three design questions this raises, recorded as decisions rather than
assumed: whether a cross-repository sprint resource lives in one repository or is composed
from a sprint resource in each; what a sprint's completion means when one member
repository is not mounted; and what shape the return link takes so that a plan naming a
shared sprint stays portable in a checkout where the other member is absent.
§3 — The layout migration destroys grouping with nowhere to put it
Measured on the live project. migrate-layout --check reports
149 moves, 113 of them out of a nested source
directory, and zero colliding targets — so it is mechanically safe. It is
semantically lossy:
Measure
Count
Standard-names documents flattened
73
Of those, whose slug already carries the topic
2
Grouping irrecoverable from the slug
71
The slugs are bare numbered sequences — 09-llm-compose,
42-polling-workers, DD-03-multiplicity-unit,
37-docs-generation-design. After flattening, 09-llm-compose.html
sits in one flat research/ beside gpu-cluster-scoping.html with
nothing marking it as standard-names work. Worse, three independent numbered
sequences — from research/standard-names/,
archive/standard-names/ and archive/research/standard-names/ —
each run their own 01..46 and interleave into meaninglessness.
The flattening is not arbitrary. Resource identity is
{project, type, id} with id the slug, so a flat typed root makes
slug-to-path a pure function. Nested directories would either make it ambiguous or force
the subdirectory into the identity. The design is right; the migration simply has no
destination for the information it discards.
The published schema has 41 properties and no tag field. The only
grouping axes are sprint, milestone, tier and
north_star — all single-valued and lifecycle-shaped. None expresses "this is
standard-names work". So directories are currently the only topical grouping in the
system, and the layout migration deletes them.
Deliverable, in order: add a multivalued tag field following the
established comma-separated meta pattern that depends_on and
informs already use; backfill it mechanically from the pre-migration paths,
which need no judgement because the source directory is the tag; then run the
layout migration. A document can carry two tags and cannot live in two directories, so
the result is strictly better than what it replaces. Done when the
layout migration runs with zero grouping loss provable by comparing tags against the
saved move pre-image.
The pre-image is already preserved at
~/.local/share/imas-codex/receipts/reckon-migration/layout-moves-preimage.txt,
so this is recoverable whenever it is taken.
The migration breaks 119 links, measured, and the checker cannot prove otherwise yet
This is the constraint that governs the whole section. Scanning every
project-absolute href in the live corpus: 526 internal links
across 175 files, of which 119 links to 48 distinct targets
point at a document the migration moves. Worked examples:
Note both link forms occur — with and without the .html suffix — so a
rewriter that handles one and not the other silently fixes 7 references and breaks 5.
And the existing link checker cannot establish a baseline.audit-doc --check-links raises
ResourceIdentity.validate_for_write failed: slug: must be a single safe path
segment on the pre-migration corpus, because a nested resource such as
research/standard-names/foo.html has no single-segment slug. So the tool that
would prove the migration safe requires the migration to have already happened. That
circularity has to be broken before anything moves, not after.
Sequenced deliverable, and the migration itself is a stage in this plan rather
than something run beforehand:
Make the link checker work on a nested corpus in check-only mode, so
a genuine before-state exists. A read-only audit has no business asserting a write-time
identity invariant.
Record the before-state — every internal link and its resolution —
as a committed artifact, not a terminal scroll.
Add tags and backfill from the saved pre-image (§3 above).
Rewrite the links as part of the move, handling both suffixed and
extensionless forms, then run the migration.
Prove the after-state equals the before-state: same set of resolved
targets, zero dangling. Done when that comparison is an assertion in the
suite rather than a judgement, and it fails if a single reference stops resolving.
Delete the one-shot scripts. Any script written purely to perform or
verify this migration is removed in the same commit that records its successful
completion, with the receipt kept as evidence. A one-shot left in the tree becomes an
undocumented tool someone runs again later against a corpus it no longer describes.
Until that chain is green the layout migration does not run. The project-state
migration is already done and is independent — the typed resolver reads flat resources for
compatibility — so nothing is blocked by waiting.
§4 — Wiring findings the migration surfaced, and one that predates it
With state split into typed resources, roadmap reports more than it
could against a single index:
Severity
Finding
WARN
plan-sprint-missing-item — a plan declares a sprint but is absent from that sprint's items
ERROR
sprint-order-inversion — a plan is scheduled before a prerequisite, twice
The membership warning is a straightforward scoped write now that sprints are
independently versioned. The two inversions share one root cause and are the more
interesting finding: both name the same prerequisite, a plan sitting active
at 96.5% with every followup resolved, no blockers and no open decisions.
It is finished and unclosed, and because it is unclosed it holds two downstream sprints in
an inverted order. A plan that is done but not marked done is not a bookkeeping nit — it
propagates into the graph as an error.
Deliverable: repair the three findings, and separately ask whether
roadmap should distinguish a prerequisite that is genuinely incomplete
from a prerequisite whose own state says it is finished. The second is a
lifecycle-hygiene signal and reporting it as a sprint-order error sends the reader to the
wrong repair.
§5 — The SPA: the tokens are good, the shell and the cascade are not
Read before recommending. The design foundation is genuinely considered and
should not be redesigned.foundation.css establishes semantic tokens
— --ink, --muted, --faint, --line —
with oklch accents, a single hairline border primitive, a restrained shadow, Geist and
Geist Mono, and a complete light and dark pair. That is a deliberate system, not
templated defaults, and it is the part worth keeping.
What needs work is structure, in this order:
File
Lines
Problem
shell.jsx
1,544
20 top-level components in one file
styles.css
2,256
498 selectors, no stated layering contract
graph.jsx
742
the most valuable view, the least legible file
styles-base.css
1,163
200 selectors; ownership versus styles.css undefined
shell.jsx holds the route parser, the top bar, the filter column, the list
column with its own sort model, the status menu, the title bar, the app root, the command
palette, the cockpit body and two modals. It is the file every change touches, which makes
it a merge magnet — and concurrent sessions are the normal operating mode here, so that is
a practical hazard rather than an aesthetic one.
Across four stylesheets there are 1,239 selectors in 5,990 lines, and
dashboard.css exists twice — once under docs/_shared/ and again
under skills/reckon-sync/assets/. Without a layering contract stating which
file owns tokens, primitives, layout and view-specific rules, the cascade is doing work
nobody can reason about.
Recommendation for the design pass. Do not spend it on colour and
type; that layer is done. Spend it on information architecture — what earns
primary space. The present layout is a conventional three-column dashboard with a hash
router, and the content most specific to this tool, the dependency graph and sprint DAG,
is compressed into a strip component while filters and list chrome take the width. The
question worth designing against is which view a person opens when they want to know what
to do next, and whether that view is the graph rather than a filtered list.
Sequencing constraint: decompose shell.jsx and state the
CSS layering contract before the visual redesign lands, or the design work
arrives as a diff to a 1,544-line file and a 2,256-line stylesheet that no one can
review.
§5b — A tag nobody can enumerate is not a vocabulary
Yes, and without it the tag field is worse than no tag field. An
author who cannot see the existing tags invents one, and within a week the corpus carries
standard-names, standard_names, sn and
standardnames as four unrelated groupings. That failure is not hypothetical
here: the same shape has already been paid for twice in a consuming project — a closed
grammar vocabulary that agents had to derive at runtime rather than restate, and an
identity-key convention that varied by label and produced four silent-zero query failures
in a single session. Both were fixed the same way: make the vocabulary discoverable from
the authority rather than remembered.
Both surfaces, because they serve different moments.
The MCP read surface is the authority.read_plan(project) already returns a discovery envelope — plans, followups,
questions, sprints, milestones, active_sprint_id, summary. The tag inventory
belongs there, computed live across plans and evidence and research docs, with a
usage count per tag. An agent doing anything programmatic gets it without asking, and a
count makes a one-off typo visible beside an established tag rather than equal to it.
The authoring skill is the reminder.reckon-create is
where a new resource's metadata is chosen, so the existing tags belong in front of the
author at that moment. A skill that says "add tags" without showing which ones exist is
the instruction that produced the four-spellings outcome above.
One design position worth stating rather than discovering later: the
tag list should be reported as observed usage with counts, not as a curated
allow-list. A closed list needs governance nobody will do, and it blocks a legitimately new
topic at exactly the wrong moment. Counts give the author the signal to reuse without
forbidding invention — and they let an audit surface singletons for review, which is the
cheap version of governance.
Done when the discovery response carries every tag in use across plans,
research and evidence with its count; a test asserts the inventory is derived from the
corpus rather than from any literal list, so a new tag appears without a code change; and
reckon-create shows the inventory at authoring time.
§6 — Make the surface designable against a real URL
The capability already exists and is one step from being useful.
reckon build docs is documented as bundling the UI and generating a portable
static site for CI and GitHub Pages: it copies JSX and CSS into docs/_ui/ and
docs/_shared/, writes an index.html with relative asset paths,
and writes a complete index.json with live-discovered inventory, sprints and
milestones so the SPA works without a running reckon server.
And CI already runs it — then throws the result away..github/workflows/reckon-pages.yml is named "Validate reckon static build",
runs uv run --frozen reckon build docs on every push and pull request to
main, and declares permissions: contents: read with no
pages: write and no deploy step. So the artifact is built, proven to build,
and discarded.
Deliverable: add a deploy job to that existing workflow, publishing to
GitHub Pages on main. That yields a public URL a browser-based design tool can fetch
directly, with no exposure of the host that currently serves the private instance — the
static build reads its own index.json and needs no server, so nothing internal
is reachable through it. Reckon's own docs/ becomes the example, which is the
honest form of a demo: the tool's own planning state, not a fixture.
The guard is not "is it private", and that is measurable
Publication is per-repository opt-in, never a central filter. The lead's
rule is that private information stays private and a fork of an upstream repository counts
as personal. A predicate of "publish unless private" satisfies the letter and breaks the
rule, measured on this machine:
Repository
isPrivate
isFork
GitHub visibility
Publish?
Simon-McIntosh/reckon
false
false
PUBLIC
yes
Simon-McIntosh/imas-codex
false
true
PUBLIC
no — personal fork
A guard keyed on isPrivate publishes that fork and reports itself correct.
That is a disclosure failure produced by a plausible predicate — the same shape as every
silent-null finding that motivated this plan, with a worse blast radius, because the output
is a public URL rather than a wrong count. isFork narrows it but is still a
proxy: the durable rule is that a repository publishes only by declaring so in its own
tree, so a repository that never opted in cannot be published by any central
misconfiguration.
That settles the topology question, and the trigger with it. Each
repository's own CI runs reckon build docs and deploys its own Pages site,
triggered by changes to its own docs/. Reckon publishes a thin composed index
over the set that has published. Nothing needs cross-repository credentials, no central
list can leak a non-participant, and the common-case trigger is local rather than a
cross-repo dispatch — only a change in the set of published projects needs
reckon's index rebuilt, which is rare enough for a manual or dispatched run.
Three costs to state honestly rather than discover. The SPA must read
several index.json files cross-origin, which needs the Pages CORS behaviour
verified rather than assumed — if it does not permit it, the composed index degrades to
links out instead of a unified view, and that is the fallback rather than a blocker.
Cross-repository plan links become cross-origin URLs, which is arguably an improvement
since they become real addresses. And a federated view can show a stale member if one
repository has not rebuilt, so each published site must carry its build time and the
composed view must show it rather than implying simultaneity.
Two things to settle before publishing rather than after. Reckon's own
plans would become public, so their content is a disclosure decision, not a technical one.
And a static build must be a genuine snapshot of the same SPA rather than a divergent
second path, or the surface being designed is not the surface being shipped —
done when the deployed build renders from the same JSX and CSS sources as
the served instance, asserted by a test rather than by inspection.
§ Decisions
Where does a sprint spanning repositories live, given it must be reachable from every member?
Locked by the lead 2026-08-24. The requirement is navigation from any member, and a single owning repository makes one participant privileged: if that repository is not mounted the sprint does not exist for anyone else. Composed-per-repo means each repository carries the members it owns and the view unions them, so the sprint is reachable symmetrically from every side. Cost accepted: composition needs a stable sprint identity across repositories, and disagreement on that identity becomes an audit finding rather than a silent divergence.
What does a cross-repository sprint report when a member repository is not mounted?
Locked by the lead 2026-08-24. Absence must never read as completion or as permission. A hard audit error blocking closure was rejected as too strict: an unmounted repository is a local condition, not a defect in the sprint, and it would make a sprint uncloseable on a machine that legitimately lacks the other checkout. An explicit unresolved row that is never counted complete and never silently omitted gets the safety without the false blocker.
Must the tag field and its backfill precede the layout migration?
Locked by the lead 2026-08-24. 71 documents lose their programme irrecoverably, and the backfill needs no judgement because the source directory IS the tag. Doing it afterwards means reconstructing from git history rather than from a live pre-image. Nothing is blocked by waiting: the project-state migration is already complete and independent, and the typed resolver reads flat resources for compatibility.
Do reckon's own plans go public as the GitHub Pages example?
Locked by the lead 2026-08-24 WITH A GUARD: private information stays private, and a fork of an upstream repository counts as personal regardless of its GitHub visibility. Publication is therefore per-repository opt-in, never a central filter. The guard is not isPrivate - measured, Simon-McIntosh/imas-codex reports isPrivate false, isFork true, visibility PUBLIC, so a predicate of 'publish unless private' would publish a personal fork while reporting itself correct. That is a disclosure failure produced by a plausible predicate, the same shape as every silent-null finding that motivated this plan.
Does the shell decomposition precede the visual redesign or land with it?
Locked by the lead 2026-08-24. shell.jsx holds 20 components in 1,544 lines and styles.css carries 498 selectors with no stated ownership boundary, so a visual redesign landing as a diff to those is unreviewable. Concurrent sessions are the normal operating mode here, which already makes that file a merge magnet. Decomposition is also the cheaper half and independently valuable if the redesign never happens.
Is the tag inventory observed usage with counts, or a curated allow-list?
An author who cannot enumerate existing tags invents one, and the corpus fragments into standard-names, standard_names, sn and standardnames as four unrelated groupings. A closed list needs governance nobody will do and blocks a legitimately new topic at the worst moment; counts give the reuse signal without forbidding invention, and let an audit surface singletons as the cheap form of governance.
Locked by the lead 2026-08-24. A curated allow-list needs governance nobody will perform and blocks a legitimately new topic at the exact moment someone is authoring. Counts give the reuse signal without forbidding invention, and an audit surfacing singletons is governance at near-zero cost. Same shape as the closed-vocabulary problem in a consuming project, where the lesson was to derive from the authority rather than curate by hand.
What shape does a plan's sprint reference take, so a shared sprint resolves from any member repository?
plan-sprint holds a bare id whose meaning is local by construction, so qualifying sprint ITEMS alone leaves a second checkout with no path to the sprint. The return path is the half the first draft missed, and it must stay portable in a checkout where the other member repository is not mounted.
Locked by the lead 2026-08-24, and coherent with composed-per-repo which nearly implies it. Each direction fails alone: items-only leaves a second checkout with no path to the sprint, and sprint-declares-members-only means a plan cannot state its own sprint portably when a plan should be readable in isolation. Both, each INDEPENDENTLY resolvable, means either half working is enough to render - which matters precisely when one repository is not mounted. Accepted risk: two mechanisms to keep consistent, so consistency between them is itself an audit surface.
How are the one-shot migration scripts disposed of?
A script written to perform or verify a single migration becomes an undocumented tool someone runs again later against a corpus it no longer describes. The receipt is the durable artifact; the script is not.
Locked by the lead 2026-08-24. A one-shot kept for reuse becomes an undocumented tool run later against a corpus it no longer describes, and this particular script rewrites 119 links. The receipt is the durable artifact; the script is a means. Retaining it under a scripts directory would also violate the consuming repository's own rule against migration-named files persisting in the tree.
How do plans from several repositories reach one published site?
reckon's CI only checks out reckon. mounts.json is per-instance local state, so CI cannot see which repositories the ITER host resolves. Three shapes were considered. Vendoring or symlinking linked repositories into reckon needs a committed repository list plus a commit per plan edit, duplicating content and making the publish decision a central filter. Central sourcing - reckon CI checking out declared repositories with a repository_dispatch trigger from each - works but makes reckon's CI the thing that decides what is published, so one misconfiguration publishes a personal fork, and it needs credentials to reach anything non-public which is precisely the capability that should not exist. Per-repository publication with a federated index means each repository's own CI runs reckon build and deploys its own Pages site, and reckon publishes a thin composed index over the published ones.
Locked by the lead 2026-08-24, after verifying the property it depends on rather than assuming it: an unregistered project with no mounts entry, no state directory and no running server built cleanly at 496K with relative asset paths and a .nojekyll marker, so reckon build is genuinely self-contained. The decisive argument is that the guard becomes structural rather than configured - a repository that never opts in has no build job, so no central list can misconfigure a personal fork into publication - and no cross-repository credentials are needed at all. Central sourcing was rejected despite being operationally simpler (one CI instead of N) precisely because it relocates the disclosure risk into a list. Accepted cost: each consumer pins reckon, which is why pypi-release is sequenced first in S12.
§ Followups
Qualify sprint items, give grouping a destination, then design the surface
Order matters and each step removes risk from the next. The three wiring findings first, since two are ERROR-level and one traces to a plan that is finished and unclosed. Then the tag field and its mechanical backfill from the saved move pre-image, so the layout migration can run with provable zero grouping loss rather than discarding 71 documents' programme. Then sprint items accepting the same project: qualifier every other ref already takes, resolved through the existing external-ref machinery. Then the GitHub Pages deploy job, which turns an already-validated static build into a URL a browser design tool can reach. The shell.jsx decomposition and the CSS layering contract precede the visual redesign, or the design lands as a diff to a 1,544-line file nobody can review.
/reckon-ship sprint-scope-and-surface
The link risk is measured, not estimated, and it is the reason the migration is a staged deliverable rather than a command to run. Across the live corpus: 526 project-absolute internal links in 175 files, of which 119 links to 48 distinct targets resolve to a document the migration moves. Both link forms are in use — /imas-codex/sn-quality-parity.html appears 7 times and the extensionless /imas-codex/sn-quality-parity 5 times — so a rewriter handling one form silently repairs 7 references and breaks 5.
And the checker cannot currently establish the baseline.audit-doc --check-links raises slug: must be a single safe path segment on the pre-migration tree, because a nested resource has no single-segment slug. A read-only audit is asserting a write-time identity invariant, so the one tool that could prove the migration safe requires the migration to have happened first. Breaking that circularity is the first deliverable of the section, ahead of any file move.
Scope corrected by the lead before any work started. The first draft of this section read the requirement as project-qualified sprint selection — running another repository's sprint — which already works and was never the gap. The actual requirement is bidirectional: attach plans owned by different repositories to one sprint, AND reach that sprint from any participating repository. A one-way outward ref from a single owning repository satisfies the first half and fails the second, leaving the other checkout with no path to the sprint at all.
That makes it two changes rather than one, and the second is the one the draft missed: plan linking needs the return path. plan-sprint holds a bare id whose meaning is local by construction, so a shared sprint is unreachable from a second checkout no matter how its items are qualified.
Per-repository publication is verified workable, by building an unregistered project rather than by reasoning about it. A throwaway project with one plan, no mounts.json entry, no state directory and no running server built cleanly: copied _shared/, wrote index.html with relative asset paths, created .nojekyll, and wrote state/<project>/index.json reporting 1 plan. So reckon build is genuinely self-contained and needs neither registration nor the server — which is the property the federated shape depends on.
Four measurements from that build. Payload is 496K, trivial for Pages. index.json already carries an updated timestamp and the project key, so the per-member staleness display the federated view needs is supported rather than new work — though it carries no git revision, so revision provenance would be an addition. Asset paths are relative, confirming Pages compatibility by observation. And the git HEAD lookup failed harmlessly outside a repository and the build continued, so it degrades rather than aborting; in CI that lookup succeeds.
One dependency worth naming:index.html links Google Fonts at fonts.googleapis.com and fonts.gstatic.com, so a published site is not fully self-contained and its typography degrades on a locked-down network. Acceptable for public Pages, and a self-hosted font would remove it.
And one cost of reckon not being on PyPI:reckon-plans resolves to no distribution, so every consuming repository's CI pins a git ref. Unpinned, a reckon change can break a consumer's build with no signal; pinned, there are N repositories to bump. That is the real recurring maintenance cost of the federated shape and it should be chosen with open eyes rather than discovered at the first breakage.
The link risk is measured, not estimated, and it is the reason the migration is a staged deliverable rather than a command to run. Across the live corpus: 526 project-absolute internal links in 175 files, of which 119 links to 48 distinct targets resolve to a document the migration moves. Both link forms are in use —
/imas-codex/sn-quality-parity.htmlappears 7 times and the extensionless/imas-codex/sn-quality-parity5 times — so a rewriter handling one form silently repairs 7 references and breaks 5.And the checker cannot currently establish the baseline.
audit-doc --check-linksraisesslug: must be a single safe path segmenton the pre-migration tree, because a nested resource has no single-segment slug. A read-only audit is asserting a write-time identity invariant, so the one tool that could prove the migration safe requires the migration to have happened first. Breaking that circularity is the first deliverable of the section, ahead of any file move.Scope corrected by the lead before any work started. The first draft of this section read the requirement as project-qualified sprint selection — running another repository's sprint — which already works and was never the gap. The actual requirement is bidirectional: attach plans owned by different repositories to one sprint, AND reach that sprint from any participating repository. A one-way outward ref from a single owning repository satisfies the first half and fails the second, leaving the other checkout with no path to the sprint at all.
That makes it two changes rather than one, and the second is the one the draft missed: plan linking needs the return path.
plan-sprintholds a bare id whose meaning is local by construction, so a shared sprint is unreachable from a second checkout no matter how its items are qualified.Per-repository publication is verified workable, by building an unregistered project rather than by reasoning about it. A throwaway project with one plan, no
mounts.jsonentry, no state directory and no running server built cleanly: copied_shared/, wroteindex.htmlwith relative asset paths, created.nojekyll, and wrotestate/<project>/index.jsonreporting 1 plan. Soreckon buildis genuinely self-contained and needs neither registration nor the server — which is the property the federated shape depends on.Four measurements from that build. Payload is 496K, trivial for Pages.
index.jsonalready carries anupdatedtimestamp and the project key, so the per-member staleness display the federated view needs is supported rather than new work — though it carries no git revision, so revision provenance would be an addition. Asset paths are relative, confirming Pages compatibility by observation. And the git HEAD lookup failed harmlessly outside a repository and the build continued, so it degrades rather than aborting; in CI that lookup succeeds.One dependency worth naming:
index.htmllinks Google Fonts atfonts.googleapis.comandfonts.gstatic.com, so a published site is not fully self-contained and its typography degrades on a locked-down network. Acceptable for public Pages, and a self-hosted font would remove it.And one cost of reckon not being on PyPI:
reckon-plansresolves to no distribution, so every consuming repository's CI pins a git ref. Unpinned, a reckon change can break a consumer's build with no signal; pinned, there are N repositories to bump. That is the real recurring maintenance cost of the federated shape and it should be chosen with open eyes rather than discovered at the first breakage.