Round-2 review found the positive-path test's writeFileSync() staged a
stub cli.js/index.js at the package's REAL resolved dist/ path, and
afterEach only removed files that had NOT pre-existed — never restoring
original CONTENT for files that had. On a host with a real pre-built
dist/cli.js (ordinary `pnpm build && pnpm test`, and CI: this package's
turbo.json overrides the `test` task to depend on `build`, so CI always
builds a real dist/cli.js before running vitest), the test would silently
overwrite the real ~26KB compiled CLI with an 87-byte stub and still
report PASS. Confirmed as the exact root cause of CI1972's red `test`
step: src/cli-smoke.spec.ts execs the real dist/cli.js in the same vitest
process/run, so the clobber surfaced there.
Fix (dependency injection, not snapshot/restore):
- defaultCapabilityProbe() now takes an injectable CapabilityProbeDeps
({ resolveCliEntry }), defaulting to the real defaultResolveCliEntry()
in production — no change to the real-artifact-read guarantee.
- defaultResolveCliEntry() itself now takes an injectable ModuleResolver
(defaults to the real require.resolve), so its resolution CHOICE (bare
"@mosaicstack/mosaic" specifier vs the buggy "./package.json" subpath)
can be tested in complete isolation from real package/build state.
- The positive-path test now stages its stub cli.js in an mkdtempSync()
scratch directory and injects resolveCliEntry to point there — it never
calls the default resolver, so it structurally cannot touch the real
package's dist/. It also asserts the real dist/ path's existence is
unchanged by the test.
- The "returns null when unbuilt" test now injects a resolver pointing at
a path that cannot exist, instead of relying on this checkout happening
to be unbuilt (deterministic regardless of ambient host build state).
Verified:
- Reintroduced the R1 bug in defaultResolveCliEntry() and confirmed the
new resolver-choice test fails red against it; restored the fix (byte-
identical diff against the pre-revert file) and confirmed green.
- Built a real dist/cli.js (~26KB) via `turbo run build`, ran the targeted
specs against it, then ran the FULL `turbo run test --filter=@mosaicstack/mosaic`
CI-parity path (which builds dist/ itself per this package's turbo.json
override before vitest runs, exactly matching Woodpecker's `test` step):
77/77 test files, 1451/1451 tests passed, including cli-smoke.spec.ts
(22/22) and lease-activation-probe.spec.ts (15/15) in the SAME run.
sha256 of dist/cli.js before and after that full run: identical
(e61d8de7a2223b6578a2b733edd927707830e011f44b9d20901194c23c4a5272,
26317 bytes) — the real build artifact is untouched byte-for-byte.
- python3 -m unittest runtime_tools_unittest: 25/25 pass, including both
C-REGRESS-locked fail-closed cases.
- typecheck/lint/format:check all pass via turbo --filter=@mosaicstack/mosaic.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Independent review of PR #870 found defaultCapabilityProbe() resolved the
installed CLI via req.resolve('@mosaicstack/mosaic/package.json') — a
subpath NOT present in package.json's `exports` map. Node throws
ERR_PACKAGE_PATH_NOT_EXPORTED for that on every real install, which the
catch-all silently turned into an always-null probe: leaseEnforcementActivatable()
could never return true anywhere, defeating the card's purpose.
Fix: resolve via the already-exported "." entry instead
(require.resolve('@mosaicstack/mosaic') -> dist/index.js), then take
cli.js as its sibling in the same built dist/ directory — matching
package.json's bin.mosaic mapping. No change to the exports map itself
(that would mask a separate, out-of-scope pre-existing issue in
resolveTool()/launch.ts per review guidance).
Adds a positive-path test that stages a realistic fake dist/index.js +
dist/cli.js at the package's real resolved location and asserts
defaultCapabilityProbe() returns the actual {name, version} capability
object with zero mocking of the resolver. Verified red against the prior
(reverted-and-restored) buggy resolution before landing the fix, and green
after — the previous only-unmocked test asserted null for the wrong
reason (masking this bug) and is left in place alongside the new one.
Re-verified: launch.spec.ts (30), fail-closed-regression.spec.ts (2), and
runtime_tools_unittest.py (25, including both C-REGRESS-locked fail-closed
cases) all still green. typecheck/lint/format:check pass via
`turbo run ... --filter=@mosaicstack/mosaic`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Whitespace-only markdown table/spacing fix. Pre-existing on origin/main
(introduced by #868), unrelated to #869 C1 — fixed only because the repo's
pre-push hook runs a full-repo \`pnpm format:check\` and was blocking this
branch's push. No functional change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a real activation-capability probe so a downstream install-ordering
guard (C2, out of scope here) can refuse to wire lease-broker enforcement
(PreToolUse/Stop hooks) on a host that cannot actually activate it — the
root cause of #828's version-skew brick, where the published CLI tarball
lagged the enforcement reseed and every tool call denied with
GATE_UNAVAILABLE.
- LEASE_ACTIVATION_CAPABILITY {name, version}: versioned signal owned by
the activation half (execLeaseGatedRuntime), independent of npm semver.
- Hidden `mosaic __lease-capability` subcommand: prints that capability
from the actually-resolvable built CLI artifact, not source-tree
presence.
- leaseEnforcementActivatable(): pure predicate, true iff a compatible
capability is advertised AND the broker supervisor (launcher + daemon.py
artifacts, socket path) resolves. Detection only — never starts the
broker. Both inputs are injectable for testing.
- C-REGRESS: added a vitest spec that runs the two test-locked fail-closed
gate cases in runtime_tools_unittest.py directly, proving the gate's
fail-closed-on-absent-identity behavior is unchanged by this card.
Part of #869 (Point-1 C1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GLPI helpdesk workflow skills written against the portable
tools/glpi/ tooling (session-init.sh, ticket-list.sh, ticket-create.sh),
cross-linked via [[glpi-*]]:
- glpi-solve — close a ticket by setting status Solved (5); GLPI auto-closes
- glpi-followup — add a followup via the top-level /ITILFollowup endpoint
- glpi-sweep — read-only hunt for done-but-open tickets needing Solve
- glpi-list — query tickets by status/recency
- glpi-create — open a new ticket
Core rule encoded: completing work means setting status Solved, not just
posting a resolution followup (a followup documents; only Solved auto-closes).
Note: illustrative examples in the bodies are USC-flavored (M2M / helpdesk
ticket numbers) and can be genericized in review if preferred.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019GjBgrb9tHgvq414Fqj37c