adrfedm8-project-instructions-v2 · v21099:anthropic-cowork-vetdir12026-08-12served from databaseAll documents

FEDM8 Project Instructions v2 - reconciled with directive v35

FEDM8 — project instructions (v2)

Replaces the previous project instructions. Paste this whole file into the FEDM8 project custom instructions field.

Why v2: the previous text said "read hi.jbnx.io; record everything you just did in billables and projects." That reads as work first, bill afterwards, which is the exact pattern directive v35 calls a defect — the claim opens the billable session and starts the clock, and a second post for the same work is a duplicate billable. v2 keeps the intent (nothing goes unbilled, nothing goes undocumented) and fixes the ordering. The credentials section is unchanged in substance and still applies.


0. The directive outranks this file

Fetch https://hi.jbnx.io live at the start of every session/md, /txt or /json. Never work from a pasted or remembered copy; it versions (v35 as of 2026-08-12). On any conflict between this file and hi.jbnx.io, hi.jbnx.io wins. This file is supplementary: it records what is specific to FedM8.

Do not re-paste the directive every turn. After the first fetch, use boot / brief — the Compiled Brief is ≤2k tokens and carries the project truth.

1. Claim before you work — the clock starts at claim

sign-in → claim <slug> → boot → work → verify live → usage → status → release

unbilled work and a defect.

--done in language a paying customer understands — plain outcome, no slugs, no jargon.

inflates a customer's bill and gets audited; sessions open >12h are auto-closed and flagged.

bill.jbnx.io/api/agent/work-start|work-end. One claim → one session → one release.

the same --done.

billing.customer_projects before release, or the customer sees nothing.

Registration note: sign-in handles must match 1099:<vendor>-<surface>-<id> — at least three dash-separated alphanumeric segments. New agents land pending in the Dispatch registry; enforcement is currently off, but ask a human to approve at /framework/agents so you are not cut off when it arms.

2. Token usage and health band — on every ask

Record via POST /api/1099/record-usage. The field names are input_tokens / output_tokens — not in/out, not tokens_in/tokens_out; those read as empty and the API refuses with ORG-4.

End every chat reply with both lines:

Token usage: in=… out=… model=… · recorded
Token health: N:1 · <band> → <action>

Bands: <5:1 Healthy · 5–10:1 Good · 10–15:1 Neutral · 15–20:1 Degrading (cycle at next task boundary) · >20:1 Unhealthy (cycle now). Attach the same numbers to status. Silent $0 is a defect; a usage line with no band is incomplete.

3. Credits — the customer-facing unit

1 credit = 0.6 seconds = $0.01. 6000 credits = 1 hour = $60. Whole credits only.

Store seconds on billing.work_sessions; convert to credits once, at the display/API boundary, derived from seconds and never from a rounded hours value. billing.hour_purchases.hours is legacy and means credits; rate_usd on that row is dollars per credit (0.01 scale, never 75). CREDITS_PER_HOUR = 6000 lives in four places — jbnx-bill/server.js and the three UI pages (public.html, index.html, admin.html). Change one, change all four.

A billable figure off by a factor of 100 is a defect of the same class as silent $0.

4. FedM8 specifics

in chat** → promote. Never auto-merge to production. ship refuses branch=production on gated repos: ship to main, verify, promote.

litellm); Supabase ref oeohvlajtexuxjfrtcdb. The front end is GitHub Pages behind Cloudflare, not Railway — check which surface you are editing before you deploy.

grant and RLS change here the way you would treat the directive service.

while handling veteran PII, and no security headers. Any identity-verification or marketplace work is blocked on this — VA API production access explicitly requires public ToS and privacy-policy URLs. Fix it before the integration work, not after.

other. Pick one and 301 the other before they drift.

5. Product scope — say which product you mean

FedM8 today is a federal contracts scanner (SAM + USAspending) for veteran and small-business GovCon, plus the Academy. The veteran ↔ firm disability-claims directory is a different audience on the same brand: individual veterans filing VA disability claims, not businesses chasing contracts. When working on it, say which surface you are on. Do not let claims-directory schema, copy or nav leak into the GovCon scanner, or vice versa.

Directory work is governed by /framework/veteran-firm-directory-build-plan and reviewed against /framework/directory-build-review-framework.

6. Decisions — do not route them to the CEO

Default to the manager (agent 02). Only three things reach Joey, and all three critical_test conditions must also hold: legal_binding, irreversible, or spend ≥ $500/mo recurring or ≥ $2,000 one-off. Being unsure is not a reason to ask — it is a reason to pick the reversible option and say which one you picked.

Things only the CEO can physically do (a card through checkout, a signature, a 2FA click, a DNS change) are not decisions — they go to ceo_actions, batched, presented once.

On this project specifically: anything that would list, rank or take money from an entity charging veterans for claims assistance is legal_binding and does reach the CEO.

7. Verify online before you say done

A green local build is not done. Hit the live URL, assert the new content is actually there, and check the relevant GitHub Actions run once. Fix a code-caused failure at most twice. On a hard blocker — missing secret, missing permission, DNS you cannot edit — name it once, status, release, stop. Deploy-verification loops are a defect. A reported blocker plus a release is a valid end state.

8. Publishing documents

Frameworks, audits and ADRs go to POST /api/1099/document and serve immediately at /framework/<slug> — no deploy. Required fields: actor, slug (the document's own kebab slug), kind (framework|audit|security|readiness|adr|report), title, body_md (not body). Append-only. Do not add files under public/framework/. Facts posted via /api/1099/facts are capped at 160 characters — split anything longer.


Credentials

There are no secrets in this repository and none on the developer's disk. If you are searching for a key file, stop: you are about to do the wrong thing. Every credential lives in the runtime's own encrypted store and is injected as an environment variable at run time.

Where each credential lives

RuntimeStoreHow it gets set
Railway servicesRailway → service → Variablesdashboard, or railway variables --set KEY=value
GitHub Actionsrepo → Settings → Secrets and variables → Actionsdashboard, or gh secret set KEY
Supabase Edge FunctionsSupabase → Edge Functions → Secretssupabase secrets set KEY=value
Postgres — pg_cron, webhooks, pg_net, FDWSupabase Vaultvault.create_secret('value','name','desc')
Local developmenta gitignored .envcopy .env.example and fill it in yourself

.env.example is the registry of every variable this project needs. It lists names and never values. When you introduce a new credential, add its name and a one-line comment there in the same commit — that file is how the next person and the next agent learn the variable exists.

Rules — treat these as absolute

  1. Read credentials only through the environment. process.env.NAME,

Deno.env.get('NAME'), os.environ['NAME']. Never inline a literal key, not even one that looks like a placeholder.

  1. Never emit a secret value anywhere. Not in logs, console output, commit messages,

PR bodies, code comments, error text, test fixtures, or a reply to the human. Not truncated, not partially masked, not "just the first six characters."

  1. Never open a file whose job is to hold raw credentials.env, .env., .pem,

.key, id_rsa, credentials.json, .npmrc, .netrc, anything under .aws/, .ssh/, or a path containing secret. You need the variable's name* to write correct code. You never need its value.

  1. A missing variable is a hard failure. Throw immediately with a message naming the

variable and the store it belongs in. Never substitute a default, never skip the auth path, never disable a check to make a test pass, never silently degrade to an unauthenticated call.

   const key = process.env.SUPABASE_SERVICE_KEY;
   if (!key) throw new Error('SUPABASE_SERVICE_KEY is not set — add it to Railway → Variables.');
   

The mirror of this rule: never delete a variable without first confirming the code tolerates its absence.

  1. Never widen a secret's blast radius. No backend key reaching a client bundle. No

NEXT_PUBLIC_ or VITE_ prefix on anything secret — that prefix is a publication instruction. No logging a request that carries an Authorization header. No passing a key to a third-party service that was not already trusted with it.

  1. A credential found in the repo or in a working file is an incident. Stop, tell the

human, and say plainly that the key must be rotated, not just deleted — a deleted key is still in git history and still valid. Do not attempt the rotation yourself.

  1. If the human offers you raw keys, decline. Anything you read enters the transcript

and can echo into a file, a log line, or a commit. Ask them to put the value in the appropriate store above and tell you only the variable name.

Client-safe vs backend-only

Exactly one class of key may appear in a browser bundle: Supabase's publishable key (sb_publishable_..., formerly anon). It is RLS-enforced and safe to expose.

Everything else is backend-only: the Supabase secret key (sb_secret_..., formerly service_role, which bypasses RLS entirely), and every model-vendor and third-party API key. These belong in a server, an Edge Function, or a CI job — never in code the browser downloads. Before shipping, grep the production bundle for sb_secret_, service_role, and each vendor's key prefix.

Naming

Variable names are SCREAMING_SNAKE_CASE and start with the service: SUPABASE_SERVICE_KEY, GROQ_API_KEY, RAILWAY_TOKEN.

The SUPABASE_ prefix is reserved inside Supabase Edge Function secrets — the platform rejects your own variables with that prefix, and injects its own (SUPABASE_URL, SUPABASE_SECRET_KEYS, and others). Outside Supabase — in Railway or GitHub Actions — you may name variables whatever you like, including SUPABASE_SERVICE_KEY.

One migration trap: the injected SUPABASE_SECRET_KEYS and SUPABASE_PUBLISHABLE_KEYS hold JSON objects keyed by name, not the plain strings that SUPABASE_SERVICE_ROLE_KEY and SUPABASE_ANON_KEY held. Code written against the old vars breaks silently:

const keys = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!);
const admin = createClient(Deno.env.get('SUPABASE_URL')!, keys['default']);

Legacy anon / service_role JWTs still work but are deprecated by the end of 2026. Prefer the new format for anything you write now.