Commit Graph
393 Commits
Author SHA1 Message Date
Jason Woltje 4f7f6b3281 feat(mosaic): add correlated lease promotion CLI 2026-08-11 20:51:03 -05:00
Jason Woltje 77edb0dea2 feat(lease): add single-turn Claude promotion trigger 2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Fable 5 3676180ae8 fix(lease): raise lease TTL 300s -> 3600s
MAX_LEASE_TTL_SECONDS (daemon cap+default) and DEFAULT_TTL_SECONDS
(lease_promote client) both move to 3600. The 5-minute TTL made
gated-by-default sessions unusable (re-promotion mid-task); 1 hour
matches a working session. Full test:framework-shell RC=0.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_013SAYFkRhQfhguY7AHfiUC8
2026-08-11 20:51:03 -05:00
Jason Woltje 0e938b66ed fix(lease): ignore benign observer idle replies 2026-08-11 20:51:03 -05:00
Jason Woltje f0fef26eb7 fix(lease): constrain read-only tool carve-outs 2026-08-11 20:51:03 -05:00
Jason Woltje c9bccd4aae test(lease): assert pi carve-out capability 2026-08-11 20:51:03 -05:00
Jason Woltje 8ef2e5b91d test(lease): distinguish pi probe timeouts 2026-08-11 20:51:03 -05:00
Jason Woltje 4cab6c09fe test(lease): enforce read-only tool invariant 2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Fable 5 d085182dc1 test: close W-0R review findings — assert the omission notice, skip chmod simulations under root
The independent W-0R review of 3592b92e passed but left two PLAUSIBLE
findings: the stderr notice for a legitimately-omitted operator source was
claimed and never asserted (a silent omission is the original defect in
miniature), and the chmod 0o000 unreadable simulations fail spuriously when
euid==0 (CAP_DAC_OVERRIDE). Falsifier for the new assertion: deleting the
notice block turns the suite red (failures=3); restoring returns green.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EHYXhcCQsL3J1Lnm7EraGq
2026-08-11 20:51:03 -05:00
Jason WoltjeandClaude Opus 5 e949fa3767 fix(lease): refuse an incomplete law binding instead of silently shrinking it
build_construction skipped any normative source it could not read
(`except OSError: continue`) and promoted whatever remained. That is not a
degraded binding, it is a forged smaller one: the broker recomputes h_source /
h_payload from the fragments it is SENT (daemon.py:602-616), so an omitted
fragment is internally consistent and PAYLOAD_BINDING_MISMATCH cannot fire. A
partial law promotes exactly like a complete one and nothing downstream can tell
the difference.

Measured before this change, against a seeded home: with only USER.md readable,
the client produced a one-fragment construction with promotion=True. Removing
CONSTITUTION.md, STANDARDS.md or the runtime contract likewise promoted.

The classification mirrors the framework's own file ownership rather than
inventing one:

  * CONSTITUTION.md / AGENTS.md / STANDARDS.md are framework-owned and
    reconciled every upgrade (install.sh FRAMEWORK_OWNED,
    config/file-adapter.ts FRAMEWORK_OWNED_FILES), as is the per-runtime
    RUNTIME.md. Absent => IncompleteBinding. A deployment missing one is broken,
    not minimal.
  * SOUL.md / USER.md are deliberately not seeded by install.sh ("generated by
    `mosaic init`") and TOOLS.md is seeded on first install only, so their
    absence is legitimate. It is reported on stderr, never silent.

Unreadable is handled separately from absent for EVERY source, optional ones
included: a file that will not open is not a file that was never configured, and
collapsing the two is what let a permission change quietly shrink the law.

Also corrects this module's own docstring, which asserted that a VERIFIED lease
means "this agent is running THIS law". It does not. Both sides of the broker's
comparison originate in this client, so it detects corruption in transit and
nothing else. That overstatement is where the belief spread from; the stronger
claim needs the broker re-reading on-disk sources against a manifest the agent
cannot rewrite.

Test: promotion_binding_unittest.py, enumerated in test:framework-shell (the
enumeration guard's population is *test*.sh and does not cover Python, so an
unenumerated test here would simply never run). Falsifier executed: defeating the
guard while leaving the module API intact turns the suite red (12 failures);
restoring it returns green.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EHYXhcCQsL3J1Lnm7EraGq
2026-08-11 20:51:03 -05:00
Jason Woltje f1761c91be Revert "feat(pi): wire lazy lease promotion into the mutator gate"
This reverts 939f2e04. Keeping the revert rather than dropping the commit,
because the failed attempt is the most useful record on this branch.

The wiring worked mechanically — verified with a live model on sb-it-1-dt: the
receipt was emitted verbatim as a whole message, and the broker token was minted
AND consumed, so observe_receipt and promote_lease both succeeded and the lease
reached VERIFIED.

It failed as a DESIGN, for reasons that are properties of the protocol rather
than of this wiring:

  * It puts control-plane traffic in the user-facing conversation channel. An
    operator asking "what model are you?" received a receipt string instead of an
    answer — the model tried a tool, was blocked, complied with the receipt
    instruction, and in one-shot mode that text turn BECAME the reply. Observed
    twice, non-deterministically.
  * The lease TTL is hard-capped at 300s (MAX_LEASE_TTL_SECONDS; ttl_seconds >
    cap raises INVALID_LEASE_TTL). Measured: allowed at T+0, LEASE_EXPIRED at
    T+310. So the visible cost recurs every five minutes of mutator activity.
  * Model compliance is not guaranteed — one run retried the command instead of
    emitting the receipt.

Any model emission is user-visible, so this is not fixable by better wiring; it
needs a design answer about how promotion is triggered and paid for. That is
under adversarial review (docs/scratchpads/lease-remediation/07-liveness-design-brief.md
in the operator's repo). Promotion triggering will return on its own branch once
that lands.

What remains here is independently sound and unblocked: harness-home isolation,
the immutable launch record, the skills relocation, the promotion client itself
(steps 1/4/5), and the #1087 prefix guard.
2026-08-11 20:51:03 -05:00
Jason Woltje 8109f72cf7 fix(sync-skills): guard the pre-existing prune against an empty prefix (#1087)
prune_stale_links_in_target compared "$resolved" == "$canonical_real/"* while
length-checking only $resolved. If $canonical_real were ever empty the pattern
collapses to == "/"* and matches every absolute path.

The failure is precisely inverted, which is what makes it worth fixing rather
than noting: is_mosaic_skill_name already `continue`s for names that ARE current
mosaic skills, so an empty prefix would delete exactly the FOREIGN symlinks in
every target directory and preserve the mosaic ones. On this host that is 4 base
installs, including codex's own .system entry.

Reported by mos-claude as #1087 after I introduced the same guard in the new
legacy-cleanup path in the previous commit and walked past this instance thirty
lines away. Same defect class, same file, one function apart.

$canonical_real is populated by readlink -f after a mkdir -p, so an empty value
requires readlink to fail — unlikely, but the consequence is deleting operator
symlinks across every harness, which is not a risk worth carrying for one test.
2026-08-11 20:51:03 -05:00
Jason Woltje a0be592d84 feat(pi): wire lazy lease promotion into the mutator gate
Completes the promotion path: the client landed in the previous commit, but
nothing drove step 2 — the model emitting the receipt. This wires it.

LAZY, not at session start. Promotion costs an entire model turn, because the
receipt must be the whole message (hmac.compare_digest, "not a transcript
substring"). Minting at session start would collide with the Constitution's
first-response mode declaration — the two cannot share a message, so requiring
both would be unsatisfiable. Deferring to the first DENIED MUTATOR means the
mode declaration happens first and the receipt gets its own later turn, so no
governance change is needed. A read-only session never pays for promotion at all.

Mechanism: on a MUTATOR_UNVERIFIED denial the tool_call hook mints a challenge
and returns the receipt in the block `reason`, which pi feeds back to the model
as the tool result — the existing injection path already used by
lease-lifecycle.ts. The model emits the receipt as its next message, message_end
ships it to the observer, and the extension then calls observe_receipt +
promote_lease.

Only MUTATOR_UNVERIFIED triggers minting. Other denials (GATE_UNAVAILABLE,
STALE_GENERATION, LEASE_EXPIRED, ANCESTRY_MISMATCH) describe conditions a
receipt cannot fix, and begin_verification revokes before it mints, so minting
there would thrash the broker.

Completion is gated on an EXACT text match against the minted receipt. This is
load-bearing, not defensive: message_end also fires for the message that
CONTAINED the blocked tool call — one turn BEFORE the model answers. An earlier
version completed there, so observe_receipt compared against the wrong text,
failed, and burned the challenge before the model ever emitted it. Matching the
text mirrors the broker's own compare_digest semantics and waits for the right
turn. Confirmed by instrumenting message_end and watching it fire with
pending=yes one message too early.

It never posts the receipt itself. receipt-observer-client.py accepts any
string, so self-posting would satisfy the broker while proving nothing — the
whole point is that a live model echoes a challenge it was given.

Bounded by MAX_PROMOTION_ATTEMPTS: model compliance is not guaranteed (observed
a run where the model retried the command instead of emitting the receipt), so
a non-complying model degrades to today's behaviour — denied mutators — rather
than looping.

Verified with a live model on sb-it-1-dt: receipt emitted verbatim as a whole
message, and the broker token was minted AND consumed, i.e. observe_receipt and
promote_lease both succeeded and the lease reached VERIFIED.

Known limitation: under `pi -p`, the receipt is a text-only turn, which ends the
one-shot loop — so promotion completes but the blocked tool is not retried in
that same invocation. Interactive and durable fleet sessions continue and retry
normally.
2026-08-11 20:51:03 -05:00
Jason Woltje f4a24b693e feat(lease-broker): add the missing promotion client
The enforcement half of the lease broker ships and denies; the promotion half
has no production caller anywhere in the package. Verified across 0.0.48, 0.0.49
and 0.0.50-next.2207: begin_verification / observe_receipt / promote_lease are
invoked only by broker-test-client.ts, the acceptance spec, unit tests, and two
probes under docs/.

Consequence: no lease on any host can reach VERIFIED, so mutator-gate denies
every mutator with MUTATOR_UNVERIFIED via a gate that nothing shipped can
satisfy. Runtimes that enforce the gate in-process (pi, via mosaic-extension's
tool_call hook) are bricked for mutators; runtimes whose gate is wired through a
settings hook escape only when that hook is absent — i.e. by being ungated.

This adds the client. It implements protocol steps 1, 4 and 5:

  1. begin_verification  -> mint a challenge, return the exact receipt text
  2. the MODEL emits that text verbatim as its entire latest message
  3. the runtime adapter ships that message to the observer socket
  4. observe_receipt      -> PENDING_PROMOTION
  5. promote_lease        -> VERIFIED

Step 2 is deliberately NOT implemented here, and that is the point.
is_verbatim_receipt uses hmac.compare_digest against the exact minted string —
explicitly "not a transcript substring" — which makes promotion a LIVENESS
PROOF: it requires a live model that received the challenge in its context and
echoed it exactly.

receipt-observer-client.py will post ANY string as the latest assistant message.
A promotion client that posted its own receipt would satisfy the broker while
proving nothing — a gate-disabler indistinguishable from a working fix unless
someone specifically looks. Emitting the receipt therefore belongs to the runtime
adapter, where a real model turn happens. A local diagnostic that posts its own
receipt exists in the operator's repo and is deliberately NOT shipped here.

The construction binds the exact normative source bytes, so a VERIFIED lease
means "this agent is running THIS law", not merely "this session id is known".
h_source/h_payload are derived by importing the framework's own
normative_fragments.build_payload rather than reimplementing it: the broker
derives them the same way and any divergence yields PAYLOAD_BINDING_MISMATCH.
There must be exactly one implementation.

session_identity() prefers the generation FILE over the env var, matching
lease_generation.py. Sending a generation higher than the broker's would revoke
the session's own authority (daemon.py:342-344), so it never guesses.

Verified end-to-end on sb-it-1-dt under a real lease-gated anchor: a mutator
denied rc=2 MUTATOR_UNVERIFIED, then begin -> observe -> promote -> VERIFIED,
then the same mutator allowed rc=0. Negative controls pass: a fresh session is
still denied, and an unrelated session still reads UNVERIFIED — promotion is
per-session and does not leak.

Still open: adapter wiring for step 2. Lazy promotion on first mutator attempt
avoids colliding with the Constitution's first-response mode declaration, since
compare_digest requires the receipt to be the WHOLE message.
2026-08-11 20:51:03 -05:00
Jason Woltje e4dffb7c18 feat(launch): isolate harness homes and record immutable launch provenance
Mosaic wrote into the operator's harness base installs — ~/.claude,
~/.pi/agent, ~/.codex, ~/.config/opencode — for settings, instructions, and a
102-symlink skill farm per harness. Any experiment with hooks or gating
therefore mutated the operator's own tooling, and a broken framework change
could take out the very harness needed to repair it.

Harness home isolation
----------------------
Each runtime now reads config from a dedicated mosaic-owned home via the
harness's own config-dir variable:

  claude    CLAUDE_CONFIG_DIR     ~/.config/mosaic/.claude
  pi        PI_CODING_AGENT_DIR   ~/.config/mosaic/.pi     (replaces ~/.pi/agent)
  codex     CODEX_HOME            ~/.config/mosaic/.codex
  opencode  XDG_CONFIG_HOME       ~/.config/mosaic/.opencode

These paths are manifest-UNKNOWN, so rule 3 (#791) resolves them to operator
ownership and a keep-mode upgrade can neither overwrite nor prune them.
A bare `claude` / `pi` keeps its own config AND auth, making it a structural
break-glass rather than one depending on restoring a file under pressure.

opencode is blunter than the rest: it has no dedicated variable and follows XDG,
so isolation also relocates XDG lookups for anything it spawns. Documented in
place.

mosaic-sync-skills now links into those homes and cleans the legacy farms it
previously planted in base installs. Ownership is proven by RESOLUTION, not by
name — only symlinks resolving inside the canonical/local skills dirs are
removed, mirroring the refusal already in commands/skill.js. Verified against a
real install: codex's own .system directory survived while its 102 mosaic links
were removed. Both resolution prefixes are length-checked first; an empty prefix
would make "$resolved" == "$prefix/"* match every absolute path and delete
foreign symlinks.

Immutable launch record
-----------------------
Every launch now appends one record to fleet/run/sessions/events.ndjson before
exec. Mandatory, mechanical, no model involvement.

pi rewrites its own argv to a bare `pi`, so /proc/<pid>/cmdline destroys the
launch evidence — that has already produced a confident wrong diagnosis ("this
agent bypassed the launcher"), disproved only by the parent's argv and only
because the parent had not yet exited. A record written before exec is the only
place this survives.

The path is the #797 Runtime Session Ledger, already operator-classified and
already covered by test-upgrade-manifest-guard.sh, which seeds it and proves a
populated ledger survives keep-mode upgrades — but nothing shipped ever wrote
it. This implements it in the shape that guard already asserts (0600 files under
a 0700 dir).

`mosaic` writes session.launch; launch-runtime.py appends lease.register with
the broker session id and activation capability. They correlate by an explicit
MOSAIC_LAUNCH_ID, never by pid: execRuntime uses spawnSync, so the runtime is a
child with a different pid.

Records normative fragment digests (CONSTITUTION/AGENTS/SOUL/USER/STANDARDS/
TOOLS/RUNTIME) — the same set the broker hashes for promotion, so drift is
mechanically detectable rather than a matter of judgement.

Credential-safe: env is captured as PRESENT NAMES ONLY, and argv values over
256 bytes become a sha256 + length rather than being inlined.

Also fixes CLI_VERSION resolution: '@mosaicstack/mosaic/package.json' is not in
the package exports map and always throws ERR_PACKAGE_PATH_NOT_EXPORTED.
resolveTool() uses that same failing specifier, which is why its documented
preference for bundled tools over the deployed ~/.config/mosaic copy has never
once applied — noted in place, not fixed here.

Verified on sb-it-1-dt: isolated homes written and base installs byte-identical
for all four harnesses; 408 legacy symlinks removed with 1 foreign entry
preserved; launch records paired across the spawn boundary. typecheck shows zero
errors in launch.ts (the @mosaicstack/types failures are pre-existing and
reproduce on a pristine origin/main worktree).
2026-08-11 20:51:03 -05:00
be-coder-08andJason Woltje f840843908 feat(pr-merge): preserve linked authors in squash messages (#1066)
Co-authored-by: be-coder-08 <[email protected]>
2026-08-11 20:51:03 -05:00
be-coder-08andJason Woltje aacb11b0b9 fix(ci): remove upgrade rollback signal race (#1060)
Co-authored-by: be-coder-08 <[email protected]>
2026-08-11 20:51:03 -05:00
be-coder-08andJason Woltje ce6bda18f2 test(ci): make queue guard harness deterministic (#1062)
Co-authored-by: be-coder-08 <[email protected]>
2026-08-11 20:51:03 -05:00
Jason Woltjeandmos-dt-0 b6c12bdfcb style(framework): apply prettier to WRITING-STYLE.md so CI format passes (#965)
ci/woodpecker/pr/ci Pipeline was successful
The `format` step of .woodpecker/ci.yml:89 (`pnpm format:check`) failed on
pipeline 2111 for this branch. Reproduced on a bench with the lockfile-pinned
[email protected] against the repo .prettierrc and .prettierignore, using CI's
exact glob: WRITING-STYLE.md was the only failing file.

The change is mechanical and semantically null: markdown table cell padding
and `*emphasis*` -> `_emphasis_`. Verified by normalizing both revisions
(whitespace removed, `_`/`*` folded, table rules collapsed) — the results are
byte-identical.

This does not address the prose findings published on #965 (P1-P4); those
await a ruling. The `test` step also failed on 2111, on a base ~40 commits
stale — attribution for that failure needs this rerun, and is not claimed here.

Co-authored-by: mos-dt-0 <[email protected]>
2026-08-11 19:00:46 -05:00
Jason Woltje b590a5c3d8 fix(git): accept http/https as one scheme class in comment URL verification (#991)
ci/woodpecker/pr/ci Pipeline was successful
issue-comment.sh and pr-review.sh verify a durable write by pinning the
provider-returned object URL's origin and full path. The origin included the
SCHEME verbatim. On a Gitea whose ROOT_URL is configured `http://` while every
client reaches it over `https://`, the provider returns `http://` object URLs,
so the comparison rejects the provider's own truthful answer about a write that
LANDED. The failure is deterministic, not intermittent: every comment, every
time, on such a deployment.

The scheme was never what the check defends. The forgeries it exists to catch —
look-alike host, decoy path prefix, wrong owner/repo/kind/number — all vary the
HOST or the PATH. Both stay strict. `http` and `https` now collapse to one
scheme class; any other scheme (file:, ftp:, javascript:) stays distinguishing,
and an EXPLICIT non-default port still distinguishes, because a different port
is a different service on the same host.

Consequences of the bug, both observed:

- The wrapper reports failure on a comment that is durably on the issue/PR, and
  attributes it to #865 ("no durable comment created"). The write landed; the
  citation is wrong. Reproduced here: the harness's persisted state contains the
  record while the wrapper exits 1.
- pr-review.sh's comment path is worse. On a host where no seat can create a
  review OBJECT, comment-form is the only gate-16 review record obtainable, and
  this check refuses all of it.

Test gap this closes: every URL fixture in both harnesses was `https://`, and
every negative case varied only host or path. The one axis that fails in
production had zero coverage — the fixtures encoded the assumption that breaks.
Added, in both suites:

- scheme-downgrade (http vs https, otherwise correct) — must be ACCEPTED. Fails
  against the unmodified wrappers, passes against the fixed ones; verified in
  both directions, and the negative control's captured output is the #865
  misattribution above.
- explicit non-default port (`:8443`) — must stay REJECTED.
- non-web scheme (`ftp://`) — must stay REJECTED.

Also fixes test-issue-comment-readback.sh hermeticity (#1007), without which the
suite cannot run on any seat that has a per-agent Gitea token: detect-platform's
step-0 identity lookup reads ~/.config/mosaic/gitea-tokens/<identity>, outside
both XDG_CONFIG_HOME and MOSAIC_CREDENTIALS_FILE, so the suite resolved a
PRODUCTION credential and died at HTTP 401 before case 1. Same two-part fix
already merged for test-pr-review-gitea-comment.sh in #1006: a sandboxed HOME
plus an empty REPO-LOCAL mosaic.gitIdentity to shadow the global. Note the
env-var route does NOT work — detect-platform.sh reads `${MOSAIC_GIT_IDENTITY:-}`
and `:-` treats set-but-empty identically to unset.

The owner-side half of #991 (setting the deployment's Gitea ROOT_URL to https)
is not in scope here and is not made unnecessary by this change; this makes the
wrappers correct against a deployment that returns either scheme.
2026-08-11 19:00:31 -05:00
30a694358d fix(framework): key §5 lookup on rendered bullets, not the token (mos-dt round-2)
§5 sent the agent to read direct|friendly|formal in USER.md, but the builder
renders prose bullets, not the token — the documented lookup could not key on
the shipped file. Table now keys on the leading bullet USER.md actually
contains. Also: 'concise, technical' -> 'concise, structured' (drop the round-1
residual value name from a rule-9 guide). Docs-only, no code, no scope growth.

Written-by: jarvis (dragon-lin)
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 18:22:15 -05:00
e01dfa0cd7 fix(framework): ride the existing communicationStyle enum, drop the no-op USER.md edit (mos-dt review #960)
F1: defaults/USER.md is never installed (generated from templates/USER.md.template
via buildCommunicationPrefs). Editing it was a no-op asserting a phantom setting —
exactly the false-green §2 warns against. Reverted.
F2: the framework already has communicationStyle (direct|friendly|formal). §5 now
maps THOSE values to output instead of inventing technical|prose|brief (rule 9).
Minor: §6 states no mechanical prose check exists today; rule 1 points at §3.4.

Written-by: jarvis (dragon-lin)
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 18:22:15 -05:00
6c4a2eb626 feat(framework): MOS-STE writing standard + Google-style code + per-user comms choice
Adds the agent output standard to the framework SOT so it injects at launch and
is selectable per user (closes the gap: it lived only as a jarvis-brain lab doc + issue #960).

- guides/WRITING-STYLE.md: MOS-STE (adapted ASD-STE100) for docs, Google Style for code,
  verification-artifact emphasis, absolute user-voice carve-out. Written in MOS-STE.
- defaults/STANDARDS.md: Output-standards block (always injected via the prompting contract).
- defaults/AGENTS.md: routing row so writing/doc/comms work reaches the guide.
- defaults/USER.md: per-user 'Comms style' option (technical|prose|brief), default technical.

Refs mosaicstack/stack#960. Owner directive (Jason, 2026-07-30): docs->adapted ASD-STE100,
code->Google style, resumes/personal carved out, comms style a per-user choice.

Written-by: jarvis (dragon-lin)
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-11 18:22:15 -05:00
mos-dt-0 540ec5b6ef Merge pull request 'fix(git): #1007 suite hermeticity — pin repo-local mosaic.gitIdentity in five test suites' (#1024) from fix/1007-suite-hermeticity into main
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline failed
2026-08-11 23:19:49 +00:00
mos-dt-0 563d1ac053 Merge pull request 'fix(shell): remove wake validation pipe hazards' (#1107) from fix/1099-pipefail-wake into main
ci/woodpecker/push/publish Pipeline was canceled
ci/woodpecker/push/ci Pipeline was canceled
2026-08-11 23:19:45 +00:00
Mos 4d8ddb9a0a fix: quote SKILL.md descriptions containing colons (silent skill-load failure) (#3) 2026-08-11 22:53:47 +00:00
mos-dt-0 0e4eb1445c Merge pull request 'fix(installer): propagate wizard gateway failures' (#1134) from fix/wizard-gateway-failure into next
ci/woodpecker/push/publish Pipeline failed
2026-08-11 22:01:43 +00:00
mos-dt-0 a43f343efd Merge pull request 'fix(security): mosaic-init RCE — eval on prompt answers → printf -v (#1115)' (#1127) from fix/mosaic-init-rce into next
ci/woodpecker/push/publish Pipeline was canceled
2026-08-11 22:01:35 +00:00
mos-dt-0 722163671f feat(pi): add persistent Mosaic /goal controller (#1152)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-10 22:54:43 +00:00
mos-dt-0 bf32f29acd fix(ci): make queue guard purpose-sensitive (#1148)
ci/woodpecker/push/publish Pipeline failed
2026-08-10 20:54:09 +00:00
Jason Woltje dc67590a96 fix(installer): propagate wizard gateway failures (#1120)
ci/woodpecker/pr/ci Pipeline was successful
2026-08-09 05:01:09 -05:00
Jason Woltje 12677a928d fix(mosaic): prevent init prompt code execution
ci/woodpecker/pr/ci Pipeline was successful
2026-08-09 00:28:57 -05:00
f10-coder f158be8003 fix(shell): remove wake validation pipe hazards
ci/woodpecker/pr/ci Pipeline was successful
2026-08-07 06:13:47 -05:00
f10-coderandMos b0f7d26dd9 fix(shell): remove test harness pipe hazards (#1106)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/manual/ci-image Pipeline failed
ci/woodpecker/manual/publish Pipeline failed
ci/woodpecker/manual/ci Pipeline was successful
Co-authored-by: f10-coder <[email protected]>
2026-08-07 11:12:17 +00:00
f10-coderandMos 3a1203b2f8 fix(shell): remove runtime early-exit pipe hazards (#1105)
ci/woodpecker/push/publish Pipeline failed
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: f10-coder <[email protected]>
2026-08-07 09:38:37 +00:00
be-coder-08 df4c591ab4 fix(fleet): make framework shell assertions SIGPIPE-safe (#1100)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-07 08:21:20 +00:00
be-coder-06andMos 4fa2768962 fix(fleet): propagate roster git identity (#1073)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: be-coder-06 <[email protected]>
2026-08-07 07:07:45 +00:00
Mos aa0a7b5fa2 fix(tools/git): issue-close.sh silently dropped the closing comment (#1085)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-07 05:59:12 +00:00
Mos f744f32214 feat(tools/git): explain tea's misleading user does not exist error (stale token, not a missing account) (#1086)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-07 05:07:40 +00:00
Mos 8ff7aac0ca fix(tools/git): detect-platform died silently outside a repo, taking every wrapper with it (#1089)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-08-07 04:26:36 +00:00
be-coder-08andMos 80a45b1e1c feat(pr-merge): preserve linked authors in squash messages (#1066)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: be-coder-08 <[email protected]>
2026-08-06 05:36:59 +00:00
be-coder-08andMos 85d2108e4e fix(ci): remove upgrade rollback signal race (#1060)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: be-coder-08 <[email protected]>
2026-08-05 22:14:15 +00:00
be-coder-08andMos 16f91157a1 test(ci): make queue guard harness deterministic (#1062)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: be-coder-08 <[email protected]>
2026-08-05 21:49:44 +00:00
coder-mos2 906ad8dc30 wip(sync): merge main into next with combined resolutions 2026-08-02 23:11:22 -05:00
coder-mos1andmos-dt-0 5916aeefd6 chore(release): @mosaicstack/mosaic 0.0.49 — ship RM-03 guard fix to release channel (#1036)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline failed
Co-authored-by: coder-mos1 <[email protected]>
2026-08-02 20:09:11 +00:00
coder-mos1andmos-dt-0 58b971aba3 fix(rm-03): make CI queue guard fail on asserted non-readiness (#1032)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: coder-mos1 <[email protected]>
2026-08-01 18:55:26 +00:00
coder-mos1andmos-dt-0 f4fd5967fc RM-61: prove ci-postgres teardown discrimination (#1033)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: coder-mos1 <[email protected]>
2026-08-01 14:54:04 +00:00
mos-dt-0andMos 06e0d40352 feat(quality): CI test-membership guard — enumeration can no longer silently under-run the disk (#1017) (#1018)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: mos-dt-0 <[email protected]>
2026-07-31 14:08:41 +00:00
Mos 166ee8c90f fix(wake): close fd 9 in the detector's sleep child so a dead detector's lock dies with it (#993)
ci/woodpecker/push/publish Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
2026-07-31 13:48:33 +00:00
Jason Woltje 2fa6bcd576 fix(git): #1007 — test-issue-comment-readback is a FIFTH affected suite (second census correction)
ci/woodpecker/pr/ci Pipeline was successful
My previous commit said four. It is five. `test-issue-comment-readback.sh` has
the same defect and is fixed the same way, and I had already looked straight at
it and filed it as an *unrelated* silent failure. Correcting that here rather
than folding it in quietly.

WHY IT WAS MISSED — the general lesson, not the excuse. `run_comment()` sends
the wrapper's stdout AND stderr to `$OUTPUT_FILE`, and the `EXIT` trap deletes
`$WORK_DIR`. The suite therefore exits 1 with ZERO bytes on stdout and stderr,
and the one line that says what went wrong —

    Error: Gitea authenticated-identity read failed with HTTP 401

— lives only inside a directory that no longer exists when anyone looks. Every
oracle I had swept the family with greps for a SYMPTOM in surviving output, so
against this suite all of them returned "nothing found", which I read as "clean"
in the first sweep and as "unrelated pre-existing failure" in the second. A
suite that discards or deletes its own evidence converts a post-hoc assay into a
non-measurement, and I wrote that sentence into the previous commit while it was
already false about a file in the same directory.

HOW IT WAS ACTUALLY FOUND. Intercept the identity read at its SOURCE instead of
grepping for its consequence: a PATH shim over `git` that logs every
`mosaic.gitIdentity` read — args, rc, and resolved value — to a file OUTSIDE any
suite's work dir, then execs the real git. Deletion-proof by construction, and
it measures the defect's cause rather than one of its symptoms. Sweeping all 16
suites with it under an ordinary invocation:

  resolves a REAL identity (`mos-dt-0`) before the fix:
    test-issue-comment-readback          1 read   rc=1 (RED on every seat)
    test-pr-review-repo-host-override    6 reads  rc=0
    test-ci-queue-wait-branch-absent     3 reads  rc=0
  the four fixed in the previous commit now read empty; the rest never read at all.

The latter two are NOT affected and are deliberately left alone: under a seat
replica (identity set, no per-slot token) neither reaches `get_gitea_token`'s
fail-loud branch, and under a canary HOME neither carries the canary credential
into any surviving artifact. They read the identity and never enter a credential
path. That residual is structural and belongs to the wrapper half of #1007 —
scoping the read with `git -C "$repo"` removes it for everyone at once.

An earlier version of that sweep reported the four fixed suites as still
resolving a real identity. That was my grep, not the suites: `value=\[..*\]` is
satisfied by `value=[] args=[…]`, because `.*` runs past the empty pair and
matches the closing bracket of the NEXT one. `value=\[[^]]` is the correct test.
Recorded because the wrong pattern failed in the direction that would have sent
me re-fixing four already-correct files.

VERIFICATION of this suite, four HOME arms, all rc=0 with zero non-empty
identity reads and the pass line on stdout: real HOME, seat replica, canary
HOME, and an empty HOME with no identity at all. Full 16-suite sweep after the
change: every suite rc=0.

CONSEQUENCE FOR THE FINDING LIST IN THE PREVIOUS COMMIT: item 2 there — the
"silently red, unrelated to #1007" suite — is withdrawn. It was #1007 all along.
Item 1 (`pr-metadata.sh:89-92`, the anonymous fallback that reports an HTTP 200
carrying valid JSON as "unknown API error") stands and is still unfixed here.

Refs #1007
2026-07-31 07:27:10 -05:00