Every choice made in phase 1 of the Cortex birth epic (PyAutoMind#379) that the epic ledger's "The decision" (2026-09-01) did not already fix. Phase 2's conductor reads these; nobody re-derives them. Each entry: the decision, then one line of why. The human's fixed inputs — the organ, the state model, rulings of record (Option A), gates as GitHub refs, the remotes, the epic split, the rolling board — are in the epic ledger and are not repeated here.
- Ruling id
R-YYYYMMDD-nn, a two-digit per-day sequence, global. Why: date-first sorts the ledger chronologically on disk and the per-day counter is small enough to assign by listing one directory; global rather than per-project so one id space is enough to cite from any ledger. - One ruling file per phase; a multi-phase ruling is N files joined by
Batch:. Why:checkthen has one phase per file to validate against, and the REWIND case (one verdict over five phases) stays expressible viarule --also. - Chains, not trees: at most one successor per ruling; supersede the head. Why: "which ruling stands" must have one answer; a tree would need a tiebreak.
acceptedis not terminal — a later ruling may supersede the acceptance (rule --supersedes), moving the phase torerunordropped. Why: the 2026-08-31 REWIND superseded accepted gates; a terminalacceptedwould have needed a new phase number for a verdict about the same runs.submitted/runningself-loops:--run <id>appends a wave, a chained job or a checkpoint resubmit without changing state. Why: real phases have several waves (342091_[0-8,10]then342102); one state change per wave would be noise.move pulled --partialon a running phase, whichcheckexpects closed by aleave-to-finishruling. Why: partial arrays are routine; the board needs the nine finished tasks now and the ruling records that the tenth is still owed.- Legacy-born phases:
new --legacy-runwrites a phase whose every run islegacy | legacy_wrong, movingready → pulleddirectly; the witness is still mandatory. Why: phase 4 migrates pre-Cortex runs, which were never submitted through the Cortex but must still be ruled on. voidis a run state: cancelled, or never produced a step. Why: a job that was killed at submit time is neitherfailed(it never ran) nor absent (the id exists insacct).- A gate is cleared when: a PR has
merged_at != null; an issue isclosedwithstate_reason ∈ {completed, null}. Closed-unmerged PRs andnot_planned/duplicateissues are dead gates, reported. Unreadable refs fail closed. Why: "closed" alone would clear a gate on a rejected PR. gates --gradereports;--writeflips. Why: the hand tool must be safe to run from any session; phase 2 schedules the write.- Reopen demotion
ready → gatedis--write-only, refused whenGate-override:is present, and forsubmitted+ phases reported but never enforced. Why: a run already on the queue is not un-submitted by a reopened issue; the human overrode on purpose. projects.yamlis code, not ledger, and is written in a restricted YAML subset parsed by the stdlib. Why:sync_cliandlocal_pathare paths a conductor executes under (worse thanrepos.yaml, which is already code in the Mind); the subset keepscortex.pystdlib-only while a PyYAML parity test keeps the file real YAML.- No
owner/Repo#Ngate form. Why:GATE_REF_RE's lookbehind rejects it; another owner is spelled as a URL, one grammar shared with the Mind. - Run lines in strict SLURM notation with structured facts on indented
continuation lines (
pulled_to:,after:,resumes:,where:,ruled:), never in the note. Why: a fact in prose cannot be checked;--accepted for—because phones cannot type the em dash. - Job ids are unique per phase, not globally. Why: one array fed two
phases in the real ledgers (
342120_[0-4]/_[5-9]in the fixture). - No
skills/and noinstall.shchange in phase 1. Why: the Cortex exposes no commands yet;install.sh'sORGAN_REPOSis a hardcoded list only Brain'sAGENTS.mduses. - No Mind PR for the session-hook fan-out. Why:
session_hook_propagate.ymlreads everyrepos.yamlrow and the Cortex row already exists (phase 0). - The fixture is the witness:
tests/fixtures/skeleton/holds one project, one phase per state, five rulings (one chain of two), one batch record and one review;tests/fixtures/empty/passescheck. Why: a schema nobody can instantiate is prose. ledger_merge.ymlrunscortex.py checkon the trial-merge tree, aftergit merge --no-ffand beforegit push. Why: that is the only place two branches that both assignedR-…-03are seen together.
Choices made while writing the files, beyond the plan:
- A header key with no value may be omitted or left empty (
Gates:,Runs:,Ruling:). Why:newwrites the empty key so a human sees the slot; a migrated file may not carry it; both parse the same. State ∈ accepted | rerun | dropped⇒Ruling:present. Why: those states are reachable only throughrule, so a file in one of them with no ruling is a contradictioncheckshould name.- **The ruling id also lives in the title line (
# <id>or `# —`), and `check` requires it to equal the filename.** Why: the plan says "id == filename" and the file must state its id somewhere a human reads. - A run line's
<partition>is a bare SLURM partition name (^[a-z][a-z0-9_-]*$), not an enumeration. Why: the project row'spartition: gpu | ral | bothis a capability; the run records the actual queue. - An
after:/resumes:target is the verbatim identifier of another run line in the same phase (342091_9,342091_[0-8,10]), not a bare stem. Why: a checkpoint resubmit resumes one task, not the array. - A
ruled:continuation must resolve to a ruling file. Why: it is a pointer and a dangling pointer is drift. - Rulings on one phase need not chain:
leave-to-finishfollowed byacceptis two chains of one;Supersedes:replaces a verdict, never sequences. Why: the plan makes--supersedesan explicit flag; forcing every later ruling to supersede would make it redundant. Batch:on a ruling must name an existingbatches/<slot>.md. Why: it is the join key; a typo would silently orphan the ruling from its slot.- Review grammar:
<HEALTH>∈HEALTHY | SUSPECT | FAILED | RUNNING;- decision: (none)+- ruled: nois an untouched member; a verb withruled: nois a leaning the human did not tick. Why: the Mind's packet has the same tick/decision split;(none)keeps the four-verb vocabulary pure while listing every member. - Batch record keys keep the Mind's names (
dispatched:= when the board opened) and dropusage-window-*,heart-ack:andexpected-effects:;- refreshed:is a repeated line. Why: one parser in the Brain can read both record kinds; the dropped fields license cloud sessions and autonomous YELLOW acks, which a science batch never spends. The member<state>is the state at the last refresh andcheckrequires it legal, not current. ledger_merge.ymlkeeps a blockingcortex.py checkon the branch tip in addition to the trial-merge check. Why:workflow_dispatchinauditmode never merges, so without it audit mode reports nothing.cortex_check.ymltriggers on its own path too. Why: a workflow edit that does not run itself is invisible until the next ledger push.
Choices made while writing cortex.py (slice B), beyond the plan:
Reset:is a phase header key, written bymove ready --reason. Why: the table demands a reason forsubmitted | running → readyand the key table gave it no home; a reason in prose cannot be found again.move pulledwritespulled_to:—--pulled-to <path>, or for alegacyrun its ownwhere:— and is refused when nodone | legacyrun would carry one. Why:checkrequires it on a pulled phase, and a verb that writes a state its own check rejects is a trap.- An unknown header key, a missing body section, a
gatedphase with an emptyGates:, or aLane:other thanlocal-devis drift. Why:Gate-cleared:is one letter fromGates-cleared:and would otherwise pass silently while the invariant it carries fails. - The run-line partition on
move --runandnew --legacy-runis the project'spartition:row when that isgpu | ral;bothrequires--partition. Why: the row is a capability, the run records the actual queue (decision 23), and the verb should not guess between two. rule --alsoon anacceptedphase supersedes that phase's ownRuling:; the primary phase takes--supersedesexplicitly. Why: the REWIND is N accepted phases with N different heads, and spelling N ids by hand is the error-prone form of the same instruction.gates --grade --writeremovesGates-cleared:when it demotesready → gated;--gradeexits 1 when any ref is unreadable. Why: a stale cleared-date on a gated phase misreads as cleared; an unreadable ref fails closed and a scheduled run must notice.
Choices made in phase 2 — the conductor, the board and the workflows (PyAutoMind#380):
-
The dashboard normaliser strips the generated-comment line AND the visible
Last updated <date>banner, in bothdashboard.mdanddashboard.html, before comparing. Why: the Mind strips only the comment, so its banner changes date every night and--checkreports drift on an unchanged registry — 348 self-heal commits in 30 days for a page nobody had touched. A no-op night here commits nothing. -
collectscores each leg PASS / FAIL / UNOBSERVABLE, and a member's<HEALTH>is FAILED if any leg failed, SUSPECT if any leg is unobservable, HEALTHY otherwise; a record'sdelivered:counts the HEALTHY members. Why: two of the four delivery legs cannot be seen from the laptop today — the checkpoint is excluded from both projects' pulls, andsubhalo_validationwrites no structured version stamp — and scoring an unobservable leg as PASS would manufacture evidence while scoring it FAIL would cry wolf. SUSPECT says exactly what is true: nobody looked. -
(amended by 51) The pull manifest is
<mirror or local_path>/.cortex/pull.json:{"pulled_at": <ISO>, "runs": {"<jobid>": {"checkpoint_bytes": <int>, "checkpoint_mtime": <ISO>}}}(decision 51 addsschemaandcheckpoints), written by each project's own sync CLI (phase 3), read bycollectwhen present. Why: "when was this pulled, and did the checkpoint grow?" is knowable only at the moment of the pull; the puller is the only process that can record it. Until the CLIs write it, the checkpoint leg stays UNOBSERVABLE, taggedRAL only. -
collect --pullis opt-in and shells out to the project's ownsync_cli pull; nothing in the Cortex or the conductor ever submits a job. Why: the laptop lane is human-driven by ruling (AGENTS.md); the conductor may refresh what is already on disk when asked, but a submission is a spend of the machine and of the human's attention, and it stays a typed command. -
The Brain conductor spells the writing flag
--apply;scripts/cortex.pyspells it--write. Why:--applyis the Brain's house spelling across every conductor and the phase-2 prompt asked for it;--writeis already the Cortex script's, documented and tested.cortex gates --grade --applyis a thin wrapper — one edit, two doors — and neither vocabulary is bent to the other. -
gates_grade.ymlis the only scheduled job that mutates the ledger, and it may only movegated → ready(orready → gatedon a reopened gate). Why: "is issue #380 closed?" is a fact GitHub answers, not a judgement, and a phase arriving inreadycosts nothing until a human admits it into a slot. Everything else on a timer reads or re-renders. It commits what flipped before it fails on an unreadable ref, so a red run never means the flips were lost. -
Every workflow that can push to
mainsharesconcurrency: group: cortex-main-writers, cancel-in-progress: false. Why: the race is not between two merges, it is between two writers of the tip —ledger_merge.yml's merge,dashboard_refresh.yml's self-heal andgates_grade.yml's flips would otherwise burn their retry loops against each other. Queue, never cancel: a cancelled run can leave a branch merged and undeleted.pages_dashboard.ymlkeeps GitHub's owngroup: pages. -
AGENTS.mdandTEMPLATE.mdunder a ledger dir are CODE (EXCLUDED_NAMES), sorulings/AGENTS.md,batches/AGENTS.md,batches/packets/{AGENTS.md,TEMPLATE.md}andbatches/reviews/AGENTS.mdnever auto-merge. Why: they are ledger by location and instructional by content — they say what an entry may be and what every future entry is stamped from. Auto-merging a rewrite ofrulings/AGENTS.mdwould let a branch edit the rule that governs its own merge. -
dashboard.mdanddashboard.htmlare LEDGER (LEDGER_FILES). Why: they are generated from the ledger and hand-edited by nobody; a branch that moves a phase re-renders them in the same push, anddashboard_refresh.yml's self-heal commit must land without a human. If they were code, every ordinary ledger branch would stop for review on two files nobody wrote. -
PyAutoBrain's tests for the cortex conductor depend on a real PyAutoCortex checkout beside the Brain (a third checkout in
tests.yml; the testspytest.skipwhen it is absent). Why: the renderer's contract is with this repo's fixtures (tests/fixtures/skeleton), and a copy of them inside the Brain would drift from the schema they are the witness for. -
The page's GitHub home is read from the repo's own
README.md/AGENTS.mdlinks, falling back to the git remote — never from a hard-coded org. Why: the render must be byte-identical on the laptop and insidedashboard_refresh.yml, or--checkwould report permanent drift and the self-heal would commit a page every night; a file that travels with the repo is readable in both places, whereas a git remote is not guaranteed in every CI container. A fork's Cortex renders its own owner. -
Four
collectscoring rules fixed while implementing it (2026-09-01). A<hash>.zipbeside a run directory outranks the directory — seven subhalo dirs were stale partial extractions, sosearch.summaryis read throughzipfilewhen the zip exists. An.errthat is non-empty, not all warnings and carries no fatal marker is UNOBSERVABLE (named first unexplained line), never PASS or FAIL — the machine cannot score it. "Newer than submitted" means newer than the phase's first submission, so a resubmit does not stale the real witness. Witness hits are ranked (run stem orWhere to lookleaf in the path first) becausewitness_fileis project-wide and phases share one tree. Also:submitted → pulledis not an edge, so asubmittedmember is scored but left with a note rather than moved. -
projects.yamlgainsstatus: plannedand one optional field,note:; every other field stays required and an unknown field stays an error. Why: phase 3 seeds the map with rows the phase-1 grammar could not spell.euclid_dr1_prelimexists as a decision before it exists as a directory —plannedsays "these are the paths it will have" without lying that the project isactive, and without the row being invented twice later. And a third of the seeded rows are only intelligible with a sentence attached: whyeuclidhas no PyAutoLabs remote, that a dormant row'sral_rootis planned rather than observed, that a personal remote is a recorded fact and not a target. That sentence had nowhere to live but a comment, and a comment is not readable by the conductor.noteis optional because most rows do not need one, and the field set stays closed (PROJECT_FIELDS + PROJECT_OPTIONAL_FIELDS) so a typo is still caught; an emptynote:is drift, exactly like an empty required field. -
The pull manifest is v1 —
schema,checkpoints,runs— andcheckpointsis the table that is always filled (supersedes the shape in decision 40, which stands as the record of why the manifest exists):{"schema": 1, "pulled_at": "<ISO UTC>", "checkpoints": {"<run dir relative to the pull root>": {"bytes": <int>, "mtime": "<ISO>"}}, "runs": {"<jobid or jobid_task>": {"checkpoint_bytes": <int>, "checkpoint_mtime": "<ISO>"}}}Why the second table: decision 40 assumed the puller knows which job id produced which checkpoint, and one of the two projects cannot — the profiling CLI pulls a results tree that carries no job id at all. The run directory path is nameable on both sides, so
checkpointsis keyed by it (relative to the pull root, the same rootcollectresolves the run directory under) and is always written;runsis written only where the CLI can link a job id to a run directory (subhalo's.outnames the sample).collecttherefore looks upruns[ident]→runs[stem]→checkpoints[<run dir rel>], and a manifest with noschemakey is read as the phase-2 shape so nothing that already exists breaks. Gathered in onessh "$HPC_HOST" "find '<ral_root>/output' -name checkpoint.hdf5 -printf '%s %T@ %p\n'"over the existing ControlMaster mux, immediately before the manifest is written — one round trip, and the sizes are RAL's own, not the laptop's guess. The write is dry-run guarded (statuscallspull --dry-run, and a dry run must not manufacture evidence that a pull happened), and.cortex/is gitignored in every project: it is pull state, likeoutput/. Key shape, corrected by the first real pull: the checkpoint lives at<run dir>/files/search_internal/checkpoint.hdf5, so thecheckpointskey is the run directory — the grandparent of the file — with any trailing/filesstripped. -
Science/euclidgetsremote: noneby ruling, and the dormant rows follow three rules. The euclid ruling (2026-09-01): it tracks a DR1-derived catalogue keyed by tile/RA/DEC (catalogue/inspection/failure_mode_breakdown_consensus75.csv), it already pushes to the personal remoteJammy2211/euclid-dr1-modeling, it nests the Overleaf paper repo, and it is 34 GB with anoutput/too large todu. Any one of those refuses a PyAutoLabs remote under the no-Euclid-data rule; together they settle it. Nothing is lost, because the project's code half is already atPyAutoLabs/euclid_strong_lens_modeling_pipelineat parity — what stays outside the org is data and prose. The row is still written, because the Cortex must be able to name a project it will never host. The dormant rules: (a) git repos only —subhalo,euclid_groupand thez_*trees are storage, and storage is not a project with a lifecycle; (b) an assistant clone is not a project —aris_PJ011646is a copy of a workspace, sopj011646is the row and the clone is not; (c) a personal remote is recorded as a fact, never as a target —slope_hierarchy,pj011646,concrandic50_workspacepush toJammy2211/…and the row says so inremote:with the reason innote:, so nobody re-derives the answer to "should this move into the org?" once a quarter. -
Provenance header:
Migrated-from:joinsPHASE_KEYSandRULING_KEYS(one tuple edit each, plus the REFERENCE key tables and a test); its value is the Mind path or ledger anchor the file was transcribed from. Batch records may carry- migrated-from:(there is no key check on a record). Why: phase 4's whole job is moving records that were written somewhere else, and a migrated file that cannot name its source is a claim without a provenance. The prompt already required the line; without the key the current schema would have flagged every migrated file as unknown-key drift, so the requirement and the checker had to be reconciled — in favour of the requirement. -
Intra-Cortex sequencing is a body line, not a gate.
Gates:stays GitHub-refs-only. A phase that waits on another Cortex phase's ruling isState: plannedwith a line under## QuestionreadingReady when: <phase> accepted (ruling R-…); the human moves it toreadyonce that ruling lands. A phase whose development gate has no issue yet is likewiseplanned, withReady when: <Mind prompt> is issued — add its ref to Gates: and move to gated. Why:gates --gradepolls GitHub, and the daily grader is the one scheduled job that mutates the ledger. A gate it cannot poll would either have to fail closed for ever or teach the grader a second, non-GitHub vocabulary. A science phase waiting on a sibling phase is waiting on a human ruling, which is exactly the thing the Cortex refuses to automate, so it belongs in prose the human reads and not in a field a robot grades. -
A Cortex-spawned development follow-up gets its issue at filing, without leaving
draft/. The Mind prompt carries a body lineIssue: <url> (opened <date> as a Cortex gate ref; reuse in start_dev — never open a second), andcreate_issuereuses that ref instead of opening another; MindREFERENCE.mdgains the rule. The same mechanism is what gives the euclid 3b gate its ref. Why:Gates:is GitHub-refs-only (decision 54), so a Cortex phase gated on work the Mind has not started yet has nothing to point at. The alternatives were to promote the prompt toactive/before anyone works it — which lies about the workflow state — or to leave the phase ungated and hope. An issue is cheap, is the organism's real join key, and is the one artefact both halves can name; theIssue:line stops the second, duplicate issue thatstart_devwould otherwise open. -
Legacy-born encodings: how pre-Cortex history enters the ledger. A run that happened before the Cortex existed is a
legacy(reusable) orlegacy_wrong(quarantined) run line carryingwhere:; a phase whose runs are alllegacy_wrongis ruleddropstraight fromready, because it cannot be pulled — there is nothing reviewable to pull. A programme-wide directive such as the 2026-08-31 REWIND is one ruling on a synthetic phase (inference_programme/rewind_2026_08_31) whose runs are the ids the directive itself names, with the directive verbatim as the ruling body. A multi-lens project gets one phase per lens. Transcribed batch records are rewritten in the Cortex grammar (member slug = phase slug) and the Mind originals stay intact with a- moved-to:line rather than being reduced to stubs; dev-only members of a mixed slot are noted in the record'snotes:, never transcribed as science members. Why: the alternative — inventingdonerun lines for runs nobody reviewed — would let quarantined evidence re-enter the programme through the back door, which is the precise failure the REWIND was called to stop. Keeping the Mind originals verbatim is a deviation from the prompt's "stub": a history that only survives ingit logis a history the next reader will not find, and two records that disagree are worse than one record and a pointer. -
A rolling slot is ruled more than once:
batches/reviews/<slot>-r<N>.md, N ≥ 2 (2026-09-02, PyAutoBrain#341 — after phase 1). The first review stays<slot>.md; every later sitting is a numbered file in the same grammar, titled for its own stem or for the slot, ruling only on the members that had landed. The record carries one- review:line per file — the key repeats — andcheckresolves a numbered review tobatches/<slot>.mdand applies every review rule to it unchanged. Why: the board was rolling but the review was not. One file per slot meant the first sitting closed it, so a human who ruled on the two members that had arrived could only rule on the third by editing a ledger file that is already someone's evidence. Numbering the sittings keeps each file append-only and leaves one record resolving them all. -
The review-slot and gate apparatus is retired (2026-09-03, PyAutoCortex#9, epic
cortex-checkinphase 1). Gate grading is gone — the daily grading cron, the--grade/--writelegs ofgates, and theGates-cleared:/Gate-override:header keys with the threecheckinvariants over them. The plainGates:header, thegatedstate and the read-onlygateslisting stay; a gated phase is moved on by a human reading the listing and typingmove <phase> ready. Also retired: the batch record and packet schema (batches/becomes closed, append-only history andBatch:becomes optional-historical),rule --also, theLane:header, and the hand-rolled restricted-YAML parser, whichyaml.safe_loadreplaces with the field validation kept. Why: measurement, not taste. Gate grading saw 2 gated refs and flipped 0 in its lifetime, and decision 54 already routes sequencing through proseReady when:lines. Of the review-slot apparatus: 0 slots were opened by the conductor, 0 rulings came from a packet, 0 partial reviews were filed, andreview-minutes-actualwas never filled — all 22 rulings were reached in a live session.rule --alsowas never used;Lane:readlocal-devon 32 of 32 phases; and the restricted parser's own PyYAML-parity test proved it redundant. What the human actually does is check in on the runs, so the apparatus that modelled them as a reviewer working a scheduled shift is cost with no reader. -
Tasks, not phases:
phases/→tasks/, the number is deleted, a ten-wordSummary:is required (2026-09-07, PyAutoCortex#20). The tree istasks/<project>/<slug>.md; thePhase: Nheader is gone outright, with no deprecated shadow —checknames it as a retired key rather than letting it pass as a generic unknown one. A task's identity is its slug, already unique per project by the path. Titles are# <Project> — <title>; ruling titles# <R-id> — <verb> <project> <slug>; a ruling's head field isTask:pointing at thetasks/…path; "revival of a dropped task is a new number" becomes "a new slug";new <project> <slug>loses--phaseand gains a required--summary. Every task carriesSummary:directly afterProject:— at most ten words, the question the task answers, not its method and not its run ids — andcheckenforces presence and the cap. Why: science is a set of unordered ideas, run in whatever order results and priorities dictate; "phase" implies a sequence that does not exist, and the number carried nothing the slug did not already carry — no ordering was ever read off it, and a dropped task's revival needed a fresh number for no reason. TheSummary:is the other half of the same decision: the board lists every open task of a project on one line each, so each task needs one line that is readable at a glance, and the rendered title ("Inference_programme — phase 11: cluster extended-source inference — gradient-based fitting building on JAX knowledge") was not it. Mind epics keep their numbered phases: development work really is sequential, andEpic:on a task file is unchanged. -
One ledger per project; tasks, witnesses, rulings and gates are retired to
archive/(2026-09-12). The tree isprojects/<key>.md, one file perprojects.yamlrow that is doing anything, holding exactly## Now(rewritten),## Runs(open | running; a finished run leaves the list) and## Log(dated, newest-first,run | result | lesson | note). The header isProject:andIssue:; the title carries a ≤15-word summary.scripts/cortex.pykeepscheckandretire, gainsnew/run/running/done/log/now/issue, and losesmove,rule,gates. The conductor keepscheckin,census,dashboard, gainsissue, and losescollect,gates, the six scoring legs, the pull manifest, the work briefs and every task chip; the board keeps one chip per active project (resume) and the check-in chip.tasks/,rulings/andbatches/moved underarchive/, frozen: the merge gate classifies any change there as code. Every task, run, ruling and note was carried into its project's log on migration day, in the words already on file, each question linking back to its archived task. The organism table's Cortex role and the Brain's organ page say "keeps track of what is true". Why: the human's own account of how they do science, and the ledger's evidence for it. In twelve days the task model produced 43 tasks of which 16 were dropped by ruling; 37 rulings were written around one- or two-line human verdicts; the pre-registered witnesses were restated after the data came in (euclid_dr1_prelim's parity clause "cannot be met by any pair of these runs and needs restating"; analytic_gaussian was accepted with 4 of 11 criteria missed). The one thing the human kept coming back to was the dated## Notesjournal that grew inside a task file — what was run, what was found, what next — which is exactly the shape the ledger now has. A task is a thing to finish and retire; science is done by running, looking, thinking and organically deciding where to go next. So the Cortex is a history of how each project unfolds and a way to pick one up after time away, not a queue. Two consequences are rules rather than schema: nothing in a ledger is inferred from results (aresultorlessonis the human's words on the human's ask; the agent records only cluster facts), and the top of each project's GitHub issue is the same concise ledger the board shows (theissueblock), with the detailed run-through underneath it.