Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / docs/BUILD_STATE.md |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | docs/BUILD_STATE.md |
| Original SHA-256 | 1e1791efb470ff40cad92e21af17b214249707f7eac7bfe7c312bc61928786c9 |
| Git blob SHA | c6ac3124f2a9dd992cf8cfb5385ee2e0f8953441 |
| Reading-copy SHA-256 | d0053ec3bd238ebc75a6a92703e9ac87c9dd6aa8db7b97c994f39a8858d4fd1d |
Recovered source transcript
Download complete source text ↓3,723 lines · 322.2 KB
Credential-bearing and unrelated personal details were removed from this reading copy. The source identity and original checksum remain recorded.
# missionMATCH BUILD_STATE
================================================================================
STANDING REFERENCE. The CURRENT handoff is the SESSION HANDOFF section below.
================================================================================
THE TOP OF THIS FILE USED TO CARRY A SECOND, OLDER HANDOFF and it went stale: it still said Gate
C fails and Gate A waits on one blocker, both of which were superseded the same day. Two handoffs
in one ledger is one handoff too many, and the stale one is the one a fresh session reads first.
Removed 29 July 2026. What remains here is DURABLE reference that does not change per session.
PRODUCTION IS TWO DATABASES. Reading this wrong caused an outage.
CONTROL PLANE, authoritative identity, what MM_DATABASE_URL points at:
missionmatch on megamind-agi 100.77.114.40 PORT 5433, v80.
ANSWER EVERY IDENTITY QUESTION HERE.
TENANT DATA PLANE, only via tenant_directory:
missionmatch on M4 100.94.8.94 port 5432, v80. Its users copy is NON
AUTHORITATIVE; an hourly sync keeps it honest.
STAGING: missionmatch_staging on M4 5432, v81, served by mm-api-staging on
Bubbles 127.0.0.1:8097, vhost missionmatch-staging.thatwebhostingguy.com.
STAGING IS ONE AHEAD ON PURPOSE: 0081 is staging only pending a deploy.
Both production nodes run 8f24414531e80a9a. Bundle index-BE5iWWvj.js.
ACCESS, all verified this session
Bubbles ssh user@100.90.97.104 pw user, sudo via echo user | sudo -S
M4 ssh joseph@100.94.8.94 psql /opt/homebrew/bin/psql,
local trust as -U postgres
megamind-agi NO direct SSH. ssh -o PubkeyAuthentication=no
-o PreferredAuthentications=password josep@100.69.120.61, then
scp a script and run: wsl -- bash /mnt/c/Users/josep/<script>.sh
MULTI LINE QUOTING THROUGH wsl BREAKS. Always stage a file.
PASSWORD REDACTED 28 July 2026, finding D4. It is in the secrets
store, never here. See the standing rule below.
psql locally /opt/homebrew/opt/libpq/bin/psql, not on PATH by default.
Staging test accounts: patriciacox6@aol.com and desk.admin@fg-talent.test.
PASSWORDS REDACTED 28 July 2026, finding D4, and both were
ROTATED before redaction because they were already burned. The
replacements are in the secrets store. Redacting without rotating
would have been theatre: the old values are in git history and in
a chat transcript, and a redaction does not un publish them.
HOW TO DEPLOY, and why production is not repository HEAD
HEAD carries Phase 1 and 2 build features that stay DARK until Gate 6. Build in
a worktree at e74fd2e, overlay ONLY the approved track's files, hand resolve
cmd/api/main.go, take AppShell.tsx from the BASELINE, then GREP THE ARTIFACTS.
Both nodes get the identical binary or failover serves two answers.
Frontend: rewrite ONLY the two asset hashes; the line count must not change.
Env files: sudo tee TRUNCATES. Stage to /tmp then cp.
Post deploy: re prove build routes dark on the UNAUTHENTICATED AND the
AUTHENTICATED leg. An unauthenticated only sweep cannot see a defect behind the
session gate, which is how a 500 was missed.
THE INSTRUMENTS, all new this session, all with positive controls
tools/verify/stress race, load, soak. -control plants a defect and the
profile MUST fail on it before a counted run.
tools/verify/visual-sweep logs in as real roles, 5 viewports, contact sheet.
--control plants a 4000px overflow.
/usr/local/sbin/mm-restore-drill.sh run at EVERY phase close.
Timers: 02:45 cp backup, 03:05 db backup, 5min watch, hourly identity sync.
CANDIDATE INTAKE, WHAT EXISTS AND THE ONE DECISION IT NEEDS, 30 July 2026. Joseph asked for candidate import
that also PULLS FROM DATABASES rather than only ingesting files. Researched before building; most of it exists.
WHAT IS ALREADY BUILT, and it is more than expected
connector.Connector is an interface: Name() and FetchPage(ctx, since, page) returning people, a next page
cursor, and the max updated timestamp seen.
connector.Sync is a REAL INCREMENTAL SYNC FRAMEWORK, not a stub. It reads the org's watermark from
connector_sync_state, pages from it, ingests each page through the proven sourcing path with a
sourcing_log provenance row, and advances the watermark PER PAGE in one atomic transaction. Network I/O
runs OUTSIDE the transaction so a slow fetch never holds one open. A 429 maps to ErrThrottled and stops
the run gracefully, keeping the pages already committed. No PII is logged.
benchimport carries a FileSource interface with TWO implementations already: a resume source that extracts
from documents, and a structured source for tabular records.
ONE CONNECTOR EXISTS: iCIMS, an applicant tracking system.
SO THE GAP IS NOT THE MACHINERY. It is two things: no HTTP route reaches any of it, and there is no second
source to pull from.
THE DECISION, AND IT IS A SECURITY DECISION RATHER THAN A DESIGN PREFERENCE.
iCIMS credentials come from PROCESS ENVIRONMENT VARIABLES: ICIMS_CUSTOMER_ID, ICIMS_CLIENT_ID,
ICIMS_CLIENT_SECRET. That means ONE DEPLOYMENT CAN SERVE EXACTLY ONE iCIMS ACCOUNT. It works for a single
pilot tenant and it cannot serve customers, because the second customer's credentials have nowhere to live.
connector_sync_state holds watermarks, not configuration and not secrets.
PULLING FROM A CUSTOMER'S DATABASE MAKES THIS SHARPER, not softer. A database source needs a DSN, which is a
live credential to a system this platform does not own, stored per tenant. Three sub questions come with it
and each is a real exposure rather than a detail:
WHERE THE SECRET LIVES. The precedent in this codebase is good and should be followed: TOTP secrets are
AES 256 GCM encrypted at rest with a key_version so the KEK can rotate. A connector_configs table with
the same shape is the honest answer, not a plaintext column and not an env var.
WHAT THE CONNECTION MAY REACH. An operator supplied DSN is a request the SERVER makes to an arbitrary
host, which is server side request forgery with a friendly name. It needs an explicit egress posture:
an allowlist, or at minimum a refusal of loopback and private ranges, decided deliberately rather than
inherited by accident.
WHAT THE QUERY MAY DO. A customer supplied query must run READ ONLY with a statement timeout, in a
transaction that cannot write, because "SELECT the candidates" and "DROP the candidates" differ by one
word in a text box.
THE OPTIONS, stated as a choice rather than a recommendation dressed as a finding:
A. Per org connector_configs with AES 256 GCM at rest, mirroring the TOTP pattern. Multi tenant, and the
most work.
B. Keep credentials in the environment and support ONE source per deployment. Cheapest, and it cannot
onboard a second customer onto the same connector.
C. Resolve credentials through the existing secrets.Resolver backend abstraction, which already has a
cache and a pluggable backend, so a cloud secret manager becomes configuration rather than code.
I recommend A for the database and ATS sources, with C as the deployment time option for platform wide
credentials. B is what exists and it does not survive a second customer.
WHAT CAN BE BUILT WITHOUT THAT DECISION, and should be, because it needs no new credential surface at all:
A STRUCTURED CANDIDATE IMPORT ROUTE. benchimport's structured source already parses tabular records into
BenchRecord and ToSourcedPerson feeds the same proven ingest path connector.Sync uses. An upload that
STREAMS and parses in memory needs no object storage, which matters because MM_STORAGE_ROOT is unset in
production and document intake is therefore dark. So a customer could load a bench today, from the product,
with no new secret anywhere.
RESUME upload is the half that DOES need storage, and it is blocked behind the same disk decision as
document intake, on a root disk at 82 percent whose backups share it.
NOTHING WAS BUILT FOR THIS YET. Recorded so the decision is made deliberately rather than discovered halfway
through a connector that is already holding a customer's database password. | this commit | 2026-07-30
PHASE 3 WORK PLAN, RESEARCHED PER ITEM, 30 July 2026. Joseph asked for each numbered item researched for what
it actually needs, then the list worked down, then a full application test round. Every "exists" or "absent"
below is a query result or a grep, not a recollection.
ITEM 1, PROPOSAL MIGRATIONS. DONE. Migration 0086 in all three databases at v86: 11 tables, 11 forced RLS, 6
slate lifecycle columns, composite tenant keys throughout, no DELETE grants, the approved_count invariant in
the schema, and mm_mirror_staging_role_privileges() so the 0085 class of gap cannot recur.
ITEM 3, THE READINESS ENGINE. NEXT, and first because everything downstream reads it.
internal/modules/proposals/engine, pure Go, injected inputs, no database, no I/O, READINESS_ALGO_VERSION.
Weights 0.30 staffing coverage, 0.25 compliance matrix, 0.20 required sections, 0.10 review and approval,
0.10 schedule health, 0.05 risk posture. Sums to 1.00, asserted by a test rather than by arithmetic in a
comment.
EIGHT HARD BLOCKERS, from Section 7: customer deadline passed, required compliance item open, required
section missing, required staffing quantity uncovered, final approval missing, unreviewed amendment affecting
scope, required attachment missing, submission validation failed.
DETERMINISM IS THE GATE CLAUSE AND IT HAS THREE KNOWN FAILURE MODES, all quiet: a float64 arriving through
JSON unmarshalling into interface{}, GO'S RANDOMISED MAP ITERATION anywhere in the scoring path, and
unstable tie breaking when two drivers score equal. Mitigations are decided in advance: decimal everywhere,
NO map iteration feeds a score (inputs arrive as slices and any map is sorted before use), and every ordering
carries a total order tiebreaker. A test runs the same input 100 times and asserts byte identical output.
It shares NOT ONE LINE with the candidate engine, per Section 7, while mirroring its cap philosophy.
ITEM 7, THE SLATE APPROVAL LIFECYCLE AND THE EIGHT WAY RACE. Second, because it is the hardest thing in the
phase and a concurrency design validated last is validated by hope.
Schema is ready. What remains is the transaction: approve and withdraw each take FOR UPDATE on the member
row, adjust proposal_staffing_links.approved_count, and audit through OnTx.
THE REAL DIFFICULTY IS LOCK ORDERING, not the counter. Eight transactions touching a member row AND the
links row deadlock unless they always acquire in the same order. Decision: ALWAYS the links row first, then
the member row, because the links row is the contended one and taking it first serialises the counter update
without holding a member lock while waiting.
The harness runs eight concurrent workers doing approve, withdraw and re approve, then asserts the final
count. The schema CHECK turns any lost update into an error rather than a flattering number.
ITEM 5, SUBMISSION WITH SERVER SIDE HARD BLOCKER ENFORCEMENT. The check lives in the SERVICE, never the
handler, so a future route cannot bypass it, and a test asserts no submission path skips it. Gate 3 is
explicit that the API refuses, not merely the UI.
ITEM 8 AND 9, PRICING AND RECORDED WAGE FLOORS, from the 30 July amendment. A pure margin function on decimal:
bill minus pay minus burden. AN UNBURDENED LINE RETURNS NO MARGIN AT ALL, which is gate clause A, and the
API omits the field with a stated reason rather than sending a spread that reads as margin. A line priced
below its recorded floor is a hard blocker, which folds pricing into item 5 rather than beside it.
ITEM 6, OUTCOME RECORDING THROUGH bidoutcomes. The module EXISTS, 477 lines with a Routes function, and
MM_BID_OUTCOMES_ENABLED is UNSET in both environments so the recording routes are not mounted and the ledger
ships dormant. This item is mostly wiring plus the flag, not new machinery.
ITEM 4, THE TWO COMMANDS. cmd/proposal-readiness-snapshot for scheduled persistence and
cmd/proposal-deadline-alerts sweeping deadlines into alerts, plus cmd/stale-requirement-scan. The alerts
module EXISTS at 930 lines, so deadline alerts extend it. Every binary follows the sam-sync pattern:
idempotent, bounded, observable, audited, provenance aware, safe to rerun.
ITEM 2, THE WORKSPACE TABS, Section 6. TEN tabs: Overview, Scope, Requirements, Compliance Matrix, Sections,
Staffing, Milestones, Risks, Attachments, Activity. Overview alone carries opportunity summary, both
deadlines, owners, stage, readiness, hard blockers, scope changes, staffing coverage, compliance completion,
section completion, review milestones, risks and recent activity. Every tile clickable through to its
filtered list. Reuse AppShell, ToolGuard, lib/queries. Activity is fed from the append only audit trail.
LAST, deliberately: a tab that renders a number the engine has not settled is a tab built twice.
ROUTES, Section 9: POST /api/proposals, GET and PATCH /api/proposals/{id}, GET /{id}/readiness,
POST /{id}/readiness/snapshot, POST /{id}/submit, POST /{id}/outcome, plus CRUD for sections, compliance
items, milestones, risks, staffing links and attachments.
CAPABILITIES: proposal.manage at recruiter and above, proposal.submit at manager and above. The other 22
exist; these two do not and the exhaustively tested matrix must be updated with them.
EVERYTHING IS BUILT IN PRODUCTION per the rail amendment, with rollback before each change, artifacts hash
verified after transit, and controls run before conclusions. | this commit | 2026-07-30
RECORDED SCOPE AMENDMENT, PHASE 3 GAINS PRICING, AND GATE 3 GAINS FOUR CLAUSES. 30 July 2026, Joseph's
decision after he challenged my claim that the gate was the obstacle. He was right and the claim was wrong: the
gate is a specification we write, not a constraint we inherit, so a phase that gains scope gains gate clauses.
PHASE 3 IS NOW: PROPOSAL OPERATIONS, INCLUDING PRICING. The directive's seven items stand unchanged, plus:
8. PROPOSAL PRICING. A staffing line on a proposal carries a labor category, a pay rate, a BURDEN, and a bill
rate. Margin is COMPUTED, never stored, by a pure function on shopspring/decimal, because a stored margin
drifts from its inputs and the whole point is that the number can be re derived.
9. RECORDED WAGE FLOORS. A table of wage determination floors per labor category and locality, entered by a
named human with the determination number, revision, and effective date. NOT extracted from the document,
and the reason is finding one: the SCA wage determination's text layer carries no usable character map, so
the floor cannot be read from it and OCR is deferred as candidate P2. A floor a human recorded is honest.
A floor guessed from an unreadable PDF would not be.
GATE 3 GAINS FOUR CLAUSES, and they are written to slot into the gate rather than sit beside it:
A. A BILL RATE WITH NO BURDEN PRODUCES NO MARGIN AT ALL. Not a spread labelled as margin, not a zero, not a
null quietly rendered as a dash. The API refuses to return a margin field for an unburdened line and says
why. This is tonight's most expensive lesson made into a gate clause: the product could show bill minus
pay and call it margin, and that number would be wrong in the direction that loses money.
B. THE SAME INPUTS PRODUCE BIT IDENTICAL MARGIN ACROSS RUNS, decimal throughout, no float64 anywhere in the
path, proven the same way readiness is proven.
C. A STAFFING LINE PRICED BELOW ITS RECORDED WAGE FLOOR IS A HARD BLOCKER ON SUBMISSION, ENFORCED SERVER
SIDE. This is why pricing belongs in this phase rather than beside it: it does not extend the gate, it
makes the gate's central clause mean more. An underpriced SCA position is exactly the class of thing that
must stop a submission at the API and not merely grey out a button.
D. WRAP RATE RESOLUTION IS RECORDED ON THE PRICED LINE with its source and version, so a margin can be re
derived months later and an assessor can see which burden assumption produced which bill rate.
WHAT IS DELIBERATELY NOT IN THIS PHASE, so the amendment does not become a bucket the way my three misfilings
did:
THE WAGE BRIDGE, D22, STAYS OUT. Canonical SOC form, ontology_crosswalk, and skills.soc_code are MARKET
COMPARISON data. Phase 3 needs a floor a human recorded, not a market band, and dragging a bridge that is
broken in three ways into a phase that does not need it is how a phase stops closing.
THE SELF EXTENDING KNOWLEDGE STORE STAYS OUT. Unphased and visible.
OCR STAYS OUT. Candidate P2, and clause C is written to not need it.
THE HARD PARTS, NAMED IN ADVANCE so the plan is honest about where the time goes. Only two of the gate's
clauses are genuinely difficult and neither is pricing:
THE EIGHT WAY RACE ON approved_count. The count moves both up AND down, since withdrawal decrements and re
approval after withdrawal is allowed, so a member can cycle. The difficulty is not the counter, it is LOCK
ORDERING: eight transactions each taking FOR UPDATE on a member row and touching proposal_staffing_links will
deadlock unless they always acquire in the same order. And the gate says PROVEN BY RACING IT, so a harness
that runs eight concurrent workers and asserts the final count, not an argument that the code reads correctly.
I am building that harness FIRST, not last, because a concurrency design validated at the end is a
concurrency design validated by hope.
BIT IDENTICAL READINESS. Three ways it breaks and all three are quiet: a float64 arriving through JSON
unmarshalling into interface{}, GO'S RANDOMISED MAP ITERATION anywhere in the scoring path, and unstable tie
breaking when two drivers score equal. The frozen engine already solved this with decimal and total order
tiebreakers; readiness has MORE inputs and must inherit the same discipline.
EVERYTHING IS BUILT IN PRODUCTION, per the rail amendment recorded above this entry. No side builds, no
sandbox. Rollback before each change, artifacts hash verified after transit, controls run before conclusions.
| this commit | 2026-07-30
CORRECTION, I ENLARGED PHASE 3 THREE TIMES WITHOUT CHECKING WHAT PHASE 3 IS, 30 July 2026. Found while
preparing the Phase 3 breakdown Joseph asked for.
THE DIRECTIVE'S PHASE 3 IS "PROPOSAL OPERATIONS", Section at line 550, and it is seven numbered items with its
own Gate 3: proposal migrations from Section 3.7, the workspace tabs from Section 6, the readiness engine from
Section 7 with snapshots and cmd/proposal-readiness-snapshot, cmd/proposal-deadline-alerts and
cmd/stale-requirement-scan, a submission workflow with SERVER SIDE hard blocker enforcement, outcome recording
through bidoutcomes, and the slate approval lifecycle merged from consolidated amendment 2.3.
I RECORDED THREE THINGS AS "PHASE 3 SCOPED WORK" TONIGHT AND NOT ONE OF THEM IS IN IT:
the wrap rate and burden model
the wage bridge, D22
the self extending knowledge store
Each entry reasoned carefully about why the item did not belong in PHASE 2, which was correct, and then
assigned it to Phase 3 as though Phase 3 were a bucket for whatever is next. I never opened the directive to
check. THAT IS THE EXACT DRIFT THIS PROTOCOL EXISTS TO PREVENT, and I spent the night enforcing it on
everything else while doing it myself three times in the ledger.
WHAT IS ACTUALLY TRUE ABOUT THOSE THREE ITEMS: they are real, they are recorded with measurements, and they
have no phase. Saying they have no phase is more useful than assigning them one, because an unphased item is
visible while a misfiled one is not.
THE ONE THING THAT WAS CORRECTLY ASSIGNED: the office format reader holds Phase 3's single IMPROVEMENT slot,
which is the licensed channel for one unscoped item per phase, and that is what an improvement slot is for.
The other three are not improvements, they are scope, and scope needs a recorded amendment rather than a slot.
WHAT NEEDS JOSEPH'S DECISION, stated as a choice rather than a recommendation dressed as a finding:
A. Run Phase 3 as the directive defines it. The three items wait, unphased and visible.
B. Amend Phase 3 to include the wrap rate, on the argument that proposal operations without pricing is half a
proposal. That is a defensible reading and it is an AMENDMENT, recorded as one, not an assumption.
C. Insert a phase before Phase 3 for the money and knowledge substrate.
I recommend A or B and I lean B only if he wants proposal pricing in this phase, because Gate 3 as written does
not test pricing at all and a phase whose gate cannot see half its scope is a phase that closes on partial
evidence. | this commit | 2026-07-30
RECORDED DECISION, THE PRODUCTION RAIL IS AMENDED, 30 July 2026. Joseph: "push it into production. do not
build something outside production again it doubles the work."
WHAT CHANGED. The standing rail read "production scope closed outside recorded approvals", and the phase
protocol built and proved everything in staging while production stayed deliberately untouched. That is now
amended: BUILD IN PRODUCTION. The doubling he is describing is real and I should have named it first. Phase 2
was built and gated entirely against staging, then had to be deployed and re verified in production afterwards,
which meant every artifact was staged twice, every credential path exercised twice, and the Gate C fixture
accounts existed only on one side. Tonight's deploy also proved the two environments are NOT equivalent: the
staging role parity defect in migration 0085 existed precisely because staging has its own roles that no
migration names.
WHAT DOES NOT CHANGE, and this is not hedging, it is the part that makes the amendment safe:
ROLLBACK BEFORE THE FIRST CHANGE. A copy of every artifact, and a database dump on disk, before anything is
touched. Tonight that path was used twice and it worked twice.
AN ARTIFACT IS HASH VERIFIED AFTER TRANSIT AND BEFORE A SERVICE START. Tonight a binary was corrupted in
transit and segfaulted production's second node. The assertion now refuses a mismatch outright, because care
is not a control and a hash is.
HEALTH IS CHECKED AND THE CHECK IS CHECKED. A bad health URL caused a false rollback tonight. A rollback
fired by a broken check is cheap; a deploy passed by a broken check is not.
CONTROLS STILL RUN FIRST. The post deploy verification asserts that routes which SHOULD answer do answer,
before concluding anything from a route that does not.
THE ENGINE STAYS FROZEN, RLS STAYS FORCED, PROVENANCE STAYS BORN AT ORIGIN, AND MIGRATIONS REMAIN THE ONLY
WAY TO CHANGE SCHEMA. Building in production changes WHERE work is proven, not what is permitted.
THE HONEST COST OF THE AMENDMENT, stated once so it is not rediscovered as a surprise. Building in production
means a defect's first audience can be a customer rather than a gate. The phase protocol's staging first order
existed to buy exactly that separation, and giving it up trades a slower build for a faster one with a shorter
blast radius between a mistake and a user. Joseph owns that trade and has made it. The mitigation is the list
above, and the one thing I will keep insisting on is that a production change still carries its rollback and
its controls, because those are what make a fast deploy survivable rather than merely fast. | this commit | 2026-07-30
## PER ORGANIZATION SETTINGS, SEALED SECRETS, FIELD SENSITIVITY AND WORK VISIBILITY, 30 July 2026
Migration 0087 is live in ALL THREE databases at v87, verified per database rather than summarized: control
plane 100.77.114.40:5433, tenant 100.94.8.94:5432, staging missionmatch_staging. Four new tables each with
FORCE ROW LEVEL SECURITY, four policies, and ZERO policies naming a role, which is the 0085 and 0086 lesson
held. Backfill covered every organization: 1 of 1 on each production database, 4855 of 4855 on staging.
Staging app grants mirrored 13 of 13 against the production app role, and the positive control confirmed the
staging app role still has NO CONNECT to the production database.
WHAT IT ANSWERS. Three asks landed on one missing thing, a place where a tenant states how it works and a
place where it stores a password. Invites had to be hand delivered, recruiters were double working the same
candidate, and rates were visible to everyone.
DECISIONS RECORDED.
- Secrets are AES 256 GCM under MM_ORG_SECRET_KEK with a key_version, MIRRORING the mfa_totp pattern from
migration 0009 rather than inventing a second meaning for encrypted at rest. A SEPARATE key from the TOTP
KEK, because one key covering both populations means one disclosure covers both.
- BOTH PRODUCTION NODES HOLD THE IDENTICAL KEY, proven by comparing hashes of the value, never the value. A
secret sealed on one node must open on the other. STAGING HOLDS A DIFFERENT KEY, proven to differ, so a
staging disclosure is not a production disclosure.
- Mail sends through the CUSTOMER'S OWN server from the CUSTOMER'S OWN address, so an invitation passes their
SPF and DKIM and does not arrive from a domain their staff was trained to distrust.
- WHEN DELIVERY SUCCEEDS THE TOKEN IS WITHHELD from the response. It has reached the intended person; a
second copy in an administrator's browser is a live credential with no purpose.
- IT DEGRADES TO THE PILOT BEHAVIOUR ALWAYS. No mail configured, or a mail server that refuses, returns the
one time link exactly as before. A mail outage never costs an account creation.
- There is NO CLEARTEXT MAIL OPTION at any port. What this transport carries is an activation credential and
a person's name.
- The test endpoint sends ONLY to the administrator's own address. One that accepted a recipient would be an
open relay wearing a settings screen.
- Settings MAY NOT WEAKEN A COMPLIANCE GUARANTEE, which is why the table is typed columns and not a key value
bag. Human approval of every slate member, adverse impact reporting, the audit trail and the fixed NIST
session timeouts are NOT settings and must never be added here.
- Defaults are the INDUSTRY ones: rates at managers, margin at owner, contacts at team, work visibility at
organization so the double work control ships ON.
THE SERVER SIDE REQUEST FORGERY BOUND, because a customer supplying a mail host is asking this server to
connect somewhere. The destination is resolved and checked and then the CHECKED ADDRESS is dialled, not the
name, which closes the rebinding window. Loopback, private ranges, link local, the cloud metadata address,
carrier grade NAT where this operator's own machines live, and IPv6 unique local are all refused. Proven live
against 127.0.0.1, 169.254.169.254 and 100.94.8.94, each refused with 502.
ONE REAL DEFECT FOUND AND FIXED BY THE WALKTHROUGH. Save was a single INSERT ... ON CONFLICT DO UPDATE. An
organization with no settings row took the INSERT branch, where the SET clause does not apply, so the FIRST
save an owner ever performed returned 200 and changed nothing. provision_org creates no settings row, so
EVERY newly provisioned customer hit it. MarkVerified and MarkAttemptFailed had the same class of bug, a
plain UPDATE against a missing row affecting zero rows and reporting success. Fixed with an ensureRow that
every writer calls. A structural regression test pins it and was PROVEN TO FAIL against the broken shape
before it was allowed to pass.
VERIFIED: 50 of 50 walkthrough assertions against staging, including the controls that matter. The stored
password never appears in any response, the scrubbed transport error carries no credential, a recruiter
cannot read the settings surface at all, a recruiter cannot widen their own access, and narrowing
work_visibility to private changes the recruiter's answer while leaving the owner's intact. 57 Go packages
pass, vet clean.
DEPLOYED: both production nodes run the IDENTICAL binary sha 93e07b85f224a0c9, both report organization
secret storage ENABLED key version 1, both health 200, public site 200, staging 200.
NOT DONE YET, named so it does not read as finished. work_claims and work_touches EXIST IN THE SCHEMA with
their indexes and policies, and nothing writes to them yet: the claim and presence ROUTES are the next piece,
and the visibility rules that will govern them are already live and tested. The frontend settings screen is
also not built; every route above is proven at the API only.
## WHO IS WORKING WHAT, the double work control, 30 July 2026
The tables went in with migration 0087 and nothing wrote to them. That is now closed. internal/modules/collab
serves five routes: read the activity on a record, record a visit, claim, release, and the caller's own list.
WHY ITS OWN MODULE. The question is identical for a candidate, a requisition, an opportunity, a proposal and
a client. Putting it in recruiting would have made the missionmatch module import a sibling to ask about a
candidate; a copy per module would have produced five answers that drift.
IT IS NOT A SECOND OWNERSHIP SYSTEM. clients and requisitions keep owner_user_id from migration 0047 as the
authority on whose desk a record belongs to. A claim is a shorter lived and different fact: not "this account
is mine" but "I am on this right now, do not also call them today". The read returns BOTH. CANDIDATES had
neither, and a candidate phoned twice in one day by the same agency is the complaint this closes.
DECISIONS RECORDED.
- A CLAIM IS NOT A LOCK. A second person gets 409 with the alternative named, and taking a record over is an
EXPLICIT flag that writes its own audit row, so "Marcus took this from Dana" is findable later.
- CLAIMING AGAIN EXTENDS rather than conflicting, because a recruiter pressing claim twice means "I am still
on this", which is what the expiry needs to hear.
- EXPIRED CLAIMS ARE RELEASED BEFORE A NEW ONE IS TRIED. The partial unique index covers every unreleased
claim, so without this an abandoned claim would own a candidate forever.
- PRESENCE IS UPSERTED, NEVER APPENDED. One row per person per record. Appending would generate a row per
page view per recruiter per candidate and put a browsing history of a named employee in a trivially
reportable table. This is presence, not surveillance. Presence is also NOT RECORDED when the organization
switches it off, rather than recorded and hidden.
- PRIVATE HIDES COLLEAGUES, NEVER YOURSELF, NEVER FROM MANAGEMENT. A caller who may not see others still sees
their OWN claim, because hiding it causes the very double work this prevents. Managers see the team at
every level, since a manager who cannot see the collision is the one person who could fix it.
- visible_to_you IS RETURNED EXPLICITLY. A filtered answer must never read as "nobody is on this".
- NO NEW CAPABILITY. Reads sit at candidate.read so a viewer sees the collision; writes at candidate.write.
The frozen matrix already said this correctly and did not need widening.
- The entity type is a CLOSED MAP to table names because the table name reaches SQL. An unknown type is a 404
before any query is built, and a test asserts every value is a bare lowercase identifier.
VERIFIED: 45 of 45 walkthrough assertions against staging with THREE real people on one real candidate, plus
8 module tests including a structural one that pins the visibility rule to exactly one call site. 58 Go
packages green, vet clean. Deployed to both production nodes, identical binary sha acf1f910eb9221f5, health
200 on each, site 200.
WORTH RECORDING: the first walkthrough run reported nineteen failures and every one was MY HARNESS, not the
product. The auth limiter is New(1, 10), burst ten refilling one per second, and creating three people in a
row makes about twelve auth calls, so the third person got 429 and I first misread it as a broken session.
The limiter was right. The harness now backs off instead, and the limiter was NOT weakened to make a test
pass.
STILL NOT BUILT: the frontend. Every route here is proven at the API only, so a recruiter cannot yet see any
of this.
## RECORD SCOPE AND TEAMS, the third access axis, 30 July 2026
Migration 0088 live in ALL THREE databases at v88, verified per database. teams and team_members with FORCE
ROW LEVEL SECURITY and zero role scoped policies, candidates.owner_user_id, three scope settings, and
mm_team_peer_ids.
WHY. A recruiting platform's access model has three axes and this had one and a half. WHAT ACTIONS may I take
was the capability matrix, already correct. WHICH FIELDS may I see was field sensitivity, migration 0087.
WHICH RECORDS may I see at all did not exist, and it is the axis Bullhorn, Greenhouse, Lever and Salesforce
all have. Two things blocked it: there was no notion of a TEAM, so "my team's records" could not be
expressed, and CANDIDATES HAD NO OWNER, so the most important record type in the product could not be scoped.
DECISIONS RECORDED.
- THE DEFAULT IS ORGANIZATION, WHICH IS EXACTLY TODAY'S BEHAVIOUR, and a test asserts every role still sees
everything under the shipped settings. A migration that narrowed somebody's view on the morning it shipped
would be an incident, not a feature.
- THE UNRESTRICTED QUERY PATH IS LITERALLY UNCHANGED. ListCandidatesScoped is a SEPARATE sqlc query, so an
organization that has not narrowed runs the same statement it always ran and cannot have been broken.
- OWNER AND ADMIN ARE ALWAYS ORGANIZATION WIDE AND IT IS NOT CONFIGURABLE. A product that lets an
administrator lock themselves out of their own data generates a support call it cannot resolve.
- A MANAGER MAY NOT BE NARROWED TO OWN, refused with 422: a manager who cannot see their team cannot run a
desk. Manager scope chooses between the team and the floor, nothing narrower.
- AN UNKNOWN OR CORRUPT SETTING NARROWS TO own, never widens. The failure mode is somebody seeing too little
and saying so, not everybody seeing everything and nobody noticing.
- A PERSON MAY BE ON SEVERAL TEAMS. Agencies run people across desks; one team per person makes the common
case a lie. Team scope is the union of every team the caller belongs to, and the peer resolver INCLUDES the
caller so somebody on no team still sees their own work.
- TEAM LEAD IS A LABEL, NOT AN AUTHORITY. Making somebody a lead does not promote them, which is what stops
an org chart feature becoming a privilege escalation path.
- UNOWNED RECORDS ARE VISIBLE BY DEFAULT. An unowned candidate nobody can see is a candidate nobody will
ever work. Releasing a candidate back to the pool is a supported action, not an error, so a departing
recruiter's bench stays workable.
- READING the desks is candidate.read, since an org chart is not privileged. WRITING membership is
org.settings.write, because under a narrowed scope adding somebody to a team IS an access grant, and it is
audited as one.
VERIFIED: 41 of 41 walkthrough assertions against staging with an owner, a manager and TWO recruiters on
different desks over four candidates, proving narrowing hides, teams reveal, removal from a desk takes the
access with it, and no one can lock the account holder out. 58 Go packages green, vet clean. Both production
nodes on identical binary sha 2580f555149f9ff0, health 200 each, site 200.
THREE DEFECTS FOUND BY THE WALKTHROUGH, all mine, all fixed.
1. THE SAME SILENT NO OP AS LAST TIME. The three scope fields were added to the store, to validation and to
the SQL, and NOT to the handler's request struct, so the API accepted them, returned 200 and changed
nothing. A reflection test now compares the two structs field by field in BOTH directions and was proven
to fail against the broken shape. This class of bug has now happened twice; it cannot happen a third time.
2. A NULL SCANNED INTO A Go bool. A team with no lead makes "lead_user_id = user_id" NULL, so every READ of
the teams surface was a 500 while every write worked perfectly. That asymmetry is why it survived until
somebody tried to look at the desks.
3. An untyped nil in an interface passed as a uuid parameter, which pgx cannot infer a type for.
STILL NOT BUILT: the frontend, and scope enforcement currently covers the CANDIDATE list, which is the record
type that had no owner and therefore the one that needed it. Requisitions, clients and opportunities already
carry owner columns and are NOT yet scoped; that is the honest boundary of this pass.
## THE TWO GAPS THAT MADE THE PRODUCT UNUSABLE, closed 30 July 2026
Research against the established products in this market, Bullhorn, Greenhouse, Lever, Ceipal, plus the
Salesforce patterns they all borrow, measured against a verified route inventory of 88 mounted routes. The
spine was strong: clients through requisitions through applications through offers through placements, with
activity logging, a task queue, six alert kinds including clearance_expiring, dispositions, reports and an
audit trail. TWO holes made it unusable in practice, and both change what a screen must show, which is why
they were closed BEFORE the frontend rather than retrofitted after it.
Migration 0089 live in all three databases at v89, verified per database: candidate_documents,
message_templates and messages, each with FORCE ROW LEVEL SECURITY, zero role scoped policies, and NO DELETE
grant to the app role, confirmed by querying grantee rather than trusting a count.
1. A RESUME COULD NOT BE ATTACHED TO A CANDIDATE. opportunity_documents existed; nothing equivalent existed
for the record type the whole product is about. Now: upload, list, download and supersede, sharing the
SAME object store as opportunity documents so there is one place to back up rather than two.
2. NOTHING COULD BE SENT TO A CANDIDATE. The only two send calls in the codebase were the account invite and
the settings test message. Now: templates with server side placeholders, a preview, and sending through
the organization's OWN mail server from migration 0087.
DECISIONS RECORDED.
- THERE IS NO DELETE ON A DOCUMENT. It is SUPERSEDED, so what was sent to a client six months ago stays
recoverable. The duplicate index is PARTIAL on is_current, so the same bytes can be re uploaded once a
document has been superseded, which is what a recruiter actually does.
- THE MEDIA TYPE IS DECIDED BY THE SERVER from a closed allowlist, and the EXTENSION beats the declared type
because a browser's guess is caller supplied and frequently wrong. Downloads are served with
Content-Disposition attachment, nosniff, a sandbox policy and private caching, so a file that slipped
through could still not execute in the origin.
- THE INTEGRITY STAMP IS CHECKED ON THE WAY OUT. If stored bytes no longer hash to what was recorded, the
download fails rather than handing somebody a file the audit trail does not describe.
- A MESSAGE ROW IS WRITTEN BEFORE THE SEND, not after. A send that succeeds while the database is briefly
unavailable would otherwise leave a candidate holding a message the agency has no record of.
- EVERY SEND ALSO WRITES A recruiter_activities ROW with auto_captured true, so outreach appears in the same
history as calls and meetings. The subject is folded into the body because that table has no subject
column, and inventing one to suit messaging would change a table five other surfaces read.
- PLACEHOLDERS ARE VALIDATED AT AUTHORING TIME and refused if unfillable, because a candidate receiving a
visible curly brace reads it as a broken mail merge.
- A PLACEHOLDER NEVER RENDERS EMPTY. first_name falls back to the full name and then to a neutral word,
because "Hi ," is worse than no personalisation.
- SENDING IS candidate.write and reading correspondence is candidate.read. Template authoring is
org.settings.write, because a template goes out under the organization's name to people who are not its
employees.
VERIFIED: 38 of 38 walkthrough assertions against staging, including the controls: an HTML upload and an
executable both refused with 415, an empty file 422, an identical re upload 409, the attachment and nosniff
headers present, and a send refused with a 409 that names the screen that fixes it rather than a 500. 58 Go
packages green, vet clean. Both production nodes on identical binary sha f31cff48b3a78faa.
TWO DEFECTS THE WALKTHROUGH FOUND, both mine.
1. PREVIEW REFUSED WHEN A RECORD HAD NO EMAIL ADDRESS. Previewing text is useful whether or not an address
exists, and refusing sends a recruiter hunting a bug that is not there. Preview now renders and returns
sendable false with the reason; only the SEND path insists on an address.
2. A CANDIDATE CREATED THROUGH THE API COULD NEVER HAVE AN EMAIL ADDRESS. The candidates table has carried
email and headline since migration 0002 and the insert always wrote them, but createCandidateRequest
never exposed either. So the outbound correspondence just built would have been unusable for every
candidate created through the API. Found only by testing a send against a real record.
DELIBERATELY NOT BUILT, recorded so it does not get picked up by accident: timesheets, invoicing and VMS,
which are a second product and which the client's enterprise customers already impose; and autonomous AI
sourcing and screening, which would damage the Automated Employment Decision Tool story that is this
product's actual differentiator.
STILL MISSING, in the order the research ranked them: saved searches and hotlists, bulk actions, an inbound
apply path and job board posting, email and calendar sync so activity logs itself, interview scheduling, and
duplicate merge. Then the frontend.
## SEARCH, SAVED SEARCHES, LISTS AND BULK ACTIONS, 30 July 2026
Migration 0090 live in all three databases at v90, verified per database: saved_searches, candidate_lists,
candidate_list_members, forced RLS, zero role scoped policies, plus two structured indexes search needed.
THE GAP WAS WORSE THAN THE RESEARCH SAID. There were no saved searches, and there was no SEARCH: the
candidates route took a limit and an offset and nothing else, so "TS/SCI cleared network engineers in
Maryland who are available" was not a question this product could be asked. The GIN index over search_tsv and
the HNSW index over resume_embedding have existed since migration 0002 with nothing mounted on them.
DECISIONS RECORDED.
- A SAVED SEARCH IS A QUESTION, A LIST IS AN ANSWER. Two tables, not one with a flag. A saved search re runs
and its result moves with the bench; a list is what somebody committed to and must not move underneath
them, because an agency that shortlists nine people for a bid needs those nine next week.
- EVERY USER VALUE IS A BOUND PARAMETER. Only column names and operators from closed sets are interpolated.
Proven with an injection string, a bare wildcard, and punctuation, all of which return 200.
- websearch_to_tsquery, not plainto_ or to_tsquery, so a recruiter typing O'Brien or an ampersand gets
results rather than a syntax error.
- SEARCH RESPECTS RECORD SCOPE through the SAME orgsettings.Filter every list uses. Without it a recruiter
narrowed to their own records could simply search for everybody else's, which would be a hole straight
through migration 0088. Proven: a scoped recruiter gets 0 and cannot widen it by naming an owner.
- scope_applied IS RETURNED, so a short answer is explained rather than mistaken for an empty bench.
- BULK IS ONE TRANSACTION and reports per record outcomes. Already on the list is a SKIP, not an error,
because somebody selecting forty people does not know which nine are already there. One malformed
identifier refuses the WHOLE batch, because a bulk action that quietly did thirty nine of forty is the kind
of thing nobody notices until the fortieth person never gets called.
- NO DELETE ON A LIST, it is archived, because "who did we shortlist in March" is a June question and because
a do_not_contact list that can be deleted is a compliance record that can be deleted. Membership CAN be
deleted, since removing somebody from a shortlist is a correction.
- A PRIVATE SAVED SEARCH IS INVISIBLE TO EVERYONE ELSE INCLUDING THE OWNER OF THE ACCOUNT, and deleting one
you do not own returns 404 rather than 403, so private search names cannot be enumerated.
THE DEFECT THIS FOUND, and it predates all of this work.
CLEARANCE WAS STORED AS A CODE AND COMPARED AS A LABEL. The persisted value is ts_sci; the interface shows
TS/SCI. codes.go says so explicitly and calls persisting the label rather than the code "a real bug class".
Two halves were both wrong: validLevelCode accepted ONLY an exact code and silently degraded anything else to
"unknown", so a candidate saved through the API with the label the interface displays LOST THEIR CLEARANCE,
with no error; and the new search compared raw input against the column, so a recruiter typing what they see
got an empty bench. Neither raised anything. Storing and searching now SHARE one translation, so the two
vocabularies cannot drift, and a test locks every label and code spelling to its stored value.
VERIFIED: 47 of 47 walkthrough assertions against staging over a five person bench with two lists and a
second signed in recruiter. 58 Go packages green, vet clean. Both production nodes on identical binary sha
b840acabd61a4222, health 200 each, site 200.
STILL MISSING, in the order the research ranked them: an inbound apply path and job board posting, email and
calendar sync so activity logs itself, interview scheduling, and duplicate merge. Then the frontend.
## THE INBOUND PATH, a public careers page and an apply route, 30 July 2026
Migrations 0091, 0092 and 0093 live in all three databases at v93, verified per database. job_postings and
job_applications with forced RLS, zero role scoped policies and no app DELETE, plus three SECURITY DEFINER
functions with EXECUTE revoked from PUBLIC.
Until now every candidate had to be imported or typed in by a recruiter, so the product could only hold
people somebody had already found. This is the difference between a database and a pipeline.
DECISIONS RECORDED.
- THE PUBLIC URL CARRIES THE EMPLOYER SLUG. A public request has no session, so no organization, so RLS
denies it everything, and something must name the tenant first. A bare token would need a global token to
tenant map that works across sharded databases; the slug is already the directory key and is information
the applicant should see anyway, because they are applying to Acme and not to an opaque string.
- THE PUBLIC COPY IS ITS OWN TEXT, NOT A JOIN TO THE REQUISITION. A requisition carries bill rate, pay rate,
the client name and the internal description, and rendering a public page from it would put the agency's
margin and its customer's identity on the open internet with a silent failure mode. Publishing is therefore
an act of WRITING, and a field that is not on the posting row cannot leak. The walkthrough asserts this
directly against a requisition seeded with a rate and a confidential client name.
- A SUBMISSION IS NOT A CANDIDATE. Under the OFCCP Internet Applicant rule an applicant is somebody who
expressed interest AND whom the contractor then CONSIDERED, and a federal contractor must show both.
Submissions land in job_applications; converting one is a separate recorded act by a named human. It also
means spam never reaches the bench.
- THE SELF REPORTED CLEARANCE IS NOT CARRIED ACROSS on conversion. Writing it into the candidate's clearance
field would turn an unverified claim into a scored attribute. A recruiter sets it after checking.
- CONSENT IS REQUIRED AND RECORDED AS TEXT PLUS A TIMESTAMP, not a bare boolean, because "they ticked a box"
is not a defensible answer and the wording changes over time.
- ONE ANSWER, 404, for a missing employer, a missing posting, a draft and a closed one, so the public surface
cannot be used to map which customers exist or which roles they are quietly working.
- NO CSRF ON THE PUBLIC ROUTES, deliberately and correctly: CSRF protects a request carrying ambient
authority and these carry none. They have their OWN rate limiter, two a second with a burst of twenty,
sized for strangers rather than for a signed in floor.
- NO DELETE on either table. An applicant flow log with holes in it is not an applicant flow log; spam is
marked spam.
VERIFIED: 35 of 35 walkthrough assertions against staging, with the public half tested using NO COOKIE JAR AT
ALL so a pass proves a genuine stranger can reach it. 59 Go packages green, vet clean. Both production nodes
on identical binary sha 322b606e56b175c9.
THREE DEFECTS, all found by the walkthrough, all mine.
1. THE STAGING PARITY HELPER MIRRORED TABLES AND COLUMNS AND NOT FUNCTIONS. Migration 0085's lesson in a new
place: every SECURITY DEFINER function ever granted to missionmatch_app was unreachable from
missionmatch_staging_app. It failed expensively, as the careers page reporting a genuinely published
posting as unavailable, so the symptom pointed at the data and not at a permission. Migration 0092 fixes
the HELPER rather than the one function, identifying functions by full argument signature because a name
alone is ambiguous under overloading.
2. THE PRE TENANT RESOLVE WAS A PLAIN QUERY against organizations, which carries FORCE ROW LEVEL SECURITY.
The app role is NOBYPASSRLS and a public request has no app.current_org_id, so it correctly returned zero
rows. The tenancy contract was working exactly as designed and the feature was wrong. Migration 0093 adds
a narrow SECURITY DEFINER resolve that returns an identifier and nothing else.
3. MIGRATION 0093 SHIPPED WITHOUT goose StatementBegin MARKERS, so goose split the dollar quoted function
body on its internal semicolons and the migration failed. It failed SAFELY, transactional, version
unchanged. Every other migration defining a function was then checked and all carry the markers.
STILL MISSING, in the order the research ranked them: email and calendar sync so activity logs itself,
interview scheduling, and duplicate merge. Then the frontend. External job board DISTRIBUTION, posting out to
ClearanceJobs or Indeed, is an integration per board and is NOT built; what exists is the careers page and
the apply route those boards would point at.
## MAIL SYNC, so a recruiter's correspondence logs itself, 30 July 2026
Migration 0094 live in all three databases at v94, verified per database: mail_accounts and synced_messages,
forced RLS, zero role scoped policies.
recruiter_activities has had the right vocabulary since migration 0049 and every row in it had to be TYPED BY
A HUMAN, which is the most reliable way to end up with an activity log nobody trusts: the people best at
recruiting are the least likely to stop and write down that they sent an email.
DECISIONS RECORDED.
- HEADERS ONLY, NEVER BODIES, and it is a property of the code rather than a convention. internal/platform/
imapclient asks for a fixed header field list and has NO PARAMETER through which a body could be requested.
Pulling bodies would drag a recruiter's whole mailbox, including correspondence with their doctor and their
lawyer, into a platform holding controlled unclassified information under an employment record retention
rule. The value of the body is small and the liability is enormous.
- A MESSAGE MATCHING NOBODY IS NOT STORED AT ALL. Every message is compared against the tenant's candidates
and contacts by address and dropped if it matches none. Connecting a mailbox is not handing over a mailbox,
and the sync result reports the discarded count so that promise is visible rather than asserted.
- WRITTEN IN HOUSE RATHER THAN TAKEN FROM A LIBRARY, following the recorded TOTP precedent: every primitive
stays provably inside the FIPS validated module and there is no external crypto to re verify. It is a
narrow IMAP4rev1 subset and must not grow into a general client.
- EXAMINE AND BODY.PEEK, so syncing never marks a recruiter's mail read. A sync that silently changed what
their mail client showed them would be noticed once and switched off forever.
- IMPLICIT TLS ONLY, no STARTTLS negotiation, because a negotiated upgrade is one more thing that can
silently not happen.
- THE SAME REACHABILITY BOUNDARY as the mail sender, resolved then dialled by ADDRESS so DNS rebinding cannot
slip past. Proven live against 127.0.0.1, 169.254.169.254, 100.94.8.94 and 10.0.0.5.
- PER USER, and every route acts on the CALLER with no user parameter. An endpoint that let one person
configure another's mailbox would be a way to point somebody else's credentials at a server you control.
- The dedup ledger is deleted on disconnect, so a recruiter who reconnects re syncs cleanly instead of being
permanently blocked by a ledger they cannot clear. Activities already logged are untouched.
VERIFIED: 24 of 24 walkthrough assertions against staging, plus 9 parser unit tests including a totality test
that throws hostile and malformed headers at every parsing function. 60 Go packages green, vet clean. Both
production nodes on identical binary sha d30e1e05814593c2.
HONEST BOUNDARY, stated plainly: THE END TO END SYNC AGAINST A LIVE IMAP SERVER IS NOT TESTED. There is no
real mailbox to connect to here. What is proven is the configuration surface, the credential never being
returned, the reachability guard, an unverified mailbox being unable to switch on, a sync refusing to run
against one, and the header parsing and address matching in unit tests. The first real mailbox will be the
first real test, and it should be a test account rather than a live recruiter's.
TWO DEFECTS THE WALKTHROUGH FOUND, both mine.
1. EVERY VALIDATION ERROR REPORTED ITSELF AS A BAD GATEWAY. Configuration mistakes and transport failures
shared a message prefix and the handler could only pattern match on it, so a missing host returned 502.
Fixed with a sentinel error rather than a better pattern.
2. THE FAILURE RECORD WAS ROLLED BACK BY THE ERROR THAT CAUSED IT. TestMailbox wrote last_error inside the
transaction and then returned the error from inside the same closure, which rolls the transaction back and
takes the row with it. A user saw a 502 and then found last_error empty, the least helpful possible
combination. The auth module carries the same note about an audit row for exactly this reason, which is
the second time this pattern has bitten this codebase.
STILL MISSING: interview scheduling, duplicate merge. Then the frontend. Calendar sync is NOT built; CalDAV
is the vendor neutral equivalent and is a separate piece.
## INTERVIEW SCHEDULING, 30 July 2026
Migration 0095 live in all three databases at v95: interview_offers and interview_offer_slots with forced
RLS, zero role scoped policies, and a public SECURITY DEFINER read with EXECUTE revoked from PUBLIC.
interviews has existed since migration 0048 and it RECORDS an interview. It had no way to ARRANGE one, which
is the part that costs a recruiter an afternoon, being four emails and a phone call per candidate.
DECISIONS RECORDED.
- THE CALENDAR INVITE IS AN iCALENDAR ATTACHMENT, NOT A CALENDAR INTEGRATION. Integrating Google Calendar and
Microsoft Graph means two vendor OAuth applications, two consent screens, two credential sets and a
permanent dependency on two APIs, which cuts against the both clouds no lock in mandate and does nothing
for a candidate whose calendar is neither. An iCalendar REQUEST attachment is the interoperable standard
all of them already accept, needs no credentials, and is a few hundred lines with no dependency.
- multipart/ALTERNATIVE, not mixed. Mixed produces a message with a file attached that a calendar ignores;
alternative with method=REQUEST is what makes the accept and decline buttons appear.
- AN OFFER IS NOT AN INTERVIEW. Offering three times creates an offer with three slots; the interviews row
appears only when somebody picks one. Creating it up front would leave a pipeline full of interviews that
never happened, which is exactly the number a hiring manager asks about.
- THE SLOT IS TAKEN UNDER A CONDITIONAL UPDATE, not a read then write. Two candidates cannot race, but one
candidate double clicking absolutely can, and a check followed by an update would create two interviews.
Proven: the second click is refused and the count stays at one.
- THE INVITATION IS SENT AFTER THE TRANSACTION COMMITS, and a mail failure does NOT fail the booking. The
time is taken and the interview exists; a missing calendar entry is something a recruiter fixes by hand.
- Times are ALWAYS UTC with the trailing Z and every value is escaped and every line folded. An unescaped
comma silently truncates a location in the recipient's calendar; an unfolded line over 75 octets is
rejected by some clients and mangled by others. An invite that arrives corrupted is worse than none,
because the recruiter believes it was sent.
VERIFIED: 26 of 26 walkthrough assertions against staging with the public half using no cookie jar, plus 8
iCalendar unit tests covering escaping, folding, multi byte safety, UTC conversion and cancellation. 61 Go
packages green, vet clean. Both production nodes on identical binary sha f611d2f915ccca36.
TWO DEFECTS, one mine and one that predates this work.
1. MINE: a parameter was used both as a timestamp column and as the left side of an addition in the same
INSERT, so Postgres deduced two types for it and refused the statement with SQLSTATE 42P08. The handler
turned that into a generic 500 with no cause in the log, which cost a round of guessing; the handler now
LOGS the cause while still returning a generic message. The arithmetic moved into Go.
2. PRE EXISTING: THERE WAS NO WAY TO LIST INTERVIEWS. They could be created and updated and never read back,
so the only way to see what was scheduled was to query the database. Found because the walkthrough tried
to confirm that booking produced exactly one interview. GET on an application's interviews now exists.
STILL MISSING: duplicate merge. Then the frontend. Calendar SYNC, reading a recruiter's existing calendar,
is not built and is a separate piece from sending an invitation.
## CANDIDATE MERGE, and the carried issues, 30 July 2026
Migration 0096 live in all three databases at v96: candidates.merged_into_id, candidate_merges, and
mm_candidate_referencing_tables.
A bench gets dirty. The same person applies through the careers page, arrives in an ATS export, and is
sourced by a recruiter, and now there are three of them with a third of the history each. Dedup existed at
IMPORT time only; nothing existed for the duplicates that arrive afterwards, which is most of them.
DECISIONS RECORDED.
- THE TABLES ARE READ FROM THE CATALOG, not a list in Go. It found EIGHTEEN referencing tables where a hand
count of the migrations found fifteen, which is the argument in one number: a list would already have been
wrong, and the failure is silent, a merge reporting success while history stays on a tombstone.
- A SAVEPOINT PER TABLE. Repointing collides whenever both records already had the same thing, which for two
rows of one person is the common case, and without it the first collision aborts the merge that exists to
fix exactly those duplicates.
- THE LOSER IS A TOMBSTONE, NEVER A DELETE. A scoring decision made in March about that record has to be
reconstructable in June, and deleting it destroys the subject of the decision.
- FIELDS ARE FILLED IN, NEVER OVERWRITTEN, and clearance is not touched at all. Overwriting would let a merge
quietly replace a verified clearance with an unverified one. Proven: after merging a Secret record into a
TS/SCI one, the kept record is still TS/SCI.
- MERGING IS org.settings.write, ABOVE ordinary candidate.write. It is not reversible by the person who did
it and it rewrites whose history is whose. A recruiter can SEE suggested duplicates, because that is a
report, and cannot act on them.
- DETECTION ONLY SUGGESTS, and labels itself honestly: an email match is strong, a name and location match
is WEAK because two cleared analysts called James Smith in one metro area is an ordinary thing.
THE FINDING THAT MATTERS MOST, and it is the system defending itself. aedt_candidate_notices has NO UPDATE
grant for the application role. The first merge attempt died on it. That grant is correct: a disclosure
notice sent to a candidate under an Automated Employment Decision Tool rule is a FACT ABOUT THAT PERSON, and
repointing it at somebody else would falsify a compliance record to make a tidy merge. The merge now records
those tables as RETAINED, distinct from DROPPED, and carries on; the tombstone still resolves so nothing
becomes unreachable. candidate_merges is excluded for the same reason.
VERIFIED: 21 of 21 walkthrough assertions against staging, exercising a REAL collision, both records on one
list and a resume on the loser. 61 Go packages green, vet clean. Both production nodes on identical binary
sha e0d98251afdf0812.
THREE DEFECTS FOUND BY THE WALKTHROUGH.
1. ADDING A COLUMN TO candidates BROKE CANDIDATE SEARCH. The search does SELECT c.* into a generated struct
by name, so merged_into_id with no matching field made every search a 500. sqlc had not been regenerated.
This is the cost of SELECT * with struct mapping and it is worth remembering: a migration that touches
candidates now requires a regenerate.
2. AN ALREADY MERGED RECORD REPORTED "no such candidate". The existence check filtered on deleted_at, so a
tombstone looked absent. That is actively misleading: it tells somebody the record does not exist when it
is exactly where they left it. Now 409 with the real reason.
3. The merge tried to repoint the merge LEDGER itself, which would rewrite the record of past merges.
CARRIED ISSUES ALSO CLOSED TODAY. Session status codes now 401 rather than 403 for every "no session" case,
with the required WWW-Authenticate header and a scheme that does not make a browser show its own login
dialog; the authenticated but unscoped cases correctly stay 403. All 28 nginx conflicting server name
warnings had one cause, backup files inside sites-enabled being loaded as live configuration; moved not
deleted, zero conflicts, all affected sites verified at 200. Clearance data assessed and deliberately NOT
changed: one record of eleven, no email and no resume, so guessing was refused. And a real security finding
while sweeping for the pasted keys: /home/user/mmk/*.blob were 0644 in a 0755 home on a host serving sixty
sites, and live.blob held a live Google key and a live Groq key; now 0600, services unaffected, and those two
keys should be treated as disclosed.
STILL MISSING: the frontend. Every surface built today is proven at the API only.
## SESSION HANDOFF, 30 July 2026. PHASE 2 IS CLOSED. WRITTEN LAST.
THIS IS THE LAST WRITE OF THIS SESSION. Nothing was done after it.
PHASE 2 CLOSED under Joseph's recorded conditional pre approval: fully green with zero open findings closes it
on his word. All five gates are green at HEAD and all four findings are closed. GATE_E.md is the account he
reads, and it LEADS with what is wrong rather than burying it.
CURRENT PHASE: PHASE 3, NOT STARTED. Exactly one phase named, and it is the next one.
GATE STATES, all at HEAD, each with a control that had to fail first
A functional PASS. 55 packages ok 0 fail. 217 integration tests 0 fail 1 skip.
B stress PASS. Complete at HEAD, needed no rerun. 30 minute soak, 2,028,801 samples.
C visual PASS WITH ONE NAMED COVERAGE LIMIT. 88 captures 0 findings both surfaces. Pixel baselines
approved and proven both ways, 48 and 40 compared with 0 regressions, controls catching 48 of
48 and 40 of 40. THE LIMIT: 48 authenticated captures hold only 18 distinct images because both
fixture accounts hold one tool, so most routes render the same denial panel.
D drift PASS. Engine diff 0 with anchor and path asserted, controlled at 1476 lines against the wrong
anchor. Schema 85 across all three databases, each queried directly. Production API binary byte
identical to the pre build hash.
E human Joseph's, and the pre approval is already recorded.
WHAT THE RERUNS COST AND FOUND. Gate A FAILED on its first rerun with seven failing integration tests, and the
reruns found TEN defects. Two were production defects live for days: the embedding provider could not
authenticate at all, so every embedding depended on another project's node proxy staying alive; and the gateway
refused every teaching signal ever sent, which is why the teaching tables are empty, not unused but unreachable.
THE EARLIER GATE A PASS IS WITHDRAWN AS EVIDENCE. Three of its tests could not have passed against a correctly
granted database, so that verdict was taken without them or with other credentials.
THE PIXEL HALF OF GATE C NEVER EXISTED. Joseph's approval of the contact sheets unblocked nothing because the
code had never been written, and two write ups had filed the gap as awaiting his approval. Built, and proven
in both directions.
THE SUITE WAS HIDING THE EVIDENCE. Run with default environment it executed 195 tests and silently omitted 22.
A test gated on an optional variable does not fail, it disappears. Four of six Gate A defects were only
reachable once those tests ran.
STATE AT STOP, measured after the last change
HEAD is this commit, tree clean. 55 packages ok, 0 FAIL, GOFIPS140=v1.0.0.
Secret sweep CLEAN, working tree and 15 of 15 out of repo scopes.
Zero Python, zero dashes across every tracked doc, each with a planted control.
Engine diff 0 at internal/modules/matching/engine against e74fd2e, 10 files each end.
Schema 85 on tenant, staging, and control plane.
PRODUCTION NOW RUNS PHASE 2. Both API nodes on 8ce5a2b8141a2ab1, frontend index-DmaEX_GX.js with self hosted
fonts, gateway 9e1ff5da245989cd. Phase 2 routes answer 403 and 405 where they answered 404 yesterday. The
teaching boundary accepts the current contract for the first time, 202, and still refuses a planted client
fact, 422. Document intake remains DARK by design: MM_STORAGE_ROOT is unset in production and enabling it is
a decision about a disk at 82 percent that also holds the backups. Rollback artifacts in place on every host.
See deploys/production-deploy.txt, including the three things that went wrong and the checks that caught them.
WHAT CHANGED ON THE MACHINES
M4 the STAGING gateway binary was replaced and restarted with identical env, so /v1/teach now accepts
the current scoring contract and logs what it accepts. The PRODUCTION gateway on 8092 still carries
the old binary and therefore still refuses every teaching signal. FIXING THAT IS A PRODUCTION DEPLOY
AND IT IS JOSEPH'S CALL.
Bubbles staging frontend redeployed twice: the Admin roster fix and the self hosted fonts. Fonts now served
from /fonts, no request leaves for Google. mm-missionclass.timer installed and enabled nightly.
pixelmatch and pngjs installed in ~ for the sweep. Gate C baselines in ~/gatec-baselines.
megamind mm-identity-sync.sh replaced earlier in the build, backup at /root, hourly timer healthy.
DATABASES migration 0085 applied to all three. Its guard makes it a genuine no op outside staging and both
other databases logged the skip before recording the version.
PHASE 3, SCOPE ALREADY RECORDED, in dependency order
1. The vocabulary decision, MAG and TAG, because every entry written after it inherits the names.
2. THE WRAP RATE AND BURDEN MODEL. Zero such columns exist, so bill rate minus pay rate is a SPREAD and the
product can show a number that looks like margin and is not one.
3. The wage bridge, D22, which is ONE item and not three: canonical SOC form, then ontology_crosswalk, then
skills.soc_code. promote-ontology is NOT safe to run until it is settled.
4. The self extending knowledge store, rows never tables, which lands before the crosswalk since the
crosswalk is its first customer.
5. The office format reader holds Phase 3's single IMPROVEMENT slot, which is spent before Phase 3 starts.
THE MONEY ITEMS COME BEFORE THE INTELLIGENCE ITEMS. Composition makes an answer only as good as its worst fed
generator, and today the profitability half cannot be answered at all.
JOSEPH OWES TWO THINGS, neither blocking
Rotate the five API keys pasted into chat: Groq, OpenRouter, Sarvam, Cerebras, Gemini.
Decide on the OpenAI account, and rotating that key before funding it is the safer order.
WHAT TO CARRY FORWARD. Seventeen instances are now catalogued of an instrument reporting a clean result while
measuring something adjacent, ten of them found in this one session. The habit that finds them has not changed:
assert the instrument CAN fail before believing that it did not, supply every variable a suite will silently
skip on, and write the number down before changing the thing that produces it.
SELF EXTENDING KNOWLEDGE STORE, DESIGN AND ONE REFUSAL, 29 July 2026. Joseph asked that the intelligence be
able to create its own tables and graph stores, everything that goes into a database, so that the class of
problem where it learns something and has nowhere to put it is fixed.
THE PROBLEM IS REAL AND IS CORRECTLY IDENTIFIED. ontology_crosswalk is the proof: a well shaped general code
bridge, created by migration 0021, granted SELECT, read by G3's skill gap bridge, and NEVER WRITTEN by anything.
Tonight's scan found the same shape repeatedly. solicitation_extractions holds 2457 grounded extractions no code
reads. teaching_signal_aggregates and curation_proposals are empty because the transport that fills them was
refusing every signal. The intelligence keeps learning into places nothing connects.
ONE REFUSAL, AND IT IS SPECIFIC RATHER THAN CAUTIOUS. RUNTIME DDL IS NOT GRANTED. Not CREATE TABLE, not CREATE
INDEX, not ALTER, from any AI code path. Four reasons, each tied to something already load bearing here:
1. A TABLE CREATED AT RUNTIME HAS NO RLS POLICY. Migration 0085 exists tonight because a policy that fails to
name a role denies everything. A table with NO policy, made by a role holding CREATE, is the opposite
failure: it is readable across tenants with no fail safe at all. The tenancy contract is "no context means
zero rows", and a policyless table cannot honour it.
2. IT WOULD SIT OUTSIDE EVERY OTHER CONTRACT TOO. No FORCE ROW LEVEL SECURITY, no organization_id leading
composite index, no place in safety.go's read allowlist, no provenance column born with the row, and no
entry in the sqlc typed layer. Each of those is a rule this build already paid for.
3. MIGRATIONS ARE THE ONLY WAY TO CHANGE SCHEMA, and that is an ASSESSOR FACING CONTROL under the 800-171
posture, not a style preference. Self created schema is unauditable drift by definition.
4. EVERY GATE WOULD STOP MEANING ANYTHING. The schema measured at Gate D would not be the schema at runtime.
After a week in which six instruments were caught reporting success while measuring something adjacent,
handing the system the ability to change the thing the gates measure is the last thing it needs.
The app role is NOBYPASSRLS with column level grants for exactly these reasons. Granting it CREATE undoes a
deliberate design, and I am not doing it quietly on my own authority.
WHAT IS BUILT INSTEAD, AND IT SOLVES THE ACTUAL PROBLEM: THE INTELLIGENCE GROWS ROWS, NEVER TABLES.
The schema is created ONCE by a migration and is general enough that new knowledge is DATA rather than DDL.
New node kinds, edge kinds, code systems, relation types and attributes all become rows. This is not a
workaround; it is what ontology_crosswalk already is, and its columns say so: from_system, from_code,
to_system, to_code, relation, source_name, synced_at. A general bridge between code spaces, written once,
extensible forever without touching schema.
THE STORE'S REQUIRED PROPERTIES, all inherited from rules this build already enforces:
RLS forced, organization_id leading, so a learned fact is a TENANT fact unless it is explicitly global.
PROVENANCE BORN AT ORIGIN, the migration 0081 pattern, so a row carries where it came from immutably and
the egress gate can refuse it without anyone remembering to.
CITATION OR NOTHING. A learned edge names its evidence, the same rule that makes a fabricated quote
impossible to store today.
APPEND MOSTLY with supersession rather than mutation, so what the system used to believe stays readable.
THE THREE DOORS HOLD. The intelligence PROPOSES into curation_proposals; a NAMED HUMAN promotes. That path
already exists with its approve and verify endpoints, and it is the only door into anything the scoring
engine can see.
SUPPRESSION AND EEO ISOLATION unchanged, so a learned aggregate cannot become a small cell disclosure.
WHAT THAT BUYS: the crosswalk gets a writer, the orphaned extractions get a home, the graph planes get edges
they can traverse, and the intelligence can invent a new relation type at 3am without a human writing a
migration, while it remains impossible for it to invent a table nobody governs.
ALLOCATION. This is PHASE 3 SCOPED WORK under the same recorded decision that moved the wrap rate there: it is
new capability, and building it inside a locked phase whose gates are mid rerun is the exact drift the protocol
exists to prevent. It is scoped work rather than an improvement, so it does not touch Phase 3's improvement slot,
which the office format reader holds.
DEPENDENCY ORDER WITHIN PHASE 3: the store lands BEFORE the crosswalk work in task 13, because the crosswalk is
its first real customer and building the customer first would mean building it twice. | this commit | 2026-07-29
RECORDED OWNER DECISIONS, 29 July 2026 night, Joseph: "Go i agree with everything now we should be able to
finish phase 2." Two decisions follow from it and both are written here before any work proceeds.
DECISION ONE, FINDING THREE CLOSES BY DECISION. The external commercial lane stays DISABLED, recorded as
outside the FIPS boundary and restricted to non regulated tasks, which is what directive 5.3 already says of it.
THE DIRECTIVE'S OWN WORDS SUPPORT THIS AND I READ THEM TOO STRICTLY FOR TWO DAYS. 5.3 says public SAM notice
text MAY use the external backend, not must. It requires: a third backend with the enabled flag default false,
boot failing closed without the secret, only the four public classes leaving the machine, every external call
auditing backend, payload class, byte count and content hash, and THE VERIFIER GATING ALL RETURNED PROSE
EXACTLY AS IT DOES FOR LOCAL MODELS. Every one of those is now proven, the audit one only because D19 was found
and fixed. The verifier requirement is satisfied BY THE DIRECTIVE'S OWN FRAMING: it is backend blind by
specification, so proving it against local prose proves it.
WHAT IS ALSO TRUE: the commercial api.openai.com endpoint is not a sanctioned production provider anywhere in
STACK_DECISIONS, which names AWS Bedrock in GovCloud, Azure OpenAI in Azure Government, and self hosted vLLM.
Exercising it would have proven a lane production would never use.
WHAT REMAINS UNPROVEN AND IS ACCEPTED AS SUCH: prose authored by an external model passing the verifier. The
residual risk is small because the validator cannot tell where bytes came from. It is not zero and is not
being written up as though it were.
THE BOUNDED RUN IS NOT WASTED. It executed every mechanism on this side against the live endpoint for the
first time, it proved the boot guard both ways, and it is what surfaced D19.
IF AN EXTERNAL LANE IS EVER WANTED IN PRODUCTION it should target Bedrock GovCloud or Azure OpenAI Gov behind
the existing provider interface, which is a future phase item and not this one.
DECISION TWO, THE WRAP RATE MODEL IS PHASE 3 SCOPED WORK, NOT PHASE 2. Joseph said go on the wrap rate and in
the same breath said Phase 2 should now finish. Those two only reconcile one way, and this is the correction:
building the wrap rate inside Phase 2 would be UNSCOPED WORK INSIDE A LOCKED PHASE, which is the precise drift
the protocol exists to prevent, and it would enlarge Phase 2's gates so they never settle. Phase 2 therefore
closes on its existing scope and the wrap rate becomes PHASE 3's FIRST SCOPED ITEM under the full five gate
protocol.
NOTE FOR PHASE 3 PLANNING: Phase 3's single IMPROVEMENT slot is already spent on the office format reader.
The wrap rate is SCOPED WORK, not an improvement, so it does not compete for that slot. The distinction
matters because conflating them would either lose the office reader or smuggle scope through the improvement
channel.
THE WRAP RATE'S DEPENDENCY IS ALREADY KNOWN AND RECORDED: it needs the wage bridge, D22, which is one item
and not three, and it must ship with a control that asserts a labor category resolves to a SOC code AND that
the code returns a non empty OEWS wage row.
SO PHASE 2'S REMAINING WORK IS EXACTLY THREE THINGS: the Gate A, C and D re runs against the settled pipeline
with positive controls before the first counted run, the honest GATE_E.md rewrite, and the bundle filing.
Zero open findings once decision one is recorded. | this commit | 2026-07-29
THIRD POSTSCRIPT, WORKING THE LIST, 29 July 2026 night. Joseph asked for a complete list of everything with
nothing left out, and for work to start down it. The list is 23 open items, tracked in the session task list
and enumerated across the four entries below this one. PROGRESS AND THE BLOCKING STRUCTURE:
DONE AND VERIFIED THIS SESSION
The corpus is fully embedded, 6902 of 6902, guarded before and verified three ways after.
The WS7 classification pass ran for the first time and is still running its full corpus pass in the
background: mission tags 0 to 84, incumbent roles 0 to 77 to 273 and climbing.
A nightly timer now runs it without me, and the out of repo sweep picked the new unit up automatically.
Finding four closed, findings one and two closed by decision, D19 found and fixed.
STOPPED BEFORE DOING HARM, which is the real result of starting the list
D22 below. promote-ontology looked like a routine run and would have written O*NET descriptor codes into
skills.soc_code, taking the wage bridge from honestly empty to confidently wrong. Caught by comparing the
two code spaces BEFORE running rather than after. That is one item removed from the safe run list and one
production data corruption not committed.
THE BLOCKING STRUCTURE, so the next session does not rediscover it
THE MONEY ITEMS BLOCK THE INTELLIGENCE ITEMS. Composition and the critic make an answer only as good as its
worst fed generator. Wire the nine generator question today and the wage half returns nothing, because of
D22, and the profitability half cannot be answered at all, because no wrap rate column exists. Build the
money model FIRST or the composition layer will confidently answer half a question.
THE WAGE BRIDGE IS ONE ITEM, NOT THREE. Canonical SOC form, then the crosswalk, then soc_code. Any one
alone accomplishes nothing and will look like progress.
THE KNOWLEDGE ROUTES NEED A DEPLOY DECISION, NOT A CODE FIX. /api/knowledge/manifest and cards are 404 in
production and the handler declares them. Mounting them ships a build feature to the production API,
which the rails close outside a recorded approval. It is Joseph's call, not a patch.
AIOE AND TENANT MISSION TAGGING MAY BE CORRECTLY EMPTY. They read decision history and production holds 11
candidates. Check the precondition before forcing a run; an empty result there is not a defect.
THE VOCABULARY DECISION GATES NOTHING TECHNICAL and should still be made first, because every entry written
after it inherits whichever names win.
## RECONSTRUCTION ENTRY, 29 July 2026, recovery session after an unplanned restart
WHY THIS REPLACED A HANDOFF. The block that stood here was the 29 July late handoff, and it had gone
STALE IN THE SAME WAY THE ONE BEFORE IT DID, for the third time in this ledger. It said the Phase 2
close was blocked on a topology decision and that GATE_E.md was not written. Commit f3e4334, made at
06:54 that morning AFTER that handoff was committed at 00:12, superseded both statements: the trial ran
through the genuine intake, gates A through D passed, and GATE_E.md exists. A fresh session reads the
top of this file first, so the stale block was again the one most likely to be believed. Replaced, not
appended, because two handoffs in one ledger is one handoff too many.
THE RECURRENCE IS THE POINT. Twice now a session has written a handoff, kept working, and left the
handoff describing a world that no longer existed. The durable fix is not another cleanup: a handoff
must be the LAST write of a session, or it must not be written at all.
WHAT WAS RECONSTRUCTED, all verified by command against the live systems
Repository /Users/josephanady/missionmatch, branch feature/opportunity-to-outcome, HEAD f3e4334,
397 commits, working tree CLEAN, stash EMPTY. Nothing uncommitted existed, so nothing needed a
ledger note for stashed work.
Phase 2 remains the one current phase. Phase 3 NOT started.
Standing order item 1, PACKAGE ACQUISITION: ALREADY DONE AND STILL INTACT. Re verified remotely.
Standing order item 2, D6 CODIFICATION: ALREADY DONE IN FULL, all six required elements present.
Standing order item 4, THE PHASE 2 CLOSE: ALREADY DONE by f3e4334.
Standing order item 3, FAIRNESS SNAPSHOT DEPLOY AND STORAGE: STILL NOT DONE, as recorded.
Standing order item 5, THE CONDITIONAL CLOSE: CANNOT FIRE. It is conditioned on zero open findings
and zero new deviations, and this session opened two findings. The pre approval does not cover them
by its own terms.
D6 AND THE CREDENTIAL RE VERIFICATION CONDITION. The standing order says to re verify every D5 consumer
IF D6 shows open or partially codified. It shows CLOSED and fully codified, so the condition did not
fire on the LEDGER. It should have fired on REALITY: the consumers were checked anyway as part of
production health and two of them are broken. Finding D7 below. The lesson is that a condition written
against the ledger cannot catch a divergence between the ledger and the running system.
FINDING D22, THE WAGE BRIDGE IS BROKEN IN THREE WAYS AND ONE EXISTING COMMAND WOULD MAKE IT WORSE | 29 July
2026, found while starting the list, before running anything. This corrects the business fit review above,
which named two broken sides. There are three, and the third is the one that matters.
SIDE ONE, KNOWN. skills.soc_code is BLANK on all 397 rows.
SIDE TWO, KNOWN. ontology_crosswalk is EMPTY and NO code writes it. Migration 0021 creates it and grants
SELECT to the app and that is all that has ever touched it.
SIDE THREE, NEW, AND IT INVALIDATES THE OTHER TWO FIXES. THE CODE SPACES DO NOT MATCH.
bls_wages.occupation_code is a SIX DIGIT SOC CODE WITH NO HYPHEN, 825 distinct values from 111011 to
537199. That is SOC 11-1011 written as 111011.
The O*NET codes in ontology_terms are CONTENT MODEL DESCRIPTOR IDs: 1.A.1.a.1, 2.B.5.d, 2.C.9.b. Measured:
of 108 coded O*NET rows, ZERO are SOC shaped and 108 are not.
So an O*NET descriptor identifier can never join to an OEWS occupation code. They are different code
spaces that happen to share a column name.
THE TRAP I ALMOST WALKED INTO. cmd/promote-ontology pass 3 does
INSERT INTO skills (name, category, source, soc_code) SELECT ... ot.code ... WHERE source_name = 'O*NET Web
Services' AND source_record_id LIKE 'onet-tech:%'
and its own comment says the soc_code column stores the O*NET code FOR LATER CROSSWALK USE. Running it today
would write O*NET TECHNOLOGY DESCRIPTOR CODES into a column named soc_code. skills.soc_code would go from
honestly empty to CONFIDENTLY WRONG, the crosswalk join would still match nothing, and the bridge would
report as populated while being broken. That is the exact failure mode of this entire week, one step from
being committed to production, and it was caught only by comparing the two code spaces before executing
rather than after.
SO promote-ontology IS NOT SAFE TO RUN until the code space question is settled, and task 19 must not treat
it as a routine run. Its other two passes, corpus mined skills and mission spine terms, do not touch
soc_code and are unaffected.
WHAT THE CROSSWALK IS ACTUALLY FOR, and it is well designed. Its columns are from_system, from_code,
to_system, to_code, relation, source_name, synced_at. That is a GENERAL CODE SPACE BRIDGE, exactly the right
shape for this problem. It was built for this and never filled.
THREE MORE GAPS THE SAME QUERY EXPOSED, all in the ontology
O*NET skill terms: 2782 rows and only 33 CARRY A CODE. 2749 coded nowhere.
Market Extraction skills: 269 rows, ZERO codes.
GSA CALC labor_category: 20 rows, ZERO codes. This one stings, because GSA CALC is a LABOR CATEGORY RATE
source and labor category is the unit the whole bill rate conversation happens in.
WHAT CLOSING THIS ACTUALLY REQUIRES, in order
1. Pick ONE canonical SOC form and normalize on write. bls_wages already uses the six digit no hyphen
form and holds 825 of them, so it is the incumbent and should win.
2. Fill ontology_crosswalk from an authoritative SOC keyed source, mapping SOC to labor category, to
O*NET occupation, and to skill. The table shape already supports all three via from_system and to_system.
3. Only then fill skills.soc_code, with real SOC OCCUPATION codes, and fix promote-ontology pass 3 so it
stops writing a descriptor code into a column named soc_code. Either rename that column to onet_code and
add a real soc_code, or stop populating it there.
4. A POSITIVE CONTROL THAT CANNOT PASS VACUOUSLY: assert that a named labor category resolves to a SOC code
AND that the SOC code returns a non empty OEWS wage row. A count of crosswalk rows proves nothing, which
is what side three teaches. | this commit | 2026-07-29
GENERATOR TO DATA MAP, AND THE OVERLAP GAP, 29 July 2026. Joseph supplied the augmentation taxonomy, 37 named
patterns, and said the knowledge and the databases must be wired to the CORRECT generators WITH OVERLAP to make
one complete intelligence. This entry maps every pattern that has a home here to the data behind it, names the
ones with no home, and states the one architectural gap that stops the parts from being one thing.
FIRST, A VOCABULARY COLLISION THAT MUST BE SETTLED BEFORE ANY WIRING
Joseph's list reads MAG as MEMORY augmented generation and TAG as TOOL augmented generation.
THIS CODEBASE ALREADY USES BOTH ACRONYMS FOR DIFFERENT THINGS, in 11059 lines, in a boot validated Registry,
and in a DATABASE CHECK CONSTRAINT:
MAG here is MODEL augmented generation. mag.go: the fourth augmentation class after RAG, TAG and GraphRAG.
It is bound to aedt_learned_models.output_kind, which is a CHECK constraint and an AEDT compliance
artifact, so the name is load bearing in the audit trail, not just in comments.
TAG here is TABLE augmented generation. tag.go: answers questions that require the database to COMPUTE.
AND THE THING JOSEPH CALLS MAG EXISTS HERE UNNAMED: ai_memory, 287 rows all embedded, per tenant episodic and
semantic memory in memory.go.
RECOMMENDATION, and it is a recorded decision to make rather than a thing to assume: KEEP the code names as
canonical and document Joseph's names as aliases. Renaming MAG would touch a CHECK constraint and the AEDT
inventory, which is a compliance surface, to gain nothing a mapping table cannot give. Whichever way it goes,
ONE vocabulary must win in writing, because two meanings for one acronym in one system is exactly the drift
that produced the UI failure code mismatch and the declared but never written audit action this week.
WHAT ALREADY HAS A HOME, with its real data and its real state
LIVE AND FED
RAG AnswerRAG plus retrieval.go. opportunity_records.embedding now 6902 of 6902,
candidates.resume_embedding, document chunks 836 in staging.
Hybrid RAG tsvector plus pgvector fused by RECIPROCAL RANK FUSION, already present in retrieval.go,
orchestrator.go, candidates.sql.go and eval/metrics.go. The fusion primitive EXISTS.
Multi hop RAG the recursive CTE traversals in the graph planes, each with a hard depth cap, a visited
path cycle guard and a total order tiebreaker.
GraphRAG, GAG nine planes G1 to G9. Fed unevenly: award_incumbent_roles 77 as of tonight,
skill_relationships only 11, ontology_crosswalk 0.
Reasoning AG the same planes. Deterministic cited traversal is this build's reasoning surface.
TAG, SQL AG the 35 intent closed Registry, parameterized, RLS scoped, allowlisted, boot validated.
MAG as model aedt_learned_models 12, coefficients 101, four families, promotion gated on eval-model
plus a human.
MAG as memory ai_memory 287, all embedded.
OAG, KAG ontology_terms 8011 (skill 3066, psc_code 2540, ksa 2286, cert 57, work_role 42,
labor_category 20), term_expansions 204, skill_aliases 17.
Domain AG bls_wages 1670 OEWS rows, usaspending_awards 538, the missionclass classifier.
Verifier AG citation per clause, quote hash grounding, ValidateScopeCandidates rejecting a quote not
present verbatim, the faithfulness endpoint, vectorguard on the vector space.
Policy AG safety.go read allowlist, forced RLS, ForbidEEO, the AEDT output_kind CHECK.
Privacy AG scrub egress classes, small cell suppression floor k, eeo schema isolation.
Human AG slate approval, curation approve and verify, champion promotion. THE STRONGEST ONE HERE,
and the one most products lack.
Agentic, PAG planner.go, 841 lines, RoutePlan.
Adaptive RAG Classify(), rule based over question shape. A trained classifier is the documented upgrade.
Federated AG internal/platform/federated, 633 lines, plus the public mm-recall edge.
Event AG the substrate is there and UNUSED AS A GENERATOR: outcome_events 1411,
application_stage_events 1382, requisition_events 1188, audit_logs 56577.
BUILT BUT STARVED
CAG knowledge.go holds curated cards; production serves ZERO and /knowledge is 404.
Feedback AG teaching.go and teach-aggregate exist; teaching_signal_aggregates 0, curation_proposals 0.
Simulation AG cmd/aioe-sim exists and has never been run.
WAG sam-sync and the attachment mining are real; the 2625 notice bodies are still unfetched.
NO HOME, and most of these absences are CORRECT for this domain
TAG as tool, AAG, Program AG no tool calling or code execution layer. Given the no Python rule and the
AEDT posture, adding one is a policy decision, not a gap to fill quietly.
Critic AG there is a verifier and no adversary. The verifier checks grounding; nothing
argues the answer is wrong. This is the most valuable missing one.
Multimodal, VAG, Sensor AG not applicable, with ONE exception: a scanned wage determination needs
vision, which is exactly candidate P2 in IMPROVEMENTS.
Schema AG implicit rather than absent: sqlc compiles the SQL against the schema and
the Registry is schema bound.
THE ONE ARCHITECTURAL GAP: THE ROUTER IS EXCLUSIVE, AND INTELLIGENCE NEEDS IT TO BE COMPOSITIONAL
orchestrator.go Answer() is a chain of early returns and a final switch. It selects EXACTLY ONE generator per
question: AnswerGraph, or AnswerPlan, or AnswerRAG, or AnswerDirect, or refuse. There is one compositional
escape hatch, AnswerPlan, and it fires only when isCompoundQuestion sees a conservative keyword signal, and
even then it composes only WITHIN the closed Registry. It never composes RAG with a graph plane with the wage
domain with a learned model with a knowledge card.
SO OVERLAP TODAY IS ACCIDENTAL, NOT DESIGNED. And overlap is not redundancy: two independent generators
agreeing is EVIDENCE, and two disagreeing is a FLAG. That is the same principle as every positive control in
this build, applied to answers instead of instruments.
THE RECRUITER'S REAL QUESTION PROVES IT. "Can we bid this and staff it profitably" needs, in one answer:
WAG to fetch the notice, document RAG over the PWS chunks, OAG to map labor categories to SOC, Domain AG for
the wage determination floor and the OEWS band, TAG for bench counts, GraphRAG for clearance reachability and
the incumbent, MAG for capture readiness, Verifier for citations, and Policy plus Privacy throughout. NINE
generators. Today that question routes to ONE, and whichever one wins, the answer is confidently partial.
WHAT TO BUILD, and RRF is already here so the primitive is not the problem: a composition layer that fans a
question to the generators whose preconditions it satisfies, collects CITED evidence from each, fuses by RRF,
and scores AGREEMENT, with a critic that must attempt to refute the composed answer before it is served.
Disagreement surfaces as a stated uncertainty rather than being averaged away.
NOT A DECISION YET. This is a proposal and a map. docs/ARCHITECTURE.txt is untouched, correctly, because its
revision protocol admits exactly four moments and a proposal is not one of them. | this commit | 2026-07-29
BUSINESS MODEL FIT REVIEW, 29 July 2026, ordered by Joseph before proceeding further with the intelligence
layer: is it learning the RIGHT things for a company and a recruiter to see open federal contracts, understand
them, and fill the roles. Researched against outside sources, then measured against the actual schema. Every
schema number below is a query result, not a reading of the code.
HOW A FEDERAL STAFFING AGENCY ACTUALLY MAKES MONEY, from the research
THE BILL RATE IS THE WHOLE BUSINESS. It funds, in order: the worker's pay, the employer burden on that pay
(payroll taxes, insurance, benefits), overhead and recruiting cost, and only then margin. Contract and temp
markups run 30 to 75 percent; permanent placement runs 10 to 20 percent of first year salary; a 25 percent
gross margin is a common target. Gross margin is bill rate minus pay rate MINUS BURDEN.
IN GOVCON THE NUMBER IS THE WRAP RATE. Bill rate equals pay times wrap, where wrap carries fringe, overhead,
G and A, and fee. Price to win is a wrap rate argument.
CLEARED TALENT CARRIES A PREMIUM: average cleared compensation about 119,131 dollars, up roughly 4 percent to
an all time high, with demand exceeding supply.
THE SCA WAGE DETERMINATION IS A LEGAL FLOOR, not a market datapoint. DOL issues a determination per labor
category per locality for service contracts over 2,500 dollars. THE EMPLOYER BEARS THE BURDEN of mapping each
employee to the correct category, and when a determination is revised the contractor must submit a PRICE
ADJUSTMENT to recover the cost.
KEY PERSONNEL ARE A COMPLIANCE ARTIFACT, not a nice to have. Named key personnel, their resumes, signed
letters of commitment stating position, level of effort and period of performance, and availability
statements have been treated in GAO bid protest decisions as MATERIAL solicitation requirements. Omission can
render a proposal unacceptable. Contingent offers must be explicitly contingent on award, customer approval,
and continued eligibility.
THE OPERATING KPIs: submit to interview about 3 to 1, time to fill about 44 days, fill rate above 80 percent
with below 80 treated as a warning, plus submittals per recruiter, offer acceptance, 90 day retention, cost
per hire.
WHAT THIS BUILD ALREADY HAS, and it is most of the shape
THE FULL LIFECYCLE EXISTS AS TABLES: requisitions to applications to application_stage_events to interviews
to offers to placements, alongside slates and slate_members for proposal staffing, bid_outcomes,
vendor_submissions, staffing_vendors, recruiter_activities, outcome_events.
STAGING PROVES IT HAS BEEN EXERCISED: requisitions 1082, requisition_events 1188, applications 677,
application_stage_events 1382, interviews and offers and placements present, slate_members 2070,
candidate_evaluations 1777, bid_outcomes 1220, vendor_submissions 1385, recruiter_activities 612.
THE MONEY FIELDS EXIST AT THE RIGHT GRAIN: requisitions carry bill_rate_min/max, pay_rate_min/max,
salary_min/max; offers carry bill_rate, pay_rate, salary, fee_pct; placements carry bill_rate, pay_rate,
salary, fee_amount; requirement_drafts carry bill_rate_min/max and pay_band_min/max.
THE CAPTURE SIDE EXISTS: contract_vehicle, incumbent_name and incumbent_uei on opportunity_records for
recompete positioning, an agency buying pattern graph plane, a capture readiness champion model with a
documented GO/REVIEW/NO_GO threshold, and bls_wages holding 1670 OEWS rows behind the M5 price to win plane.
THE GAPS, in business impact order. Every one verified by query.
GAP 1, THERE IS NO WRAP RATE, BURDEN, FRINGE, OVERHEAD, G AND A, OR INDIRECT COLUMN ANYWHERE. Zero.
My first regex reported three matches and all three were FALSE POSITIVES: uploaded_by_user_id matched on
"loaded", and signal_value and source_signal matched on "gna" inside "signal". Named and checked, the true
count is ZERO.
CONSEQUENCE, and it is the most important sentence in this entry: bill_rate minus pay_rate is a SPREAD, not
a gross margin. Without burden the product cannot tell a recruiter whether a placement makes money, and it
cannot produce a defensible price to win, because price to win in GovCon is a wrap rate argument. The
system can currently show a number that LOOKS like margin and is not one. That is the same class of defect
as every instrument this week that reported a clean result while measuring something adjacent.
GAP 2, THERE IS NO SCA WAGE DETERMINATION MODEL AT ALL. Zero tables and zero columns matching wage
determination, sca, wd_number or wd_rev.
AND IT COMPOUNDS WITH FINDING ONE: the one document class the extractor cannot read is the SCA wage
determination, whose text layer carries no usable character map. So the legal floor under every bill rate on
a service contract is neither modelled nor readable. There is also no mechanism for the price adjustment
that follows a revised determination, which is money the contractor is entitled to and would simply not
claim.
GAP 3, THE BRIDGE FROM A LABOR CATEGORY TO WAGE DATA IS BROKEN ON BOTH SIDES.
bls_wages is keyed by occupation_code, a SOC code. skills carries a soc_code column on all 397 rows and it
is BLANK ON EVERY ONE, 0 non blank. ontology_crosswalk, which G3's skill gap bridge joins with
ox.from_code = req.soc_code, is EMPTY.
So even if the crosswalk were populated tomorrow the join would still match nothing, because there are no
SOC codes to join to. Two empty sides of one bridge, and it is why the price to win plane cannot reach real
wages for a given skill. Fixing either alone accomplishes nothing, which is worth knowing before someone
fixes one and reports progress.
GAP 4, KEY PERSONNEL IS A SINGLE BOOLEAN. requirements.key_personnel, and nothing else: no letter of
commitment record, no signed status, no level of effort, no period of performance, no availability
statement, no contingent offer state. Given that GAO treats these as material solicitation requirements,
a proposal staffing product that cannot track whether the LOC is signed is missing the artifact that
decides whether the proposal is acceptable at all.
GAP 5, THE INPUTS THAT WOULD FILL ALL OF THE ABOVE ARE STILL UNREAD. Labor categories, quantities, clearance
levels and certifications live in the PWS and the SOW, and the notice body has never been fetched while the
attachment corpus is only now being mined nightly. solicitation_extractions already holds 2457 rows with
labor_category, required_clearance, required_skills, required_certifications and required_years, grounded to
2457 distinct opportunity_records, and NO CODE READS IT. That is the shape of the answer sitting unused.
WHAT I AM NOT CLAIMING. This is a fit review against outside sources and the schema. It is not a customer
discovery interview, and Joseph knows this market far better than a web search does. Where the research and his
experience disagree, his experience wins and this entry should be corrected by name rather than quietly edited.
| this commit | 2026-07-29
INTELLIGENCE ACTIVATION, 29 July 2026, on Joseph's order that all of it be fixed and that it be learning and
growing from SAM.gov and using what it has learned. THE HEADLINE: this was overwhelmingly a RUN problem, not a
BUILD problem. Every dark item but two already had a command built for it, and none of those commands had ever
been run in production. That is why the intelligence looked absent while being present.
WHAT WAS ACTUALLY RUN AND VERIFIED TONIGHT, in production
1. THE CORPUS IS NOW FULLY EMBEDDED. 6902 of 6902 opportunity_records, up from 6535. 367 backfilled.
GUARDED FIRST, because embedding with the wrong model silently corrupts the vector space and there would
be no error to see: cmd/verify-vector-model compared stored vectors against fresh embeddings of the same
text and returned cosine 1.000000 on five samples, PASS at qllama/bge-small-en-v1.5 dim 384. Only then
was the backfill run, with -only-missing so it could touch nothing that already had a vector.
VERIFIED AFTER, three ways: 0 rows still missing, 6858 DISTINCT non zero vectors so nothing was zero
filled, and a nearest neighbour query seeded from an elevator record returned three Elevator
Modernization notices first. The vector space answers.
2. THE FEDERAL SIGNAL CLASSIFICATION PASS RAN FOR THE FIRST TIME. cmd/missionclass-sync, WS7.
federal_mission_tags 0 to 84. award_incumbent_roles 0 to 77. 10065 records scanned, which is exactly
6902 opportunity_records plus 2625 sam_opportunities plus 538 usaspending_awards.
AND IT MINES SAM ATTACHMENTS ITSELF, which corrects D20 again: it fetches the attachment package,
extracts text, and classifies from it, bounded by -max-attachments to protect the SAM quota. First pass
50 notices, second 300, and a full pass over the remaining 2325 is running in the background.
It also names what it cannot read rather than guessing, logging skipped unreadable attachments per
record, which is the same honesty posture as the Phase 2 extractor.
3. IT NOW LEARNS ON A SCHEDULE INSTEAD OF WHEN I RUN IT. mm-missionclass.timer installed and enabled on
Bubbles, nightly at 04:20 with a 600 second jitter, ExecStart capped at 300 attachments per run. The cap
is deliberate: the job is idempotent and self limiting, so a nightly cadence works through the backlog
instead of spending a whole day's SAM quota in one pass and failing the rest of the way through. It runs
AFTER the nightly SAM sync so it classifies what arrived tonight, not what arrived yesterday.
The unit carries TWO EnvironmentFile lines and ZERO inline Environment= lines, so no credential is
embedded and none is passed as an argument. The out of repo secret sweep from finding four picked the new
unit up automatically, because its scope is a glob over /etc/systemd/system/mm-*.service, and reported
15 of 15 scopes clean. That is the sweep earning its keep on a unit written after it.
WHAT REMAINS, split honestly into runs and builds
RUNS, the command exists and has never been run in production
teach-aggregate the WS8 teaching loop cadence. teaching_signal_aggregates 0, curation_proposals 0.
aioe-backfill, aioe-run converts decision history to outcome_events, then one deterministic AIOE pass.
All three aioe tables are 0. Production has almost no decision history, so this
one may be correctly empty until tenant data exists. Check before forcing it.
tag-missions tenant mission tagging. Same caveat: production holds 11 candidates.
ontology-sync, grow the ontology further from free federal sources and O*NET.
promote-ontology
train-model, eval-model retrain on the grown corpus, then the promotion gate. Do NOT promote a champion
without eval-model passing the ranking and four fifths gates AND a human.
brain-probe stress tests the LIVE brain end to end. Run it LAST, as the acceptance check.
BUILDS, small, and nothing does them today
A. FETCH THE NOTICE BODY. 2625 description_url values, every one 94 characters and http prefixed. The
column is stored, selected and upserted and NEVER FETCHED. This is the single highest ratio of value to
risk left: it turns 168 bytes of header per notice into real notice text for the embedder, the
classifier, and REQUIREMENT_NORMALIZE.
B. WRITE ontology_crosswalk. It is READ by G3's skill gap bridge, which joins it on soc_code, and it is
allowlisted in safety.go, and NO code writes it. So G3's taxonomy bridge traversal has nothing to walk.
AND THREE SMALL FIXES
C. Mount /api/knowledge/manifest and /api/knowledge/cards. Both 404 in production.
D. Teach the curated CAG cards into production. /api/mm-recall is live and returns
{"results":[],"count":0} for an ordinary query.
E. FIX THE mm-recall CONTROL. Its documented guarantee is that a protected attribute query returns no
cards. That cannot be distinguished from an empty knowledge base, and today the base IS empty, so the
control passes while proving nothing. It must assert that an ORDINARY query returns cards in the same
run that asserts a protected attribute query returns none. Do this WITH D, not after it. | this commit | 2026-07-29
INTELLIGENCE LAYER INVENTORY, 29 July 2026, ordered by Joseph after he said the build already has its own
intelligence and that two days had been spent drifting. He was right on both counts. This is a full scan of
every file and every database, and it CORRECTS finding D20 above, which was written from the production tenant
alone and drew the wrong conclusion from a half empty database.
WHAT IS BUILT. internal/modules/ai is 11059 lines, the LARGEST module in a 112469 line Go codebase, and it is
a complete augmented generation architecture, not a wrapper around a model call.
SIX ROUTES the orchestrator chooses between: refuse (eeo and injection), tag (the database computes), rag
(retrieve and narrate), direct (CAG, answer from stable rules with no retrieval), graph (walk a cited
evidence path), plan (bounded plan and execute over the closed Registry).
FOUR AUGMENTATION CLASSES: RAG, TAG, GraphRAG, MAG. MAG is model augmented generation, the fourth class.
NINE NAMED GRAPH PLANES, each a reviewed static SQL constant with a depth cap, a cycle guard and a total
order tiebreaker: G1 market and relationship, G2 vendor co occurrence and teaming, G3 skill and role
adjacency, G4 bench, G5 clearance lattice, G6 the integrated capture loop, G7 agency buying pattern,
G9 recompete lifecycle and foresight, M5 price to win on real BLS OEWS wages.
35 INTENTS in ONE closed Registry, tag.go:112 to 585, asserted at boot by ValidateRegistry from
cmd/api/main.go:367. 11 of them are MAG model intents.
THE AEDT GUARDRAIL HOLDS IN THE TYPE SYSTEM. A MAG intent may emit exactly three output kinds,
opportunity_score, display_order, confidence. A candidate score is impossible to register, impossible to
store because it is also the CHECK on aedt_learned_models.output_kind, and impossible to emit. The frozen
v1.4.0 engine stays the only per candidate scorer.
Plus a planner, a teaching and curation loop, per tenant episodic and semantic memory, a vector model guard,
small cell suppression, mission scoping, capture readiness, vendor intelligence, and a one way public
knowledge feed.
WHAT IS TRAINED, in the PRODUCTION tenant, all exact counts
aedt_learned_models 12, aedt_model_versions 5, aedt_model_coefficients 101.
FOUR MODEL FAMILIES, all fit by cmd/train-model on real labels, not placeholders:
opportunity_win_probability logistic, champion v1, prior only, intercept is the logit of a defensible
0.60 federal recompete base rate
requirement_time_to_fill discrete hazard, champion v1, challengers v2 to v4 fit over 415 requirements
opportunity_capture_readiness weighted scorecard, champion v1, factor weights 0.35/0.30/0.20/0.15,
GO at 0.65, REVIEW 0.45 to 0.65, NO_GO below
slate_display_order pairwise logistic, champion v6, fit over 46 decided slates and 2316 approved
over rejected pairs, ridge l2=1. Earlier fit used 93 slates and 1138 pairs.
Champion promotion is gated: challengers sit champion=false until cmd/eval-model passes the ranking and four
fifths gates AND a human promotes. That gate is real and is holding.
THE DATA SUBSTRATE, PRODUCTION, exact
audit_logs 56577, ontology_terms 8011, opportunity_records 6902 with 6535 EMBEDDED, sam_opportunities 2625,
solicitation_extractions 2457, bls_wages 1670, usaspending_awards 538, ai_memory 287 ALL embedded,
skills 267 ALL embedded, term_expansions 204, aedt_model_coefficients 101, mission_areas 10, mission_spine 10,
skill_aliases 17, skill_relationships 11. 41 of 86 tables populated.
ontology_terms by kind: skill 3066, psc_code 2540, ksa 2286, certification 57, work_role 42, labor_category 20.
THE CORRECTION TO D20, AND IT IS A BIG ONE. STAGING IS WHERE THE SYSTEM ACTUALLY LIVES.
staging: candidates 8872, organizations 2814, users 1105, slate_members 2070, candidate_evaluations 1777,
candidate_skills 1515, outcome_events 1411, vendor_submissions 1385, application_stage_events 1382,
bid_outcomes 1220, requirements 1184, requisition_events 1188, requisitions 1082, applications 677,
recruiter_activities 612, slates 496, aedt_model_coefficients 433, skills 397,
AND opportunity_document_chunks 836.
production: candidates 11, organizations 1, users 7, requirements 6, slates 13, document chunks 0.
SO THE PHASE 2 DOCUMENT PIPELINE IS NOT DARK. It has 836 chunks in staging. What is empty is PRODUCTION,
and production is empty of TENANT data generally, not of documents specifically. D20's headline, that the
document pipeline has processed nothing, is true only of production and reads as though it were true of the
build. Withdrawn as stated and restated here.
WHAT IS GENUINELY DARK, and this is the useful list
1. THE PUBLIC KNOWLEDGE BASE IS EMPTY IN PRODUCTION. /api/mm-recall is LIVE and unauthenticated by design,
a contents free federation edge, and it returns {"results":[],"count":0} for an ordinary query. The
curated CAG cards in knowledge.go were never taught into production. Worse, the documented control that a
protected attribute query returns no cards CANNOT DISCRIMINATE while everything returns no cards. That is
the same failure mode as every other instrument caught this week.
2. /api/knowledge/manifest and /api/knowledge/cards return 404 in production. Not mounted.
3. solicitation_extractions holds 2457 rows grounded to 2457 distinct opportunity_records, 2163 of them ok,
carrying labor_category, required_clearance, required_skills, required_certifications and required_years,
AND NO GO CODE REFERENCES THE TABLE AT ALL. Only migration 0060 mentions it. Written by code that no
longer exists, read by nothing. That is extracted intelligence, orphaned.
4. The teaching loop is dark in production: teaching_signal_aggregates 0, curation_proposals 0.
5. AIOE is dark: aioe_runs 0, aioe_weight_profiles 0, aioe_weight_adjustments 0.
6. ontology_crosswalk is 0, so G3's skill gap bridge traversal has no crosswalk taxonomy to walk.
7. Mission tagging is dark: candidate_mission_tags 0, requirement_mission_tags 0, federal_mission_tags 0,
even though mission_areas and mission_spine each hold 10 curated rows.
8. 367 of 6902 opportunity_records carry no embedding.
9. The two unfetched link sets from D20 still stand and are still the deepest gap in the MARKET corpus:
2625 notice description URLs and 5363 attachment links, none ever followed. Verified again: every
description value is 94 characters and begins with http, so no notice body has ever been fetched.
10. nginx reports 34 CONFLICTING SERVER NAME warnings across the vhost set, caused by backup files left
inside sites-enabled where the glob loads them. missionmatch.thatwebhostingguy.com has such a .bak
sibling. Not a missionMATCH defect and it affects missionMATCH.
WHAT I GOT WRONG, named so it is not repeated
I probed the brain against missionmatch.thatdevpro.com, which is not the hostname, got 000 on every route,
and was one sentence away from reporting the brain unreachable in production. The real host is
missionmatch.thatwebhostingguy.com and /api/ai/query answers 403, meaning MOUNTED AND AUTH GATED. Caught by
running a control first: / answers 200 and /api/auth/session answers 403. A check with no control is a
rumour.
I also read the first inventory off pg_stat_user_tables.n_live_tup, which reported skills 0 and
ontology_terms 0 when exact counts are 267 and 8011. Stale planner statistics. Every number above is an
exact count(*), taken through query_to_xml per table. | this commit | 2026-07-29
FINDING D20, THE INTELLIGENCE LEARNS FROM HEADERS, AND THE DOCUMENTS IT WAS BUILT TO READ ARE UNREAD IN
PRODUCTION | 29 July 2026. Found by measuring the ingest and embed loop after Joseph pointed out that the whole
point is to ingest SAM.gov, embed it, and grow from it. He was right, and the measurement is worse than the
question implied. Every number below is from the PRODUCTION tenant, read only.
WHAT IS GENUINELY GROWING, and it is not nothing
opportunity_records 6902 rows, 6535 EMBEDDED, 94.7 percent
ontology_terms 8011
skills 267, ALL embedded
ai_memory 287, ALL embedded
sam_opportunities 2625 notices cached
So the market scale ingest and the vector substrate are real and working.
WHAT IT IS LEARNING FROM, measured rather than assumed
The deepest text any SAM notice contributes is 75 to 445 BYTES, average 168, composed from the flat metadata
columns: title, department, sub agency, notice type, NAICS, PSC, set aside, place of performance. That is a
HEADER. It is not a solicitation.
WHAT IS SITTING IN THE DATABASE UNREAD, with the pointers already stored
2625 notice DESCRIPTION URLS. The cache column is description_url and the raw jsonb description key is also a
URL, verified: all 2625 values are exactly 94 characters and every one begins with http. The notice BODY
has never been fetched. One HTTP GET per notice would produce it.
5363 ATTACHMENT LINKS across 1484 notices, all URL shaped, none ever followed.
RESULT: opportunity_documents 0. opportunity_document_chunks 0. opportunity_scope_items 0. ZERO.
SO THE ENTIRE PHASE 2 DOCUMENT PIPELINE HAS PROCESSED NOTHING IN PRODUCTION. Table extraction, the readability
bar, chunking, provenance born at origin, the amendment diff, the split and merge, the draft generator. All of
it is PROVEN, on the real Package A and Package B, in STAGING. In production it has no fuel, because nothing
follows either of the two link types the database already holds. Only 1 opportunity is tracked, and tier one
attachment sync is track driven by design, so it can only ever pull for what a customer already found.
THIS REFRAMES FINDING THREE ENTIRELY, and the reframing is mine to own. I spent an evening trying to make an
external model extract contract scope from 168 bytes of header metadata. Even had OpenAI answered, it would
have been reading "Elevator Preventive Maintenance. Department: VA. NAICS: 238290." That is why each of the
three notices yielded exactly ONE scope item and why the payloads were 237, 117 and 114 bytes. The bottleneck
was never the model. It was that the text was never fetched.
ALSO FOUND, smaller
367 opportunity_records carry NO embedding, 6902 minus 6535.
ontology_crosswalk is EMPTY, 0 rows, so the crosswalk has never been populated.
WHAT THIS IS AND IS NOT. It is NOT a defect in Phase 2's scope: the phase was to build the document pipeline
and the pipeline works. It IS the gap between a pipeline that works and a product that knows things, and it is
the same lesson already recorded as depth equals completeness. NOTHING WAS BUILT FOR IT IN THIS SESSION, and
nothing should be: it is a scope decision under a phase lock and it belongs to Joseph.
THE ORDER OF VALUE, if he wants it, cheapest and most local first
1. Follow the 2625 description URLs. Local, no third party, no new dependency, turns 168 bytes per notice into
real notice text for the embedder and for REQUIREMENT_NORMALIZE. This is the single highest ratio of value
to risk available in this codebase right now.
2. Follow the 5363 attachment links into the pipeline that already exists and is already proven. This is
IMPROVEMENTS item 2, tier two the rolling corpus harvest, approved and NOT STARTED, and its recorded
caveat stands: it needs a storage budget with a ceiling and a retention rule first, because Bubbles is at
82 percent of a 221G root disk and the backups live on that same disk.
3. Embed the 367 stragglers and populate ontology_crosswalk.
None of the three needs an external model. | this commit | 2026-07-29
FINDING D19, THE EXTERNAL INFERENCE AUDIT ACTION WAS DECLARED AND NEVER WRITTEN | 29 July 2026, found while
executing finding three. ai.ActionExternalInference has existed since 27 July, and AllowExternal carried a
comment stating that binding the gate and the audit in one call means a caller cannot send without both.
THE BINDING WAS NEVER REAL. AllowExternal returned a map and trusted its caller to record it, and the only
caller in the codebase wrote that map to a LOG LINE and persisted nothing.
So the single class of call that physically leaves this machine was the ONLY class with no audit trail, for
two days, while FIVE UNIT TESTS PASSED. They passed because every one of them asserts the SHAPE OF A MAP. Not
one asked whether a row existed. This is the eleventh instance in this build of an instrument reporting a
clean result while measuring something adjacent to the thing it claims to measure.
FIXED. AllowExternalAudited gates and writes as ONE call and FAILS CLOSED: an external call whose audit row
cannot be written does not happen, which is the posture the auth module already takes for security events. The
row is written BEFORE the payload leaves, the only ordering an auditor can trust, because a row written