Repairing the 55 broken skill bindings made the catalogue correct. This
makes it reachable, which it was not — for any skill, on any mission, since
the catalogue was built.
The skills had exactly ONE delivery channel: the `clawmates_skills` MCP
server. A mission claw could not reach it for three independent reasons:
1. `provision_claw` wrote the constant `["clawmates_door"]` and ignored
the template's mcp_bundles — which mission_orchestrator had already
resolved and stored on the team row.
2. The runtime config defines no `clawmates_skills` bundle. The live
local config defines no bundles at all, not even the door.
3. Mission claws run on `claude_cli`, which the runtime's own config
comments document as text-only: it cannot surface a tool call, so no
MCP server is reachable from a mission turn regardless of bundles.
And a mission turn's whole system context is two sentences synthesised from
the role slot in topology_exec::build_prompt. The template's role prose is
not used either — mission_orchestrator documents this, and it means the
role prompts describing which procedures to follow were never read.
Two doc comments in cm-runtime describe the mission path as already having
the summary-and-fetch contract. It never did. The belief was written down
twice and checked zero times, which is why nobody looked — and it is why
the Skill-Use measurement this review planned could only ever have returned
a trigger rate of zero. That would have read as a finding about the agents.
- provision_claw takes the bundles, with clawmates_door always added: a
template that forgets to list it must not get an ungated agent
- all 11 templates now request clawmates_skills; web_fetch removed, since
a list that is honoured must not name a bundle that does not exist
- the re-provision sweep re-asserts the team's own stored bundles rather
than a constant, which would have silently stripped a capability
mid-mission
- pinned skill BODIES are injected into the mission prompt, bounded and
with truncation stated. Bodies, not an index: there is no `skills.read`
tool on this path, so an index would advertise a capability that does
not exist — the exact failure this whole change is about
Three tests: the body reaches the prompt, an agent with no skills adds no
heading (an empty "Your skills" section announces skills the agent does not
have), and the composition is exercised separately from the lookup, because
`pinned_skills_text` working and `run_turn` calling it are different claims
and the second is the one that was false.
Also adds the three review documents: CAPABILITY-REVIEW (inventory, what
was repaired, what is deferred and why), PROVENANCE-ASSESSMENT (assess
only, per decision — what each store answers and the two candidate paths),
and RESEARCH-SWEEP (the fortnight's papers and what we did about each,
including the ones we deliberately did nothing about).
Full workspace suite green.
Co-Authored-By: Claude Opus 5 <[email protected]>
Four defects, found by checking the audit's claims instead of trusting
them. Two of the audit's own findings turned out to be wrong, and the
registry that exists to record which config keys are read was itself
inaccurate — so the corrections are part of the change.
upsert_task raised 42P10 on every call, for every caller
`mission_tasks_external_uniq` is a PARTIAL unique index (WHERE
external_id IS NOT NULL). Postgres will not match a partial index to an
ON CONFLICT target unless the statement repeats the predicate, so the
upsert failed on its first row. Both callers — the task-card parser that
turns INT markers into tasks, and the security scanner — map the error to
a string their caller logs. Two features were broken and nothing was red.
Regression test in cm-db with a negative control: reverting the WHERE
reproduces 42P10 exactly.
the security scan never ran
`security_scan::run` was reachable only from an operator button, so
security_hardening.toml — a workflow whose entire first phase is a scan —
ran an agent that was never told to scan and never fired the scanner
either. phase_runner now sweeps finished security_scan phases, mirroring
the benchmark baseline sweep that was added for the identical defect.
Guarded on a new completion marker rather than on findings: a clean scan
writes no findings, so a findings-guard would rescan forever. The marker
also answers the question an operator actually asks, which is not "how
many findings" but "was this looked at, by what, and when".
two recipes could not fail
security_hardening.toml and benchmark.toml carried no `task` and no
`done_when` on any phase. A phase without done_when never enters
evaluating, is never judged, and reports completed whatever it did — so a
security mission could scan nothing and go green, and a benchmark mission
could record no baseline that the next refactor would then compare
against. Both now state the work and the condition, with inert keys
annotated inline rather than deleted, so the gap between what a recipe
asks for and what a phase receives stays visible.
the config registry was wrong in both directions
`harness` was listed NOT IMPLEMENTED while benchmark_runner reads it and
phase_runner runs a baseline through it. `tools` was listed NOT
IMPLEMENTED while security_scan::run reads it. A registry that exists so
an operator can trust what a recipe does is worse than useless when it is
inaccurate. Both corrected, `bench_name` and `cmd` added, and
`test_command` deleted — it had neither a reader nor a writer, so it
described a situation that could not arise.
Also: CLAWMATES_JUDGE_MODEL had two different defaults (opus-4-8 in
routes/topology.rs vs opus-5 in cm_runtime::judge_model) and a doc comment
naming a third; topology now calls the one function. GITEA_TOKEN's absence
in mission_plan is stated rather than degrading to the same "could not be
read" string a private repo produces.
BRAINHUB_API_KEY needed no change — hub::push already rejects an unset key
with a named error. That half of the finding was overstated.
Co-Authored-By: Claude Opus 5 <[email protected]>
Every TopologyKind now runs, mapped to five execution patterns:
- hierarchical ← hub_spoke, star_moe, market
- pipeline ← ring
- swarm ← flat, holacratic
- mesh (new) ← blackboard (two peer-exchange rounds + aggregate)
- debate (new) (propose → critique → revise → judge)
execute()'s match is now exhaustive (adding a kind upstream forces an executor),
so the Unsupported error is gone. Benchmark spans all five distinct patterns.
14 tests with --features provider; clippy clean. Doc updated.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add the WorkClaw design-reference docs (specs, manifests, tokens, motion,
icon/asset references, gifs) under docs/, and move the platform
spec/PRD/roadmap from the repo root into docs/.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Last open item from the roadmap + post-1.0 list. Run against the live
Clerk instance closing-seasnail-39.clerk.accounts.dev.
- Backend (crates/cm-auth/tests/live_clerk.rs, CM_LIVE_CLERK=1): pulls
REAL discovery + JWKS from the live instance, mints a REAL session JWT
via Clerk's Backend API (create user -> open session -> session token),
and runs it through AuthService::authenticate — verify + JIT provision
(keyed on the real sub), duplicate-subject suppression, tamper
rejection against the live JWKS. Decodes the instance domain from the
publishable key; cleans up the test user after. PASSING
- Frontend: built with AUTH_MODE=clerk + real keys, next start serves
Clerk's <SignIn /> at /login wired to the instance (instance domain +
data-clerk attributes present in the HTML). Both halves confirmed
end to end against production Clerk
- docs/clerk.md: documented the smoke procedure for both halves
166 Rust tests (+6 live, key-gated). Keys used via env only, never
stored — rotate them (they passed through chat).
Co-Authored-By: Claude Fable 5 <[email protected]>
Full-depth rename per the approved plan; the 'claw' product vocabulary
(claws, /claws routes, clawId, Claw Chat) stays — it is now the brand.
- Display brand: Clawmates (manifest, titles, hero, login/rail logo
'clawmates'); default host app.clawmates.work; registry
ghcr.io/clawmates
- Crates tc-* -> cm-* (16 crates + all imports); binaries
clawmates-server/broker/bundler; images clawmates/*; env prefix
CLAWMATES_* (+ CM_TEST_DATABASE_URL / CM_LIVE_LLM); config
clawmates.toml; helm chart deploy/helm/clawmates with clawmates-*
resources; db names clawmates*; sockets /run/clawmates; cookie
cm_session; kind cluster clawmates-test; seccomp node profile
clawmates-agent-profile.json
- All 9 Playwright brand assertions updated in lockstep; historical
spec document left untouched as the only remaining 'TeamClaw'
- Local env migrated: dev pg clawmates-dev-pg/clawmates_dev, shared
test server clawmates-test-pg, kind cluster recreated with image +
profile, compose images rebuilt under clawmates/*
Verified end to end: 161 Rust + 68 frontend tests, 29 Playwright
journeys, 4 live kind tests, helm/install/LOC/placeholder gates, and
the clean-room install rehearsal serving the clawmates login page from
a signed bundle of the rebuilt images.
Co-Authored-By: Claude Fable 5 <[email protected]>
- src/lib/auth/bearer.ts is the single identity dispatch for both
server-side token consumers (RSC apiFetch and the /api proxy route):
local -> httpOnly tc_session cookie; clerk -> Clerk getToken() session
JWT. The Clerk SDK is imported lazily, so the air-gapped/local path
never loads it
- Runtime env (AUTH_MODE / CLERK_PUBLISHABLE_KEY / CLERK_SECRET_KEY),
deliberately NOT build-time NEXT_PUBLIC_*: the same standalone image
serves both deployment targets
- Conditional <ClerkProvider> in the root layout (publishableKey passed
at render from runtime env); /login renders Clerk's <SignIn /> in
clerk mode and the local form otherwise; proxy.ts middleware delegates
to clerkMiddleware() only when active
- Helm: frontend deployment injects the Clerk keys from a Secret when
auth.mode=clerk
- mode.ts unit-tested (default local, exact-match clerk, loud failure
without the publishable key); the local path stays proven by all 29
journeys; the Clerk branch is thin delegation to the SDK, exercised in
deployment smoke per docs/clerk.md
157 Rust + 68 frontend tests + 29 Playwright journeys.
Co-Authored-By: Claude Fable 5 <[email protected]>
- tc-auth JwtVerifier: OIDC discovery -> JWKS, RS256 with the issuer
pinned, 5s leeway (the crate's default 60s would double the life of
Clerk's 60s session tokens), key cache with one refresh on unknown kid
(Clerk rotates). Serves auth.mode = clerk AND generic oidc — a Clerk
instance IS an OIDC issuer, so one verifier covers both
- AuthService.authenticate dispatches: JWT-shaped bearers take the
hosted-identity path, everything else stays a local opaque session.
External users JIT-provision keyed by the stable sub claim
(users.auth_subject, unique partial index in migration 0007); an
existing local account with the same email is LINKED, not duplicated;
role tracks the issuer claim every request (org:admin -> Owner)
- Config auth.mode = "clerk" (requires issuer_url; validated), server
pins the issuer at boot, Helm values/configmap accept mode=clerk
- Tests with REAL crypto, no mocks: fresh RSA keypairs, a live local
issuer publishing real discovery + JWKS docs, Clerk-shaped tokens —
JIT + role mapping, repeat-subject no-dup, expired refused (leeway
regression), wrong-key forgery refused, foreign issuer refused, and
the full router round trip with Authorization: Bearer <session JWT>
- docs/clerk.md: dashboard session-token customization (email + org
role claims), config, @clerk/nextjs getToken() wiring, what CI proves
157 Rust + 63 frontend tests + 29 journeys. Air-gapped installs keep
local auth — Clerk is a cloud-only alternative, not a replacement.
Co-Authored-By: Claude Fable 5 <[email protected]>