Repository navigation
Lore CLI 0.12.0
Added
lore query --across-refs— read the bundle across refs, read-only (LCLI-652; ODOC-330 /
DEC-40, paired with quest-cli QCLI-417). A document added on an open pull request is invisible
until it merges, because a checkout sees only its own bundle; this answers for the repository.
Bare, the population isorigin/devplus every open pull request intodev, discovered
throughgh; given one or more ref names, it is exactly those refs, resolved locally with no
forge and no fetch. Every hit gainsrefProvenance{ref, pullRequest, sha}— a separate key
from--workspace'sprovenance, which carries an unrelated workspace identity — and every
answer reportscoverage{complete, population, discoveredAt, refsRead[], refsUnreadable[]}
as an envelope key betweendataandprincipal, in the shape agreed byte-for-byte with
quest-cli. Conflicting copies of one id are shown side by side, never merged: an id on
origin/devalways emits dev's row, another ref's copy emits a row only when its bytes differ,
and an id absent from dev emits one row per ref carrying it. Nothing is written — other refs
are read with a destination-lessgit fetch(objects only) andgit archiveinto a temporary
directory, andlore syncrefuses the flag, since a branch's managed blocks regenerate only
from that branch's own records. An incomplete read is adrifterror at exit6naming the
unreadable refs and carrying the coverage object in the error'sinput, with nothing on
stdout;--allow-partialanswers at exit0withcomplete: falseinstead. With noghon
PATH, or anoriginthat is not GitHub, the run degrades topopulation: "dev-only"the same
way — incomplete coverage, never an uncaught failure.
Changed (breaking)
-
The pinned Bun moved from
1.3.14to1.4.2(LCLI-648, pair with quest-cli QCLI-411).
Marked breaking becauseengines.bunmoves with it to>=1.4.2, so the declared support floor
narrows: a consumer running the package from source on Bun 1.3.x is now outside it. Nothing
else about the surface changes.
1.3.14mis-parses a TS contextual keyword —declare,type,abstract,namespace,
module,global,interface— that starts a larger expression, desynchronising the parser's
scope tracking and panicking the runtime:oven-sh/bun#31239's repro exits 133 with no output
on1.3.14and exits 0 on1.4.2, andtest/bun-declare-binding.test.tspins that on the
pinned runtime. Two upstream behaviour changes ride along.Bun.Globnow descends
into dot-directories by default —.github/workflows/*returns 0 matches on1.3.14and 4 on
1.4.2(dot: truereturned 4 on both) — which changes what a dottedlore replace --in
pattern matches, previously nothing. AndBun.TOML.parserejects an imprecise bare integer
literal that1.3.14silently rounded; both runtimes still surface a stablevalidationerror
naming the config file, andtest/config.test.tscovers each shape. -
lorenow refuses to drive aquestthat is not its exact pair version (LCLI-650; DEC-31
adopting the opum-doc ADRlock-lore-and-quest-to-their-exact-pair-version-at-runtime). The two
CLIs carry one version number in every release (constitution Article 3 clause 1), so a correct
install never notices: the check compares the full version string once, at the capability gate
every quest-backed command passes before its first quest spawn. A mismatch fails — exit6,
validation, with a message naming both installed versions and the side to upgrade — rather than
warning. Marked breaking because a mixed pair that used to work (lore X against quest Y) now
refuses until the older side is upgraded; that is the intended cost, and the message names it. -
lore initdetects a tracker, offers, and instructs — it never installs, initializes, or migrates
one for you (LCLI-656; DEC-57, adopting ADR-0024). Every path that used to act on the operator's
behalf now stops with the exact commands instead. Aquestselection with noquestonPATH
stops with the install pinned to lore's own exact version (npm install -g @opum-ai/quest@<lore's version>) plusquest init(dropped when the repository already has a
Quest workspace) and the rerun; abacklogselection likewise namesbacklog.mdand its floor.
A repository holding a Backlog project is offered the migration commands rather than migrated
from the wizard, and no TTY means the same stop rather than an assumed "no". Marked breaking
because three previously-advisory cases are now stops:--tracker questor--tracker backlog
with the CLI missing or the project uninitialized used to warn and persist a selection (exit0)
and now stop with the instructions (exit3/6), writing nothing at all — the stop precedes the
scaffold, so neither the selection nor the bundle lands until the backend can serve it.--install-trackerand--no-install-trackerare accepted for one release as no-ops that
print a deprecation note — they install nothing, on any path, and together remain a usage error.
The wizard's post-migrationbacklog/removal offer is gone with the wizard's migration arm
(the flag path's--remove-backlog/--no-remove-backlogis unchanged),--no-trackerremains the
documented opt-out, and a bare non-interactivelore initstill pins the default backend and
probes nothing (LORE-260). The remedy names lore's own exact quest version, neverlatest, so the
onboarding message and the runtime pair-lock refusal are one sentence.
Changed
-
A profile anchor on a heading nested inside a blockquote or a list item resolves, instead of
crashinglore agent context(LCLI-647; DEC-22 option A). The renderer read top-level headings
only while the validator's walk saw every node, so such a profile validated and then exited1
with zero bytes of stdout; both now read one shared heading enumeration. A nested section's region
stops at its container and its breadcrumb names its nested ancestors; an anchor that matches no
heading is avalidationrefusal (exit6) with a remedy, never an uncaught crash. Where a
duplicate heading takes a slug (a quoted# Overviewbeside a top-level## Overview), an anchor
on that slug now resolves to the heading the slugger assigns it — the meaning the validator and
GitHub's own anchors already used. That re-points one case a profile could have relied on: an
anchor of#overviewwhere a quoted# Overviewtook the slug resolves to the QUOTED heading,
not the top-level one the pre-fix renderer picked; anchor#overview-1for the top-level heading.
Classified### Changedrather than breaking: the pack item's target changes, but nothing is
refused that worked before, and the pre-fix target was the violation of this repository's own
retrieval spec. -
A RESUMED
promote-latestrun now refuses when any package's currentlatestis newer than
--version(LCLI-638, twin of quest-cli QCLI-405). The LCLI-631 gate validated the record a
FRESH run was about to write, and a resume reuses the record the first run wrote instead — so
nothing compared the registry as it reads NOW with--version, and a run resumed after something
else movedlatestahead would move it backwards, the move the ADR refuses.planPromotionnow
makes the same numeric comparison on the resume path, before any write, naming the package and
both versions; a package already at--versionstays accepted, which is the partial state a
resume exists for. The comparison is by the release a live value leads with, so a registry
latestof5.7.0-rc.1counts as newer than5.6.7and refuses, while5.6.7-rc.1or
5.6.7+build.7is not newer than5.6.7and still resumes. On a fresh run, a package whose
currentlatestis not a plainX.Y.Znow gets a headline in the registry's own terms before
validateRecord's "recorded prior latest ...", which named a value nothing had recorded yet. -
Every release script now runs its CLI when it is invoked through a symlinked path (LCLI-637,
twin of quest-cli QCLI-404). Six scripts guardmain()—launcher-equivalence,pair-receipt,
promote-latest,version-parity,github-releaseandcheck-breaking-bump— and all six
comparedresolve(process.argv[1])withfileURLToPath(import.meta.url). That is false through
any symlinked path, becauseresolve()never follows a link while Node resolvesimport.meta.url
to the module's real one; the macOS tmpdir (/var->/private/var) trips it with no symlink of
anyone's own. The failure was silent in the worst way: the process exited 0 having done nothing,
so a release gate reported success for work it never did. All six now call one shared
realpath-basedisMain()inscripts/is-main.mjs, andtest/lcli637-is-main-symlink.test.ts
invokes each of them through a real symlink and fails against the old guard. Measured before:
node scripts/version-parity.mjs --helpexits 2 with 52 bytes of usage, the same file through a
symlinked directory exits 0 with 0 bytes. After: both exit 2 with byte-identical output.
check-breaking-bump.mjsis in the list because re-measuring the inventory repo-wide found it,
not the four sites the record named. -
provenance-post's not-published warning now leads with the cause that run's publish job
allows (LCLI-635, LCLI-628's reviewer finding F4). A package this release should have published
but the registry does not list has two explanations — a publish that stopped partway, or the
registry read API lagging a publish that succeeded (LCLI-460). The warning led with the partial
publish either way, so on a run whose publish job was green it named a cause that could not be
true and prescribed "Re-run failed jobs" for bytes that were already on the registry.release.yml
now passesneeds.publish.resulttoscripts/release-provenance.mjs --postas the new
--publish-resultflag, which is required there:successmeans every package is on the registry,
so the warning leads with the read-API lag and does not prescribe a resume, while any other value
makes a partial publish the likely cause and sends the reader to the publish job's log, the one
thing that tells the two apart. The same choice orders the job summary's verdict. A value outside
GitHub's result vocabulary is refused (exit 2) rather than read as "not success", and
--publish-resultis refused on--pre, which has no not-published branch. -
A malformed window variable now fails the release BEFORE the first publish, and fails a
publish:falserehearsal too (LCLI-634, extending LCLI-630).vars.REGISTRY_WINDOW_SECONDSand
vars.PROVENANCE_WAIT_SECONDSare refused unless they are a whole number of seconds (0 to
999999999, no leading zero), naming the variable and showing the value escaped. A new always-run
release-windowjob refuses the repository/organisation-scoped value on every dispatch; the
publishjob refuses the environment-resolved one before its first publish. The LCLI-630
post-publish check stays as defence in depth.scripts/release-provenance.mjs's--wait-seconds
now refuses30m,08,1e3andabcinstead of reading them as 30, 8, 1 and a crash. -
Release prep refuses a patch-level bump when the notes for the version being released carry a
breaking-change marker (LCLI-632, paired with quest-cli QCLI-328; ADR in opum-doc).
scripts/check-breaking-bump.mjscompares the checked section with the previous version heading
and refuses a patch bump when that section carries a### ... (breaking)heading, or lore's
legacy bold behaviour-change marker, whose refusal names the canonical### Changed (breaking)
spelling. Because lore and quest share one version (constitution Article 3 clause 1), the gate
also reads quest-cli'sCHANGELOG.mdby ref and applies the same rule, and an unreadable quest
changelog is a refusal, not a pass.release.yml's newbreaking-bumpjob gates the publish job. -
scripts/release-provenance.mjsrefuses anacknowledge_dangling_provenancedispatch input
that could forge workflow commands (LCLI-636). The value previously printed raw inside
::warning::lines, so an API dispatch with an interior CR/LF could have forged runner commands
and annotations. The input is now refused (exit 2) when it still contains%, a newline, any
other control character, or DEL after trimming, with the value shown escaped. -
lorereads quest's terminal statuses fromquest task status-flowinstead of treating only
Doneas complete (LCLI-633, paired with quest-cli QCLI-331 and its later QCLI-406). A task in
quest'sclosedStatusis now complete for Story/Task coupling andlore check, and the adapter
accepts a terminal status set within the status flow plusclosedStatus. A genuinely foreign
terminal status is still drift, and its message names a lore/quest version mismatch rather than a
too-old quest. -
lore readrenders the markdown body when stdout is a terminal (LCLI-615). It renders
headings, emphasis, lists and task lists, block quotes, fenced code, links (astext (url)) and
GFM tables, word-wrapped to the terminal's width.--plain, piped output and--jsonare
unchanged and byte-for-byte, solore read <id> | tail -n +3still recovers the body. Terminal
control sequences in a document are removed before rendering, including ones produced by
character references such as‮.NO_COLORremoves all styling and keeps the layout.
New exact-pinned build dependencies, compiled into the binary:mdast-util-gfm3.1.0 and
micromark-extension-gfm3.0.0 (MIT; the binary grows by about 66 KB). -
scripts/promote-latest.mjscuts lore's GitHub Release itself, and refuses before anything
moves if it could not (LCLI-622, paired with quest-cli QCLI-398 and QCLI-401). On--dry-run
and--promotealike, before the record or any registry write, it refuses whenCHANGELOG.md
has no non-empty## [X]section, whenghcannot read the repository, or whenvXalready
exists as a draft, a prerelease or with different notes. An existing release is never edited.
Oncelatestis verified it removes the private npmrc, then createsvXfrom that section
(non-draft, non-prerelease,--verify-tag, marked latest), or only marks an identical existing
release latest. A failed cut exits3with "do NOT roll back" and the repair command
node scripts/github-release.mjs --version X --create, and the README read-back still runs. The
post-latest checklist reports the outcome instead of printinggh release create. The first
live cut is the next promotion. -
scripts/promote-latest.mjsrefuses a--versionthat would movelatestbackwards, or is
not plainX.Y.Z(LCLI-631, paired with quest-cli QCLI-402). On a fresh--dry-runor
--promote, before any registry write, it refuses a--versionolder (numerically) than any
package's currentlatest, naming both versions, and it refuses when a currentlatestis not
a plain release. Before this, such a run wrote a record that both resume and--rollbackthen
refused, which stranded the promotion. A prerelease--versionwas already refused earlier, by
the equivalence check, and a test now holds that end to end. Backports and prereleases are not
a promote use case. A resumed run was not re-checked against a currentlatestuntil LCLI-638,
which closed that: see the bullet above. -
The root launcher stages as
X-rc.N; the platform packages still stage asX(LCLI-621,
constitution Article 3 clause 5 as amended by ODOC-302). The Release run'snpm-packages
artifact now carries eight tarballs: the six platform packages atX, the launcher atX-rc.N
and the launcher atX. Staging, fromrelease.yml'spublishjob or from
scripts/publish-release.sh, publishes seven of them underrelease-candidate: the platforms
atXand the launcher atX-rc.N, which pins the platforms at exactlyX. TheXlauncher
is carried and never staged.Nis the newlauncher_rcdispatch input, which defaults to1.
A re-stage of the sameXtakes the nextN, and works only if the new run's platform tarballs
are byte-identical to the ones already on the registry. That has been measured once: two Release
dispatches on the same commit,20a1d24b(0.6.1, runs 34783940117 and 34786808767), produced
seven byte-identical tarballs. From a different commit it is unmeasured. The finalX
launcher reacheslatestthroughscripts/promote-latest.mjs; see the next entry. -
latestgets the launcher by a fresh publish of the qualifiedX, last (LCLI-621, Part B).
scripts/promote-latest.mjsnow takes--release-run <id>. It downloads that run's
npm-packagesartifact afresh and re-runs the equivalence gate on it. It then re-reads the
pass-1 receipt against the artifact. That receipt'scommitmust be whatv<version>peels to,
itslauncherVersionmust be the artifact'sX-rc.N, and itslauncherSubstitutionmust be
MATCHnaming the basenameopum-ai-lore-<X>.tgzat the artifactXlauncher's sha256. The
staging check readsX-rc.Non the launcher, and the pair receipt'slauncherVersionis now
required and must equal the artifact's rc. A receipt withoutlauncherVersionrefuses, as
quest-cli's reader refuses it. Before anything moves, and again after the six platforms move by
dist-tag,npm packof the registry'sX-rc.Nmust be sha256-identical to the artifact's rc.
TheXlauncher must be equivalent to that served rc and still hash to the receipt's
finalTarball.sha256. Then it runsnpm publish <X> --tag latest, last. That is the one publish
in the repository without--tag release-candidate, and a test holds it to that one site. If
Xis already on npm as the artifact's bytes, it moves the tag instead. Other bytes refuse
before anything moves. Afterwards it verifieslateston all seven packages and npm'sX
integrity. It then reads the package-levelreadmeback; the next entry says how (LCLI-616
replaced a warn-only byte count). A failed platform move or launcher publish restores every
latestthat run moved, the launcher's included, by dist-tag. A failure in the step-7 checks
afterwards restores nothing by itself, by design, and names--rollback <record>as the remedy.
--rollbackrestores every recordedlatest. Nothing is unpublished. Every npm call the promotion
and pair-receipt scripts make passes both--registry=https://registry.npmjs.org/and
--@opum-ai:registry=https://registry.npmjs.org/. That covers the dist-tag moves, the publish,
everynpm view, and thenpm packof the served rc.--registryalone does not beat an
@opum-ai:registryset in a user, project or global npmrc, as measured on npm 12.1.0. Every
read also passes--userconfig=/dev/null, so no token is sent. The Release run must be a
workflow_dispatchrun built fromopum-ai/lore-cli. The spec is quest-cli's promote clause
(QCLI-399,e3c59d7b) and opum-cli-e2e'sreceipts/README.mdsteps 5 to 7 (4f078e6b). -
The README read-back runs after the final
latestpublish, with three verdicts (LCLI-616,
OPAG-474 AC3).scripts/promote-latest.mjs --promoterunsscripts/readme-readback.shonce,
after step 7, in a private directory holding theXlauncher tarball's ownpackage.jsonand
README.md. The registry pins travel in its environment. Every inherited npm config variable,
in either case, is dropped first, because an uppercaseNPM_CONFIG_@OPUM-AI:REGISTRYbeat the
lowercase pin.REGISTRY_WINDOW_SECONDSpasses through, and--dry-rundoes not run it. The
script now ends every path, an interrupted one included, with one line:
A4 VERDICT: PASSED|NOT-CONFIRMED|FAILED <reason>. Promote reads that line, not the last line
printed. An emptyreadmeafter the window is settled by one packument read ofreadmeand
versions. If that read already lists the version, the verdict is FAILED (OPAG-474). If not, it
is NOT-CONFIRMED, because lag is not ruled out: the packument had noreadmebefore this
publish. The previous-release pick skipsX-rc.Nversions, which sort just belowXand never
set the field. PASSED exits0. NOT CONFIRMED and DID NOT PASS both exit3, which since
LCLI-622 also means the GitHub Release was not cut: a post-latest step did not complete. The message says the promotion is complete and verified, says not to run--rollback,
and gives the commands that re-read the readme or re-run the read-back by hand. A temp
directory, extraction or script that fails is a NOT CONFIRMED tooling failure, never an OPAG-474
claim.release.yml's stagingpublishjob no longer runs the read-back. A staged rc never sets
the field, and the job's sparse checkout never carried the script, so everypublish: truerun
concludedfailureand could not be promoted.test/release-workflow.test.tsnow requires every
scripts/file a job runs to be in that job's own checkout, and states what it cannot see. -
The post-latest steps are printed after the promotion, not after staging (LCLI-618).
scripts/publish-release.sh's closing checklist, from--print-checklistand at the end of a
real run, now lists only what applies before the finallatestpublish. It tells opum-cli-e2e
the staged pair is ready, with the Release run id, and gives the promote--dry-runand
--promotecommands. It then points atscripts/promote-latest.mjsfor the rest. The README
read-back, the GitHub Release, the "lore is live" notices, the LCLI-469 marketplace handshake
and the release-truth record are gone from it.promote-latest.mjs --promotenow prints them as
a post-latest checklist when it finishes, whether or not the read-back passed. The checklist
carries the read-back's state (PASSED, NOT CONFIRMED or DID NOT PASS) and verdict line, and
the GitHub Release's outcome, which LCLI-622 made an executed step rather than a printed command. It lists the "lore is live" notices for quest-cli, opum-cli-e2e
and opum-agent. It gives the four LCLI-469 handshake values, resolved: the tag object and
peeled commit, and theskills/tree SHA read throughgh api. It also gives the run id and
record path. It cites runbook section 3 item 8, which carries the
same five steps, and a test holds the two together. publish-release.sh's post-publish
registry-state table is now printed after the registry-visibility wait, not before it. On the
0.11.0 staging it read the launcher asABSENTthrough propagation lag, and the poll after it
then found the launcher. -
A resumed staging skips a package only if the registry holds this run's bytes (LCLI-621).
Bothrelease.yml'spublishjob andscripts/publish-release.shcompare an already-published
package'sdist.integritywith the run's tarball, platforms included, and refuse on a difference
or an unreadable value. The comparison runs for all seven in a pre-flight before the first write,
so a refusal leaves nothing published and never waits out the registry-visibility window. Before
this, a platform package already on the registry was skipped unchecked, so anX-rc.Nstaged
from a new Release run could install an older run'sXplatform bytes. Whether a package is
already published is read as absent only from npm's own not-found (E404); any othernpm view
failure refuses, while the registry-visibility poll still retries it. -
Staging now requires the receipt fields promotion requires (LCLI-621).
scripts/publish-release.shgates staging onscripts/pair-receipt.mjs --check-release-receipt.
That is the sameevaluateReleaseReceiptpromotion runs, and it replaces a 60-line inline
node -echecker that had never learned opum-cli-e2e's TASK-126 fields. A receipt must now name
this run'sX-rc.NaslauncherVersion. It must also carry alauncherSubstitutionofMATCH
whosefinalTarballnames the basenameopum-ai-lore-<X>.tgzat the carried launcher's sha256.
Itstarballsmust name exactly the seven staged packages. The carriedXlauncher is no longer
accepted there even at its true digest, per opum-cli-e2e'sreceipts/README.mdate0021c7.
Before this, staging accepted receipts that promotion then refused. The closing checklist's
promote commands now pass--release-run <run-id>, without whichpromote-latest.mjsexits 2.
Changed (breaking)
lore checkfails on every error-tierquote-safetyfinding thatlore validatereports (LCLI-612, ADR-0007). It uses the same rule name and byte-identical messages,
for every type. The findings cover an unquoted YAML-1.1 boolean (yes/no/on/off/y/n),
a value that starts with a YAML indicator character, and a value containing:. Before this,
such a file failedvalidateand passedcheck. Runlore validateto preview whatcheck
will now fail on. A bareYYYY-MM-DDdate stays a validate-only warning. Frontmatter that is not
valid YAML still stopscheckat the parse error, before per-file rules run. A fleet measurement
by ref found 0 such findings in 396 files across 11 repositories. This ships in a minor release.
Added
-
lore checkreports an agent profile whose declared sources cannot fit itsmax_tokens
budget (LCLI-642, DEC-11). The newagent-profile-capacityfinding is computed from the profile
declaration alone — every candidate of everypinned/sourcesreference plus the auto-pinned
Constitution, rendered by the same code the pack compiler uses, score annotations included — so it
is the same on every task, needs no task string and compiles no pack. That distinction is the whole point of the check: a real
pack omits declared sources on most tasks while being entirely within capacity, so a check built on
a compiled pack would false-positive (measured on this repository's owndocumentationprofile
before the check was written). The finding names the profile, its budget, the declared set's size,
and the sources that cannot fit, in declaration order. DEC-11 rules that it ships as a warning
for one release and then as an error; this release is the warning's, and the flip is one line —
AGENT_PROFILE_CAPACITY_SEVERITYinsrc/core/check.ts— held by LCLI-646 and listed in the
release runbook's "Held release steps" so the ordering is not left to memory. Thecheck.report
also carriesagentProfileCounts(read,overCapacity,unmeasurable) and the printed report
names it, so a clean capacity answer can never be a zero-input artifact. This repository's four
profiles were re-budgeted to hold their complete declared sets — 108000 / 63000 / 94000 / 63000 —
rather than narrowed, so no declared coverage was dropped; the budgets carry headroom for the task
line and the query section, which the measurement itself does not count. -
Launcher equivalence gate (LCLI-621).
scripts/launcher-equivalence.mjsrefuses an
X-rc.Nlauncher that differs from theXlauncher by anything but the version string. It
compares entry by entry over the unpacked tarballs: the same set of paths, the same type and
mode per entry, and byte-identical content after everyX-rc.Nis replaced withX. It also
refuses an rc whose ownpackage.jsonis notX-rc.N, and an rc pinning a platform at
anything butX. It reads each archive to its end and refuses non-zero data after a zero
block, because node-tar installs an entry placed after a lone zero block. It refuses any entry
that is not a regular file or a directory, since a link's target is never compared (npm pack
emits none). It runs inrelease.yml'spackagejob and inscripts/publish-release.sh, and
both launchers get the LCLI-510 shipped-README assertion and install-sanity.
Fixed
- The GitHub Release's notes come from
CHANGELOG.mdat the commitv<version>peels to, not
from the operator's working tree (LCLI-639, twin of quest-cli QCLI-407).releaseNotesForread
<checkout>/CHANGELOG.mdout of the treescripts/promote-latest.mjsran in, so an uncommitted
edit — or a section edited after the tag — became the body of a NEW release. An existing release
was never at risk: differing notes are refused, and never edited. The bytes are now read with one
pinnedgh apiraw-contents call at the peeled sha, for the pre-move refusal, the cut and the
repair tool alike, addressed by the commit rather than the tag ref (a ref can be re-pointed, a
commit cannot).releaseNotesForis a pure function of the text it is given, and the repair
command resolves the tag first: a tag whose commit carries no such section refuses naming that
commit, and its remedies are a re-tag or a hand-cut, not an edit to a checkout the tag does not
name. Measured on this repository:node scripts/github-release.mjs --version 0.9.0now reports
"would mark it latest" where the working-tree read reported differing notes. The extractor's fence
handling is the rule agreed with quest-cli, refined after both reviews found the first version
wrong in three ways: the fence state is computed for the whole document first and remembers the
opener's character and run length (so a tilde line no longer closes a backtick fence, a
four-marker fence no longer swallows the next entry, and a line of fewer markers no longer closes
a longer one), a backtick fence's info string may not itself contain a backtick, and the section's
START is subject to that state too, so a fenced## [X]line can no longer hijack a section out
of the real heading below it. An unclosed fence still runs to the end of the file. It implements
the fence state and nothing else of CommonMark — no indented-code-block interaction, no lazy
continuation, no container or list scoping. provenance-postchecks what a Release run published: the platforms atXand the launcher
atX-rc.N(LCLI-625). Since LCLI-621 a Release run stages the launcher asX-rc.N, but
scripts/release-provenance.mjs --poststill checked every package atX. On a
publish: truerun it asked for@opum-ai/lore@X, which did not exist yet, and never checked
the rc the run did publish.--postnow requires--launcher-rc N.release.ymlpasses it
from thelauncher_rcinput, and the script refuses a missing or malformed value with exit 2.
The launcher's version is chosen by package name, so a--packageoverride cannot route it
back toX.--preis unchanged, and refuses--launcher-rc. Each--postrun says, in the
log and the job summary, that theXlauncher was not checked. That launcher is published
later byscripts/promote-latest.mjsand carries no provenance. The release runbook now
records it as "provenance-missing, byte-bound to the qualified rc". The equivalence gate and
finalTarball.sha256bind its bytes to the qualified rc, but they do not substitute for
provenance. Restoring provenance on the final publish belongs to opum-agent's OPAG-127, for
both CLIs.provenance-prere-verifies releases, not launcher version strings (LCLI-627). LCLI-625
leftscripts/release-provenance.mjs --preunchanged. It still checked all seven packages at
every version in the launcher's packument, which since LCLI-621 holdsX-rc.Nstrings the
platform packages never have. Against a packument of0.7.0,0.7.1and0.8.0-rc.1to
rc.3,--pre --limit 3asked for 21 attestations, 18 of them for platform versions that can
never exist. It never reached0.7.1, never checked a platform at0.8.0, and exited 0.
--prenow groups the launcher's versions into releases byX, which is the version with only
a trailing-rc.Nremoved.Nfollows the--launcher-rcrule. For each release it checks the
platforms atXonce, including a release whose launcher has only rcs, and the launcher at
each version it has, once each. Every other version is its own release, checked at that exact
string for every package. That includes a prereleaseXsuch as1.0.0-beta.1, which is
separate from1.0.0. It also includes a version that is not semver-shaped, which sorts last.
--limitcounts releases. A release at or below the baseline takes its rcs with it. Within a
release, rcs run in numericNorder andXcomes last. The defect was latent: no
prerelease of@opum-ai/loreis on the registry yet. It had to be fixed before the first
publish: trueRelease run under LCLI-621 puts one there.scripts/publish-release.shrefuses a malformed registry window or propagation cushion before
it reads anything remote (LCLI-629). BothREGISTRY_WINDOW_SECONDSand
PROPAGATION_CUSHION_SECONDSwere first read after all six platform packages were published,
and neither was checked. Measured on the real script under macOS/bin/bash3.2.57, a window of
30mor08skipped the visibility gate and the cushion together, so the launcher was staged
over a platform package that never became visible, and the run exited 0. A window ofabc
exited 0 after six platform publishes, with the launcher never published and no error. A cushion
ofabcwas skipped, and30mbecame a real 30-minute wait, because macOSsleepaccepts unit
suffixes. Both variables now have to be whole seconds, 0 to 999999999 with no leading zero. That
is the grammarscripts/readme-readback.shandscripts/promote-latest.mjsalready use, and
tests hold all three to one pattern. Anything else exits 2 before the version-parity read, any
download, or any registry call, dry run included. The value is shownprintf %qescaped on one
line.--verify-onlyand--print-checklistread neither variable and are exempt. Unset or
empty keeps the defaults, 1800 and 20.provenance-posttells a never-published version from one published without an attestation
(LCLI-628). npm's attestation endpoint answers a 404 in both cases. It was measured returning the
same{"error":"Not found"}for@opum-ai/lore@0.11.0and for the never-published
9.9.9-rc.1. So a Release run whosepublishjob died before the launcher reported the rc
launcher as unattested and blamed LCLI-482. It also spent the propagation window on a version
that did not exist.--poststill reads the attestation first, so a readable one is judged
exactly as before. Only after a 404 does it ask whether the version exists. It believes a
version-document 404 only when the packument also answers without that version, and reports
that asnot-published: its own verdict, with no propagation wait, and a warning that names a
partial publish or read-API lag (LCLI-460). Any other answer to the existence read, such as an
outage or a whole-package 404, isinconclusive, nevernot-published.promote-latest --rollbackrefuses a recorded priorlatestnewer than the release
(LCLI-617, paired with quest-cli QCLI-391). A hand-edited record whosepriorLatestwas newer
than itsversion(5.7.0in a5.6.7record) passed every earlier check.--rollbackthen
movedlatestonto a version no receipt qualified, because--rollbackis not receipt-gated.
EverypriorLatestmust now compare belowrecord.version, each component compared by length
and then lexically. That is exact numeric order past 2^53, so0.10.0is newer than0.9.0.
record.versionitself must be a plainX.Y.Zstring. The two repositories give identical
verdicts, measured by each reading the other's rule by ref: 0 differences over 2583 inputs, with
a positive control.release.yml's post-publish visibility wait refuses a malformedREGISTRY_WINDOW_SECONDS
before it reads anything (LCLI-630). The variable went into bash arithmetic unchecked. This was
measured by running the step's ownrun:block asbash -ein anubuntu:24.04container,
bash 5.2.21, which is not a GitHub runner. There,abcor an empty value silently made the
window 0 seconds, so a lagging package got a green step and no content check.30mor08
left the deadline empty, and against a lagging registry the step looped until the job timeout.
An arithmetic subscript such asx[$(cmd)]rancmdinside thepublishjob, which holds
id-token: write. No such variable is set today at repository,release-environment or
visible organisation level, so the hole has not been live. The step now refuses anything
outside the sharedREGISTRY_WINDOWgrammar, whole seconds from 0 to 999999999 with no leading
zero, with exit 2. It does this before any registry read, deadline or sleep. The value is shown
%q- and%25-escaped on one::error::line. A test pins the pattern to
REGISTRY_WINDOW.source. The guard still runs only after the publish; checking both window
variables before the first publish is LCLI-634.