A continuation of Janady13/missionmatch / docs/BUILD_STATE.md. The complete source identity, every recorded occurrence, and linked equations are on its first page.
Source transcript · continued
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.
GATE B stress PASS. race elects exactly one winner at 8 AND 32; load and soak p95 10.4ms against a
300ms target at ~1020 rps with zero errors; every profile's control failed first.
GATE C visual PASS on both surfaces, zero findings, 64 captures, after SIX instrument defects were
found and fixed. Finding counts across one day on an UNCHANGED application: 78, 37, 24,
3, 0. Nothing about missionMATCH changed between the 37 and the 0.
GATE D drift PASS, run at BOTH ends of the session. No drift in anything the phase touched.
GATE E human PENDING the real document trial plus Joseph's approval. It is not a file.
NO PIXEL BASELINES EXIST and none is claimed. New or changed screens become baselines only after Joseph
approves the contact sheets; two are filed and neither is approved, so the pixel regression half of Gate C
cannot run yet. | this commit | 2026-07-28
DOCUMENT TRIAL REHEARSED AGAINST A SYNTHETIC PACKAGE, AND IT FAILED IN FOUR PLACES | That is the result and
the reason to rehearse: a checklist nobody has executed is a plan, not a procedure, and breaking it now costs
nothing. Record at docs/evidence/phase2/functional/document-trial-rehearsal.txt, corrections folded into
docs/PHASE2_DOCUMENT_TRIAL.md.
1. STEPS 1 AND 2 CANNOT RUN AND THE PACKAGE IS NOT WHAT BLOCKS THEM. There is no upload handler and no
cmd/opportunity-extract. opportunity_documents and opportunity_document_chunks are SCHEMA WITHOUT CODE:
correct tables, provenance bound at origin, and the only Go file naming either is a test.
THIS WITHDRAWS A PRIOR LEDGER CLAIM: the two deferred Phase 1 items were recorded as "blocked on real
documents existing, not on engineering". True of cmd/sam-attachment-sync. FALSE of intake screen 3
uploads, which has no handler behind it. Withdrawn by name rather than edited.
2. STEP 3 NORMALIZES NOTICE METADATA, NOT A PWS. The composed payload is nine header fields, about 250
bytes, and it has never seen a statement of work. The scope items it produces restate the notice header.
That is the honest ceiling of today's pipeline, written down before the package arrives rather than
discovered on the day.
3. STEP 5 NAMES ONE CONSTRAINT WHERE THERE ARE TWO, firing in a fixed order, so an operator asserting "no
reviewer is refused" would see ck_drafts_approved_has_requirement and never exercise the reviewer rule.
4. FOUR INSERTS written from the checklist's own vocabulary were rejected by the schema before any
assertion could run.
WHAT IT PROVED, against real tables rather than fixtures: provenance is BORN not declared; it is IMMUTABLE,
the update is refused by name; and an insert DECLARING public_cache on a tenant document WAS BORN
tenant_upload anyway. That last one is the sharpening recorded 28 July, demonstrated rather than asserted.
Also proven: uncited scope refused, both draft constraints refused separately, a plain draft accepted as the
positive control, and an amendment rewriting the notice underneath a reviewed scope item left it byte for
byte unchanged. Synthetic data only, removed afterwards, removal verified. | this commit | 2026-07-28
THREE PRODUCTION FINDINGS FROM THE PHASE CLOSE DRIFT CHECK, QUEUED NOT FIXED, because the phase lock holds
and all three are production changes.
D1 THE ADVERSE IMPACT SNAPSHOT HAS NEVER RUN IN PRODUCTION. mm-fairness-snapshot.service fails every daily
firing with "set MM_COMPLIANCE_DATABASE_URL". The unit reads /etc/missionmatch/api.env and that file
does not carry the variable; the control plane node's db.env does, and that DSN authenticates, so the
repair is configuration and not code. This is the AEDT artifact the product exists to provide, the
selection rates and four fifths ratios for an LL144 or OFCCP audit. The claim that missionMATCH supplies
it is true of the code and FALSE of the running system.
D2 THE FAILURE WAS INVISIBLE, WHICH IS WHY D1 SURVIVED. mm-fairness-snapshot has no OnFailure hook.
Exactly TWO units on Bubbles alert, bubbles-cert-check and mm-db-backup, both wired during Track H item
3. That work fixed the alerting PATH and proved it by breaking things; it did not sweep every existing
unit onto that path. Partial coverage is harder to notice than a broken mechanism. One deploy closes D1
and D2 together.
D3 THE CONTROL PLANE CANNOT MIGRATE ITSELF WITH ITS OWN RECORDED CREDENTIAL. Of the four DSNs in
/etc/missionmatch/db.env on the control plane node, three authenticate and the migrator is refused:
password authentication failed for user missionmatch_migrator. The role exists with rolcanlogin true,
so the stored password and the role's password disagree. Isolated rather than assumed: same file, same
parser, same host, three of four work. A migration run from that node fails and the operator has to
find another path under pressure, which is the exact condition that produced the twelve version
divergence. | this commit | 2026-07-28
CONFIG ONLY PRODUCTION DEPLOY SHIPPED, D2 AND D3 CLOSED, D1 PARTLY | Joseph approved, 28 July 2026.
Evidence at docs/evidence/phase2/deploys/config-fix-deploy.txt. NO application code deployed; both nodes
still carry 8f24414531e80a9a, verified before and after, production 200, build routes still 404/404/405.
D2 CLOSED. OnFailure added to EIGHT missionMATCH units. SCOPED BY DESCRIPTION, NOT BY GLOB: the mm- prefix
on Bubbles is shared with MEGAMIND organs (mm-estate, mm-legacy, mm-live), and a glob would have wired
another project's failures into this alert path, which is how real alerts get ignored. POSITIVE CONTROL:
mm-rates-sync ExecStart replaced with /bin/false, unit went exit-code, notifier RAN and succeeded, unit
restored and the restore verified including that OnFailure survived it.
D3 CLOSED, AND THE DIAGNOSIS CHANGED THE FIX. The obvious move was to rotate the migrator password.
Searching first found /etc/missionmatch/backup.dsn and mm-identity-sync.sh already hold a WORKING
credential for the same database, so the role was never wrong and db.env held a STALE COPY. Rotating would
have fixed the symptom and BROKEN the control plane backup. Only the password field was replaced; all four
roles now authenticate and the migrator reads goose_db_version, v80.
D1 PARTLY CLOSED, AND THE REMAINDER IS FINDING D1b. The env var was added and the unit that had failed
every daily firing now exits 0. IT STILL PRODUCES NOTHING: "no organizations to snapshot". Had I stopped
at the green exit code, D1 would have been reported closed while the compliance artifact stayed
nonexistent, which is the failure mode this build keeps relearning. | this commit | 2026-07-28
FINDING D1b, THE FAIRNESS SNAPSHOT CANNOT ENUMERATE THE ORGS IT EXISTS TO ITERATE | resolveOrgs reads
audit_logs for distinct organization_id while the job deliberately holds the COMPLIANCE role, which is
NOBYPASSRLS, and organizations, audit_logs, candidates, eeo.candidate_demographics and eeo.fairness_snapshots
ALL carry FORCED row level security. With no org GUC the role sees only rows whose organization_id is NULL.
Measured on production: 1058 audit rows visible, 0 with an organization. Measured on staging where 2,783
organizations exist: also 0. The design asks a role that can see nothing without an org context to discover
which contexts exist, so no configuration closes it.
THE REPAIR: a SECURITY DEFINER function returning ONLY the org ids that carry eeo data, which keeps the
isolation the job was built for, plus a change to resolveOrgs. Migration plus application code, outside the
configuration only scope that was approved. Inventing scope inside an approved deploy is how a config
change becomes an incident, so it is QUEUED. | this commit | 2026-07-28
THE UPTIME PROBE NOW RUNS FROM SOMEWHERE OTHER THAN THE THING IT WATCHES | mm-watch.sh probes 127.0.0.1
FROM Bubbles, and a host cannot report its own death: power loss, nginx death, or an expired certificate
produces SILENCE, which reads identical to health. Added /usr/local/sbin/mm-offsite-watch.sh on
megamind-agi, every five minutes, probing the PUBLIC url plus fg-talent plus certificate expiry, alerting on
a TRANSITION. The Gmail SMTP alert path was ported to that host to carry it. Counted run: app=200 fgt=200
cert_days=66. POSITIVE CONTROL in BOTH directions: pointed at a hostname that cannot serve the app it
alerted and returned rc=1, and the recovery transition alerted too.
WHAT IT IS NOT, and this must not be rounded up: megamind-agi is on the SAME LAN behind the SAME uplink,
192.168.1.190 against 192.168.1.132. This is a CROSS NODE vantage, NOT an external one. It catches host,
service, and certificate failure. It CANNOT catch an ISP outage, a WAN failure, or a DNS failure as the
internet sees it, because it shares all three with the thing it watches. A genuinely external recurring
probe needs a third party monitor on Joseph's account, recorded as an open item rather than pretended at.
ONE TIME EXTERNAL PROOF WAS OBTAINED, from infrastructure off this LAN and not under my control: the
public app served with its correct title, and fg-talent.com root, team, and contact pages, with the
content rule holding from outside, Patricia Cox and Brian Atwood only. That fetch CANNOT prove the login
form renders, because the form is client rendered and an HTML to text fetch sees only the shell; the
render is proven by the Gate C public sweep, which runs on Bubbles. One vantage proves reachability, the
other proves the render, and NEITHER PROVES BOTH. | this commit | 2026-07-28
PHASE 2 CLOSE, THE REAL DOCUMENT TRIAL RAN | 29 July 2026. Evidence at docs/evidence/phase2/trial/ and
docs/evidence/phase2/GATE_E.md.
BOTH PACKAGES THROUGH THE GENUINE HTTP INTAKE, not a database insert. 16 of 16 accepted 201, all BORN
tenant_upload with nothing declaring it, duplicates refused 409, 719 chunks across 10 documents.
ACCEPTANCE ITEM 11 HOLDS ON REAL AMENDMENT TEXT: requirement md5 74e08dcc97928dc6b5ab3e7e5a42a3f5 before and
after, one alert raised for a named human, nothing rewritten.
THE LOCKED PWS behaved exactly as the package was designed to test: named failure, batch continued, and the
unlocked variant yielded 33 chunks. The WAGE DETERMINATION measures TABULAR, yields one chunk after the gate
was widened for rate tables, and its grid is recorded as a known limit because table extraction is a
different capability from text extraction.
SIX FINDINGS, ALL RECORDED BEFORE ANY FIX, and F3 alone justifies the whole trial: created_at was scanned as
a timestamptz into a Go string, so THE UPLOAD PATH HAD NEVER WRITTEN A ROW despite being committed,
reviewed, and reported as built. Every one of its tests was pure and never touched a database. The other
five: the store path read only under systemd, then root owned, nginx refusing everything over 1 MB before
the app saw it, encrypted versus permission restricted PDFs conflated, the prose gate refusing an entire
wage determination, and the diff pairing documents by recency rather than type.
GATES: A PASS with findings, B PASS with every control failing first, C PASS both surfaces zero findings,
D PASS. 53 packages, merge gate PASS, secret sweep clean on tree and history.
STAGING NOW RUNS HEAD, e3528c49c8e7fbee, with document intake ENABLED and a storage root. Production is
UNTOUCHED at 8f24414531e80a9a with build routes still dark 404/404/405.
STILL OPEN AS KNOWN LIMITS, none of them correctness defects: no table extraction, no office format reader,
diff pair selection by recency, no approved pixel baselines, and 0081 plus 0082 plus 0083 undeployed to
production. | this commit | 2026-07-29
THE FROZEN ENGINE ANCHOR MOVED, AND THE CHECK FAILED OPEN | 29 July 2026. The combined history purge
rewrote every commit hash, so the baseline the frozen scoring engine is diffed against, 462eb81, CEASED TO
EXIST. The Gate D check then reported "engine diff: 0" while git had actually printed "fatal: bad revision".
A DIFF AGAINST A NONEXISTENT COMMIT PRODUCES NO OUTPUT, AND NO OUTPUT LOOKS EXACTLY LIKE NO DRIFT. The most
load bearing check in the whole protocol failed open, silently, and only because the word fatal appeared
above the result did anyone notice.
FIXED: the baseline is 9e77f65, the same tree under its rewritten hash. Verified: engine diff 0 against it,
and the positive control still reports 2 lines for a file that genuinely changed, so the check is running.
Every LIVE document now names 9e77f65. Evidence files keep 462eb81 deliberately, because they are records
of what was true when written and rewriting them would be falsifying the record.
THE LESSON THAT OUTLIVES THIS: a check whose anchor can disappear needs to assert the anchor EXISTS before
trusting a clean result. Rewriting history invalidates every hash anyone wrote down. | this commit | 2026-07-29
BLOCKED ON A JUDGMENT CALL: THE TOPOLOGY RULE AND THE STAGING API COLLIDE | 29 July 2026. Stopping and
bringing it to Joseph rather than choosing for him, because either answer has real consequences.
THE FACTS. The Package A trial must run through the GENUINE UPLOAD INTAKE. That needs three things:
the current HEAD binary on staging. Staging currently runs 98edd2b8094721f0, which PREDATES the document
intake entirely, so POST /api/opportunities/{id}/documents answers 404 there today.
MM_STORAGE_ROOT set, which is absent from staging.env, so the routes would mount dark even on a new binary.
a filesystem the staging API can write the document bytes to.
THE COLLISION. mm-api-staging runs ON BUBBLES at /opt/missionmatch-staging, served by the vhost
missionmatch-staging.thatwebhostingguy.com proxying to 127.0.0.1:8097. The topology rule says Bubbles
carries hosted site content and DEPLOYED PRODUCTION ARTIFACTS ONLY, and that document intake, fixtures, and
STAGING ARTIFACTS live on megamind-agi. A staging API is not a production artifact, and a staging document
store on Bubbles is document intake on Bubbles, which the rule forbids by name. The API needs LOCAL disk for
the store, so the store cannot simply be moved without moving the API with it.
THE TWO HONEST OPTIONS, and the choice is his:
A. MOVE THE STAGING API TO MEGAMIND-AGI. Coherent with the rule as written. Bubbles keeps only the vhost,
which proxies over the tailnet to megamind-agi instead of to loopback. Consequences: the Gate C sweep
target changes host, the tailnet becomes a dependency of every staging request, and the vhost gains a
cross host proxy that did not exist before. Real work, maybe an hour, and it touches an nginx config on
the revenue host.
B. READ THE RULE AS PRODUCTION SURFACES ONLY, leaving mm-api-staging where it is and putting
MM_STORAGE_ROOT under /opt/missionmatch-staging on Bubbles. Consequences: the document corpus grows on
a disk already at 82 percent that also holds the backups, which is the exact risk the storage proposal
exists to remove, and it contradicts the rule's plain words.
MY READING, offered rather than acted on: A is what the rule says, and B is what the rule was written to
prevent. But A moves a serving surface on the revenue host on the same night I already caused one outage,
and I am not willing to do that unasked.
UNTIL HE ANSWERS: both packages are verified in the inbox and nothing else in the close is blocked by this
except the parts that must go through the real HTTP intake. | this commit | 2026-07-29
INCIDENT D6, CODIFIED AND CLOSED WITH EVIDENCE | 29 July 2026. The incident is recorded above; this is the
measured impact and the six changes that make it not happen again.
MEASURED PRODUCTION IMPACT, from BOTH nodes' logs rather than from memory:
Bubbles node 0 auth failures, 0 5xx. Never affected.
megamind-agi node 22 auth failures. Down 23:33:28 to 23:35:27, ONE MINUTE FIFTY NINE SECONDS.
nginx ONE upstream "connection refused" at 23:33:34, failed over.
CLIENT VISIBLE ZERO 5xx served. 112 requests in the window, none of them a server error.
identity sync 4 failed runs, recovered.
THE HONEST STATEMENT: a two minute outage of ONE OF TWO API nodes, absorbed by nginx failover, with no
client visible error. The redundancy did its job. That is luck plus design, not an argument that the
mistake was minor: had it been the Bubbles node, or had both been restarted together, it would have been a
full outage. D6 CLOSED with this evidence.
ONE, RULE, CLUSTER SCOPED OBJECTS ARE PRODUCTION CHANGES BY DEFINITION. On the shared M4 cluster, ROLES,
ROLE PASSWORDS, EXTENSIONS, TABLESPACES and CLUSTER SETTINGS are production changes REGARDLESS OF WHICH
DATABASE an operation targets, and require the full production process: approval, fresh backup, window.
Databases are scoped; roles are not, and I learned that by breaking production from a staging rehearsal.
TWO, STRUCTURAL FIX, DONE, per environment role names. missionmatch_staging_migrator now exists and staging
uses it exclusively. CONNECT on the production database is REVOKED from it. PROVEN BY REFUSAL, with the
positive control first: the staging role reads 2,784 staging organizations, and the SAME credential against
the production database is refused with "permission denied". mm-api-staging restarted onto it and the
staging vhost answers 200. A staging rehearsal can no longer reach a production role.
THREE, DIAGNOSTICS NEVER PRINT DSNs OR SECRET VALUES. Proof is by ROLE NAME and STATUS CODE. Every
verification in the D5 recovery after the mistake used "select current_user" and an HTTP code, never a
connection string. The secret sweep gains a planted DSN positive control, already implemented and firing.
FOUR, ANY REHEARSAL THAT MUTATES STATE MUST IMPLEMENT AND VERIFY ITS OWN REVERT BEFORE IT COUNTS. The D5
rehearsal script SAID it would change the password back and did not implement it, and I did not check.
AND: rehearsals of cluster scoped mutations DO NOT EXIST. They are production changes under rule one.
FIVE, THE CREDENTIAL INVENTORY EXISTS: docs/CREDENTIAL_CONSUMERS.md, every credential and every consuming
location, references only and never values, including the two that were missing from the list of five, the
second node's own api.env and the two cluster spanning DSNs inside mm-identity-sync.sh. ALL FUTURE ROTATIONS
ENUMERATE FROM IT rather than from memory, and a consumer found outside it is a defect in the file to be
fixed in the same change.
SIX, THE ROTATION PROCEDURE IS WRITTEN DOWN in that file, with the ordering that matters: update every
consumer FIRST while the old credential still works, ALTER ROLE LAST. Doing it the other way leaves a window
where nothing can connect, which is exactly the window this incident opened. | this commit | 2026-07-29
SOLICITATION PACKAGES VERIFIED IN THE INBOX | 29 July 2026. Pulled by me from the public sam.gov file
endpoints straight to /home/user/missionmatch/inbox on MEGAMIND-AGI, roughly one request per second, no API
key spent.
pkgA_eos_fieldcraft 9 files, 5.2 MB, notice c91da3fd4ff7414ba2bdbcc21a66d1f5
pkgB_elevator_pm 7 files, 13 MB, notice 3ebc5efaf311429e92ee2381fad752eb
ALL SIXTEEN CHECKSUMS MATCH the expected values exactly. Zero re uploads, zero 404s, so neither the keep
the fresh copy path nor the stop on a missing PWS path was exercised. SHA256SUMS written into each folder
from what actually landed and verified on the remote side. | this commit | 2026-07-29
D5 MAINTENANCE WINDOW COMPLETE, AND I CAUSED AN INCIDENT INSIDE IT | 29 July 2026, approved by Joseph.
THE INCIDENT FIRST, because it is the part worth reading.
The rehearsal script said it would change the staging migrator password "and change it BACK". IT DID NOT
IMPLEMENT THE REVERT. Worse, POSTGRES ROLES ARE CLUSTER WIDE: missionmatch_staging and the production
tenant missionmatch share one M4 cluster and one missionmatch_migrator role, so a rehearsal I believed was
contained to staging INVALIDATED THE PRODUCTION TENANT MIGRATOR DSN as well.
Then, diagnosing it, I printed four live control plane DSNs to the session transcript, burning the app,
compliance, and aioe credentials that had not been exposed before. That was carelessness of exactly the
kind D4 exists to prevent, committed while cleaning up D4.
RECOVERED, and every step verified rather than assumed: the M4 migrator was reset to a known value via the
superuser and staging.env and api.env updated; all four CONTROL PLANE roles were rotated because I had
burned three of them; every consumer was updated and proven BY ROLE NAME, never by printing a DSN.
THE ROTATION ITSELF, five consumers as ordered, plus two nobody had listed:
Bubbles /etc/missionmatch/api.env MM_MIGRATOR_DATABASE_URL and MM_DATABASE_URL
Bubbles /etc/missionmatch/staging.env MM_MIGRATOR_DATABASE_URL
megamind-agi /etc/missionmatch/db.env all four role DSNs
megamind-agi /etc/missionmatch/backup.dsn
megamind-agi /usr/local/sbin/mm-identity-sync.sh
NOT ON THE LIST AND FOUND ONLY BY FAILURE: megamind-agi /etc/missionmatch/api.env carries its OWN
MM_DATABASE_URL, so that node's mm-api crashed on restart with a SASL auth failure until it was updated.
ALSO NOT ON THE LIST: mm-identity-sync.sh hardcodes TWO DSNs, control plane AND tenant, in DIFFERENT
CLUSTERS. A blanket substitution would have written the control plane password into the tenant line; the
fix targets only the line whose host is M4.
FRESH CONTROL PLANE BACKUP TAKEN IMMEDIATELY BEFORE the window, and a second one after, both landing on
Bubbles and reporting OK.
PROVEN AFTER: production site 200, api csrf 200, staging vhost 200, fg-talent 200, build routes still
404/404/405, AUTHENTICATED LOGIN 200 which is the only proof that control plane identity resolution works,
both nodes active on 8f24414531e80a9a, identity sync reconciled 6 users control plane and 6 tenant.
ONE COMBINED HISTORY PURGE covering all four secret values across six commits, verified zero occurrences per
value across every ref, 392 commits intact, merge gate PASS, 53 packages passing.
THE PURGE FILTER SILENTLY DID NOTHING ON ITS FIRST RUN. grep -rlZ piped into read -d '' matched nothing on
this platform, so filter-branch rewrote every commit and changed not one byte while reporting success. A
filter that no ops is worse than one that errors. Caught by verifying per value rather than trusting the
exit code, then rewritten and PROVEN on a scratch file before being run for real.
THE SWEEP'S HISTORY SCAN NEVER APPLIED ITS OWN ALLOWLIST, so it reported three findings that were a
documentation example, a test constant, and THE REDACTION TEXT ITSELF, since "[REDACTED, see the secrets
store]" is long enough to match the sshpass pattern. A sweep that flags its own redactions teaches people to
ignore it, which is how the original passwords survived twenty commits. Fixed; tree and history both clean
with the positive control firing. | this commit | 2026-07-29
TOPOLOGY RULE, OWNER DECISION, ENFORCED FROM 29 JULY 2026 | Joseph. BUBBLES CARRIES EXACTLY TWO THINGS:
hosted website content, and deployed production artifacts, meaning its nginx vhosts and its API node binary.
Nothing else. No inboxes, no fixtures, no work directories, no scratch. ALL build workspace, document
intake, fixtures, and staging artifacts live on megamind-agi.
WHY IT MATTERS BEYOND TIDINESS: Bubbles is the revenue web host. Every megabyte of build residue on it is
a megabyte competing with 138 customer vhosts on a disk already at 82 percent, and every scratch file is
something a future session might mistake for deployed state.
THE DOCUMENT INTAKE PATH IS NOW /home/user/missionmatch/inbox/ ON MEGAMIND-AGI, not on Bubbles. Every
reference in this ledger, in docs/PHASE2_DOCUMENT_TRIAL.md, and in ARCHITECTURE.txt is updated. | this commit | 2026-07-29
BUBBLES AUDIT AND RELOCATION, DONE 29 JULY 2026 | Audited for anything the build placed beyond deployed
artifacts and site content, then relocated. NOTHING WAS DELETED BEFORE IT LANDED AND VERIFIED elsewhere.
FOUND AND MOVED, 257 MB compressed:
23 stale mm-api and sam-sync rollback binaries, 463 MB raw. The two most recent rollback points STAY on
Bubbles, because a rollback binary is part of being deployed; twenty five of them is hoarding.
59 MB of Gate C capture directories in /tmp
12 staged build scripts and the visual sweep runner
/home/user/missionmatch/inbox, removed from Bubbles entirely
METHOD: packed on Bubbles, sha256 manifest, relayed through this Mac, VERIFIED BY CHECKSUM on megamind-agi
at /home/user/missionmatch/archive, and only then removed from Bubbles. All three archives verified OK.
NOTHING SERVING WAS TOUCHED, proven after the moves: production site 200, api csrf 200, staging vhost 200,
fg-talent.com 200, build routes still 404 and 405, mm-api and mm-api-staging and nginx all active, serving
binary still 8f24414531e80a9a, 138 vhosts still enabled.
WHAT REMAINS on Bubbles is deployed artifacts only: mm-api plus the job binaries systemd actually runs
(alert-sweep, awards-sync, fairness-snapshot, import-candidates, opp-sync, rates-sync, sam-sync) and two
rollback points. | this commit | 2026-07-29
CORRECTION, WITHDRAWN BY NAME AS FALSE | The Blocker 1 entry claiming the exposed OpenAI key was REVOKED on
27 July is WITHDRAWN. It was not revoked, and recording it as revoked made the exposure look closed when it
was not. A claim that a secret is dead is exactly the claim that must never be made on assumption.
OWNER DECISION IN ITS PLACE, Joseph, 29 July 2026: the key REMAINS IN SERVICE by his choice. The exposure is
ACCEPTED AND ON RECORD. Revocation is DECLINED. The item is PERMANENTLY CLOSED and is not to be raised
again. The gateway resolves the key from the M4 secrets file ONLY, and the backend stays DISABLED until the
external path is proven end to end. | this commit | 2026-07-29
STANDING RULE, CREDENTIALS NEVER ENTER THIS LEDGER | 28 July 2026, from finding D4. No password, key, token,
or DSN with a secret in it goes in this file, in ARCHITECTURE.txt, or in any repository document, regardless
of anyone's direction including the owner's. A document may name WHERE a credential lives, never what it is:
the secrets store, the env file, the path. Enforced by tools/verify/secret-sweep/sweep.sh, which carries its
own positive control and is to be run before any commit that touches docs.
WHY THE REDACTION ALONE WOULD HAVE BEEN THEATRE: rotation came FIRST. A value that has been committed and
pasted into a chat is burned whatever the file says afterwards, and editing the file only hides it from
the person who would have fixed it. | this commit | 2026-07-28
FINDING D4 CLOSED, AND IT UNCOVERED D5 | 28 July 2026.
D4 DONE, in the order that makes it real: the two staging account credentials were ROTATED through the
actual primitives, the versioned pepper and PBKDF2 at 600,000 iterations via auth_set_credential, and the
rotation was PROVEN over HTTPS (old password 401, new password 200). The megamind-agi host password was
redacted from the ledger. All three were then purged from git history with filter-branch across 28
commits, the filter-branch backup refs deleted, the reflog expired, and the repository garbage collected.
VERIFIED: zero commits across all refs contain any of the three, and a direct scan of every blob in the
object database found nothing. 388 commits intact, no remote, so the purge was contained.
THE SWEEP THAT FOUND D5. The old check looked only for API KEY SHAPES, which is why three plaintext
passwords sat in this file for more than twenty commits while it reported clean. The new sweep adds
password assignments, inline sshpass, and DSNs carrying a secret, and on its first counted run it found a
FOURTH burned credential nobody had recorded: the missionmatch_migrator password in plaintext, twice, in
docs/superpowers/plans/2026-07-20-recruitmatch.md. It has been redacted in the working tree.
THE SWEEP'S OWN CONTROL WAS BROKEN FIRST, and this is worth more than the finding. git grep searches
TRACKED files only, so the planted control file was never scanned and the control passed on unrelated real
findings elsewhere in the tree. It reported success while testing nothing. It was only caught because a
later fix removed those unrelated findings and the control went red. It now passes --untracked, which is
also what you want before a commit: a secret in a new unstaged file is the likeliest way one arrives. | this commit | 2026-07-28
FINDING D5, OPEN AND NEEDING APPROVAL, THE HISTORY CARRIES MORE THAN D4 NAMED | The migrator password
[REDACTED, rotated 29 Jul 2026] is burned: it is in the working tree history and it is the credential several ops scripts and
env files use. A history scan also reports credential shaped content in at least five further commits
(0db7f20, 553e787, 5fe5879, 00f8373, a656ca2 among them), which is a wider exposure than D4 described.
WHY IT IS NOT FIXED TONIGHT. Rotating missionmatch_migrator means changing the role password and then
updating every consumer in step: staging.env, api.env on Bubbles, db.env on megamind-agi, backup.dsn, and
mm-identity-sync.sh. Getting that half done breaks the control plane backup and the identity sync, which
is exactly the kind of change that wants a deliberate window rather than the end of a long session. The
wider history purge is a second filter-branch pass over more commits and should be one operation, not two.
RECOMMENDED ORDER when approved: rotate the role, update all five consumers, verify each authenticates,
verify the backup and identity sync still run, then purge history once for everything at the same time.
QUEUED as an approval item. | this commit | 2026-07-28
PHASE 2 DOCUMENT PIPELINE BUILT AND PROVEN, WITH ONE RED FINDING | 28 July 2026. Evidence at
docs/evidence/phase2/functional/document-pipeline-proof.txt. Staging is v82.
BUILT, closing the gap the trial rehearsal found: document intake service and handler, cmd/opportunity-extract,
cmd/opportunity-amendment-diff, cmd/sam-attachment-sync (the deferred Phase 1 item), migration 0082.
GREEN AND PROVEN:
PROVENANCE BORN FROM THE SOURCE. 0082 replaces the per table binding for opportunity_documents, because
that one table holds BOTH a customer's private package AND published federal attachments, and binding all
of it tenant_upload made public federal text unusable by the lane built for it. A caller still CANNOT
DECLARE provenance: declaring tenant_upload on an attachment yielded public_cache, declaring public_cache
on an upload yielded tenant_upload, and relabelling source_type afterwards is REFUSED, which closes the
laundering path. Mapping defaults to the restrictive value.
EXTRACTION SURVIVES BAD INPUT with a named reason each: malformed, locked, empty, scanned, bytes_missing.
The bytes_missing case was a planted positive control and it failed as required.
CITATION PER CLAUSE, six chunks per PWS labelled C.3.1 through C.5. The first version produced ONE chunk
per document because the PDF reader flattened line structure, which lost the entire point of chunking.
AMENDMENT DIFF detects all five material changes including QUANTITY four to six and TRAVEL 15 to 40, both
of which it missed at first because the patterns wanted symbols and the document writes prose. For a
cleared staffing product the headcount is the most important number on the page.
ACCEPTANCE ITEM 11 PROVEN: requirement md5 IDENTICAL before and after an amendment, one alert raised for a
named human, nothing rewritten.
ATTACHMENT SYNC pulled 4 real federal attachments from the PUBLIC endpoint, no API key spent, dedupe
positive control clean on the second run, ingest_manifest written. The live call found three defects no
fixture would have: the response is nested, mimeType carries an EXTENSION so every real attachment was
being refused, and the payload carries exportControlled and accessLevel flags that were being ignored
entirely, which for a cleared workforce product is not a bug to fix later. mayPull now fails closed.
RED, AND IT BLOCKS THE REAL DOCUMENT HALF OF GATE A: THE PIPELINE CANNOT READ A PRODUCTION GRADE PDF. Zero
of four genuine federal attachments extract. The reader was fixed to inflate FlateDecode, then produced 16
chunks of BINARY NOISE that stored cleanly with correct provenance and were garbage, because inflating every
stream picks up font programs. A prose quality gate now refuses them and is CORRECT, and its consequence is
that real PDFs yield nothing. THE GATE WAS NOT WEAKENED TO PRODUCE A GREEN RESULT: garbage chunks are worse
than none because a reviewer cites them. Measured, not asserted: one real solicitation carries 149
FlateDecode streams and 20 embedded font programs, so telling content from fonts needs real PDF object
parsing. Three options, all Joseph's: add a Go PDF dependency (cheapest, a stack decision under the do not
blindly build rule), build the parser in house (its own phase item), or shell out to pdftotext (breaks the
cloud portability mandate, listed only for completeness). | this commit | 2026-07-28
THE LEARNING BOUNDARY, THREE DOORS AND NO FOURTH | Recorded 28 July 2026 as ingestion begins to grow the
corpus at scale. This is a standing rule about what ingestion is ALLOWED TO CHANGE, and it exists because a
system that ingests federal documents daily and also scores humans has an obvious and dangerous shortcut
available to it.
INGESTION MAY OPEN EXACTLY THREE DOORS:
1. THE CORPUS. New documents, new chunks, new notices, new attachments. More evidence to cite.
2. THE EMBEDDINGS. New vectors over that corpus, so retrieval finds things it could not find before.
3. THE ONTOLOGY SUGGESTIONS. Candidate skills, synonyms, and adjacencies PROPOSED for operator curation.
THERE IS NO FOURTH DOOR. Ingestion NEVER alters scoring weights, never alters the band thresholds, never
alters the clearance hierarchy, and never promotes an ontology suggestion into the live ontology on its own.
WHY THIS IS WRITTEN DOWN RATHER THAN ASSUMED. The scoring engine is an AEDT under NYC LL144 and OFCCP. Its
weights are a frozen, diffed, exhaustively tested artifact precisely so a customer can point at one number
and say what produced it. A pipeline that learned from the market and adjusted those weights would produce a
scoring function nobody could reproduce, explain, or audit, and it would do it silently, one federal
document at a time. The failure would not look like a bug. It would look like the numbers slowly changing.
Door 3 is the one that looks safest and is not. An ontology that grows itself changes which candidate
skills match which requirement skills, which changes the Skills component, which changes the score. So an
ontology SUGGESTION is data, and its PROMOTION is an operator action by a named human, and the gap between
those two is load bearing. cmd/promote-ontology exists for exactly that gap.
HOW IT IS ENFORCED TODAY, honestly: by the frozen engine diff against 9e77f65 asserted EMPTY at every drift
check, by the ontology promotion being a separate operator binary, and by nothing else. It is a rule with a
detector, not a rule with a lock. Named that way so nobody mistakes the detector for a guarantee. | this commit | 2026-07-28
OWNER DECISION, OPERATOR MFA IS DEFERRED | Joseph, 28 July 2026. Recorded as a DECISION, which is a
different thing from an open item, and it closes what was item 3 on his list.
THE DECISION: the production stand down of operator MFA STAYS. MM_OPERATOR_MFA_REQUIRED remains false in
production only; the default is true, staging proves the rule, and config validation still refuses the
false value under a production MM_ENV, so the stand down is deliberate and announced at every boot rather
than accidental.
ACCEPTANCE ITEM 39 CARRIES A DEVIATION ON RECORD. It is not satisfied, it is not quietly reinterpreted as
satisfied, and it is not going to be reported as passing. A deviation on record is the honest shape for a
requirement the owner has consciously chosen not to meet yet.
REVISIT IS A NAMED GATE 6 CHECKLIST ITEM. It is not to be raised again before Gate 6. Anyone reading this
ledger between now and then should treat operator MFA as settled and stop relitigating it; that is the
point of recording a decision rather than leaving a question open.
WHY IT MATTERS ANYWAY, stated once and then left alone: for an AEDT handling CUI adjacent data with a
federal buyer in view, this is the gap a first assessor finds. The owner holds that. | this commit |
2026-07-28
PATRICIA HAS BEEN EMAILED, ITEM CLOSED | Joseph, 28 July 2026. Reported by the owner and recorded on his
word, which is the correct basis for a fact only he can observe. No independent verification is claimed and
none is implied. The item is closed. | this commit | 2026-07-28
FINDING D4, THIS LEDGER CARRIES LIVE PASSWORDS IN PLAINTEXT, AND I PUT THEM THERE | Found while checking
this file for secrets before committing the phase close entries. Three lines carry credentials: the
megamind-agi host password in the environment block, and the two staging test account passwords. They landed
in b8c7b07 and 58a94a1, both mine, both earlier in this build.
WHY THIS IS NOT SIMPLY FIXED HERE. Redacting the tip of the file would leave every one of them in git
history and would LOOK like a remediation while changing nothing an attacker could not still read. That is
worse than the current state, because it converts a visible problem into an invisible one.
THE HONEST OPTIONS, and both are Joseph's:
ROTATE. Change the megamind-agi password and both staging account passwords, then redact the file. This
makes the history harmless without rewriting it, and is the option that does not touch git.
PURGE. Rewrite history to remove the lines. Destructive, and the repository has NO REMOTE today, so a
purge is contained; it is still a rewrite of 372 commits and not something to do unasked.
MITIGATING, and stated as mitigation rather than as an excuse: the repository has no remote and lives on
one laptop. The exposure is local. It is still a rule this build set for itself and then broke.
NOT DONE UNDER THE PHASE LOCK. Queued as Joseph's item 8. | this commit | 2026-07-28
ENVIRONMENT CORRECTION, megamind-agi HAS TWO TAILNET IDENTITIES | The Windows host is 100.69.120.61 and the
WSL2 instance is 100.77.114.40 running its own tailscaled. POSTGRES 5433 BINDS INSIDE WSL2, so it answers on
.40 and a connection to .61:5433 TIMES OUT WITH NO USEFUL ERROR. There is no sshd inside WSL2 either, so the
only route in is ssh to the Windows host then wsl with a STAGED script; scp to the Windows path needs -O and
the default home, and inline nested quoting through wsl silently produces nothing. The control plane's own
postgres superuser is reachable as: wsl -- bash <staged script> running sudo -n -u postgres psql -p 5433.
Recorded because rediscovering it cost real time this session. | this commit | 2026-07-28
## CURRENT PHASE: PHASE 2, scope and requirements, with TRACK U and TRACK H running in parallel
Exactly one phase is current. Phase 0 complete with Gate 0 passed. Phase 1 complete except the two items
Joseph deferred until client go live (intake screen 3 uploads and cmd/sam-attachment-sync, both blocked on
real documents existing, not on engineering), with Gate 1 criteria passed and recorded. Phase 2 is IN
PROGRESS. Phases 3 through 6 are not started. Task T1 is standalone, complete, and outside the phase gates.
## Corrections ratified by Joseph, 28 July 2026
CORRECTION 1, documents now match reality | The environment rule text in BOTH docs/CLAUDE_CODE_BUILD_DIRECTIVE.md
Section 0 item 9 and docs/DIRECTIVE_AMENDMENTS_CONSOLIDATED.md Amendment 1 now names the functioning staging,
missionmatch_staging on M4 100.94.8.94 port 5432, and states that megamind-agi port 5433 is an empty shell
that MUST NOT be used. Section 0 item 7 of the amendments file is corrected to match. | 472a2bd | 2026-07-28
CORRECTION 2, STAGING ROLE ISOLATION PROVEN, and it FAILED before it passed | Attempted access exactly as
asked. BEFORE: staging and production ran as the SAME role, missionmatch_app, on the SAME cluster. The staging
credentials CONNECTED to production missionmatch and successfully wrote a row (INSERT 0 1). The zero row reads
were row level security with no org GUC set, NOT a privilege denial, so there was no isolation at all. Test row
deleted immediately, verified 0 remaining. ROOT CAUSE: PostgreSQL grants CONNECT to PUBLIC by default, so every
role in the cluster could reach production. FIX: created missionmatch_staging_app (LOGIN, NOBYPASSRLS) with
grants mirrored exactly from the app role inside missionmatch_staging only, 79 tables to 79 tables; granted
CONNECT on production explicitly to the four legitimate roles (app, migrator, compliance, aioe); revoked the
PUBLIC blanket. The boot role assertion hardcoded missionmatch_app and failed the boot closed, which is what
blocked a differently named role, so MM_APP_DB_ROLE now names the expected role per environment, defaulting to
missionmatch_app so production is byte for byte unchanged and the assertion stays exactly as strict.
AFTER, ledger evidence: staging role refused on missionmatch, tenant_alpha, and tenant_beta with "permission
denied for database"; the identical write that previously succeeded now fails at connection with FATAL:
permission denied for database "missionmatch"; the staging role still reads its own database; production
migrator, production app role, and the production API (active, healthz 200) are unaffected; zero stray test
rows. | 472a2bd | 2026-07-28
CORRECTION 3, the eighteen parity migrations classified, and a VIOLATION found and fixed | Nine were BUILD
migrations authored by this build: 0069 ingest_manifest, 0070 tracked_opportunities, 0071 opportunity_versions,
0072 opportunity_documents, 0073 opportunity_document_chunks, 0074 opportunity_records_embedding, 0075
opportunity_amendment_alerts, 0076 opportunity_scope_items, 0077 requirement_drafts. Nine were PREEXISTING
migrations staging was simply behind on: 0060 solicitation_extractions, 0061 tenant_directory, 0062
superuser_and_tool_access, 0063 provision_org_grants_tools, 0064 bootstrap_role_and_scoped_touch, 0065
revert_audit_logs_rls, 0066 recompute_furthest_stage, 0067 duplicate_indexes_and_expansion_author, 0068
audit_logs_tenant_read. Nine plus nine is eighteen.
COULD NOT CONFIRM the no build feature in production rule, because it was FALSE: the capture routes answered
403 (mounted) and /slate/capture served the Capture screen, both deployed on 27 July under the then current
instruction to build directly against production. Made it true rather than reporting it as acceptable.
Rebuilt the API binary and the frontend bundle from the starting commit 9e77f65 in a clean git worktree and
deployed both to BOTH nodes. The restored bundle hash index-DEG0K1sr.js is byte identical to what production
served at session start, which is independent proof the restoration is faithful.
EVIDENCE: POST /api/requirement-drafts/{id}/approve, /reject, and /opportunities/tracked/{id}/scope all return
404; POST /api/opportunities/tracked returns 405, because the pre build binary registers only GET
/opportunities/{noticeID} so the path matches that GET route while the capture POST handler does not exist;
both nodes agree and 8 of 8 requests through the public pool return 405; the string "Opportunity capture"
appears 0 times in the shipped bundle. Production remains fully working: site 200, healthz ok, the preexisting
authenticated API still 403. The schema stays at v77 because the build migrations are additive, carry no data,
and are needed at Gate 6; only the reachable surface was removed. | 2026-07-28
CORRECTION 4, ledger and task list reconciled, knowledge feeding CLOSED | The task list named Phase 1 as
current while the ledger showed Phase 2 work underway. Reconciled: exactly ONE phase is now named current,
Phase 2, in the heading at the top of this file and in the task list, with Phase 1 closed and its two
deferred items recorded as blocked on documents rather than on engineering.
KNOWLEDGE FEEDING RECONCILIATION, OUTCOME RECORDED, task closed. The question was how Joseph's intent to
feed the system knowledge squares with the directive's cited provenance model. It is resolved in design and
the design is already partly built. There are THREE sanctioned doors and no fourth: (1) curated knowledge
cards through curation.curate, superuser only, each cited to a primary source and traced to an
ingest_manifest row, which is Phase 4 item 4, the eleven compliance cards; (2) public external sources
through the sync binaries, each writing an ingest_manifest row, which is BUILT and proven, sam-sync wrote
its first provenance row on 27 July; (3) tenant documents through opportunity_documents and
opportunity_document_chunks with the citation CHECK enforcing provenance in the schema, plus the Import
Center staged batch path added by consolidated amendment 4, where nothing becomes a live record until a
human commits with a first party attestation. Arbitrary document dumping into a model is not a door and
never becomes one. The embedding layer added on 27 July gives the AI semantic recall over the public
opportunity catalog, 6535 of 6535 records, through the SAME provenance backed corpus.
BLOCKER, now clearing: the only thing outstanding was real documents. Joseph is uploading a solicitation
package, an RFP or combined synopsis with a PWS and at least one amendment, for Phase 2 extraction
verification. On arrival the deterministic pass runs first per Section 5.1, then model normalization, then
the scope review queue. | 2026-07-28
## Consolidated amendments 1 through 5, applied 28 July 2026
AMENDMENTS 1 THROUGH 5 ACCEPTED, CONSOLIDATED | source docs/DIRECTIVE_AMENDMENTS_CONSOLIDATED.md
(708 lines, copied into the repo 28 July 2026). docs/archive/DIRECTIVE_AMENDMENT_5.md archived per
Section 0 item 3; exactly one amendment source now exists under docs. | 2026-07-28
CONSOLIDATION MERGED AND VERIFIED | all seven Final Merge Index lines verified by grep against the
merged directive, not by assertion: (1) Section 0 gained the Amendment 1 environment rule; (2) Phase 3
gained the slate approval lifecycle and Gate 3 gained its additions; (3) Phase 5 gained desk integration
A through C, publishing to the tenant website, desk workflow additions 1 through 11, the Import Center
and uploads, and the candidate spine and Candidates page, and Gate 5 gained sections 2.8, 4.6, and 5.7;
(4) the directive gained the canonical dataflow as Appendix A; (5) Section 11 gained acceptance items 25
through 35, all eleven present; (6) Task T1 is registered as standalone work outside the phase gates;
(7) the fast follow list gained the 2.7 items. Directive grew to 938 lines. | 2026-07-28
CONFLICT A, staging location, RESOLVED without stopping | The amendment Section 0 item 7 names staging as
missionmatch_staging on megamind-agi port 5433. Verified 28 July: that database exists but is an EMPTY
SHELL, 0 organizations, 0 candidates, goose_db_version not readable by the app role. The WORKING staging is
missionmatch_staging on M4 100.94.8.94 port 5432: 76 tables, 2766 organizations, 8872 candidates, and it is
the database the live mm-api-staging service on 127.0.0.1:8097 serves (healthz 200). Gate verification
requires an end to end check through a live authenticated API and a rendered screen, which only the M4
database supports. Per amendment Section 0 item 5, the smallest change satisfying both is to honor the
amendment's database NAME and its staging only rule while using the host where that database actually
functions. STAGING MEANS missionmatch_staging ON 100.94.8.94 PORT 5432. | 2026-07-28
CONFLICT B, work already verified against production, RESOLVED without reopening | Amendment 1 reinstates
staging only until Gate 6, superseding Joseph's 27 July instruction to build directly into the existing
application. Migrations 0069 through 0077 and the Gate 0, Gate 1, and partial Gate 2 evidence were verified
against PRODUCTION missionmatch under that now void instruction. Per amendment Section 0 item 5, PASSED
GATES ARE NEVER REOPENED, so that evidence stands as recorded. The smallest change satisfying both is
forward looking: from 28 July every migration and every gate verification runs against staging, and staging
is brought to schema parity with production first so the two do not diverge. Production is not touched again
until Gate 6. | 2026-07-28
PHASE 0 HUMAN BLOCKERS, all three recorded, no stop point | Amendment 1 requires surfacing if any of the
three is absent from the ledger. All three are present above and dated 2026-07-27: the exposed OpenAI key
revoked, the fresh project scoped key placed on M4 as openai_api_key root owned 0600, and the SAM.gov system
account application submitted. Nothing external is blocked. | 2026-07-28
STAGING BROUGHT TO PARITY, the environment rule is now live | snapshot taken first
(/home/user/mm-snapshots/staging-schema-*.sql), target asserted as missionmatch_staging before any write,
then migrated v59 to v77 (+18) with eeo unchanged at v2. Verified on staging: all seven new tables present
(ingest_manifest without RLS as shared reference data, tracked_opportunities, opportunity_versions,
opportunity_documents, opportunity_document_chunks, opportunity_scope_items, requirement_drafts all with
rls_forced true), opportunity_records carries both the embedding and search_tsv columns, and the existing
data is intact at 2766 organizations and 8872 candidates. mm-api-staging on 127.0.0.1:8097 still active with
healthz 200. Production remains at v77 and was not touched by this run. Every gate from here verifies on
staging. | ee67c3a | 2026-07-28
BLOCKER FOUND AND FIXED during parity | migrate-all-tenants read the tenant directory before migrating and
died on a database that did not have tenant_directory yet, which is a deadlock: the table is created BY a
migration, so the command that would create it could never run. A missing relation is now treated as no
tenants yet while every other error still fails hard. | ee67c3a | 2026-07-28
TASK T1 COMPLETE, verified rendered over HTTPS | Joseph picked fg-talent.com, missionMATCH's first
client. Located it: fg-talent.com resolves to 169.155.162.118, the same host as Bubbles, served from
/var/www/sites/fgtp with the vhost in /etc/nginx/sites-available/fg-talent.com (a sites-enabled grep missed
it, nginx -T found it). Backed up the live client docroot to /home/user/fgtp-backup-20260728-121556.tgz
before any edit. Added a brand native Recruiter Login link, hand written markup reusing the site's own
btn btn-ghost btn-sm class, to the desktop header CTA row and the mobile drawer on ALL 34 pages, idempotent
so a rerun is a no op. VERIFIED LIVE: five representative pages return 200 with the link present, the
rendered markup is exactly the intended anchor, the target missionmatch.thatwebhostingguy.com returns 200,
and FG Talent's own Submit background and Hire talent CTAs are untouched. | 2026-07-28
TASK T2 REGISTERED, standalone work outside the phase gates, like T1 | Joseph, 28 July 2026. Make
https://fg-talent.com fit every screen, desktop, tablet, and phone. Executed on the Bubbles vhost at
/var/www/sites/fgtp, outside the missionMATCH repo, and it does NOT block phase progress.
SCOPE, exactly: correct viewport meta on every page; fluid layouts with no horizontal scroll at 320, 375,
390, 414, 768, and 1024 pixel widths; a readable type scale on phones; tap targets of at least 44 pixels;
the existing mobile drawer and the Recruiter Login link from T1 both working; images and grids scaling
instead of overflowing; tables wrapping or scrolling inside their container; desktop visually unchanged.
CONSTRAINTS: back up the live docroot first, as T1 did. CSS and layout only. No copy changes and no new
content. The FG Talent content rule stands unchanged, Patricia Cox and Brian Atwood only, Tom and Glennette
nowhere. No Python.
WHY IT MATTERS BEYOND ITSELF: the Phase 5 careers brand template (amendment 2.5 item 4, the careers section
must read as native to the site) inherits whatever is fixed here, so this is also the mobile foundation for
the future job pages that candidates will land on from a phone. | 2026-07-28
TASK T2 COMPLETE, verified live over HTTPS at every required width across all 34 pages | Backed up first to
/home/user/fgtp-backup-t2-20260728-133551.tgz, as T1 did, plus the stylesheet alone to
/home/user/brand.css.pre-t2-20260728.
ONE FILE CHANGED, proven not asserted: every file in the docroot was compared byte for byte against the
backup afterwards. 0 of 35 HTML files changed. assets/css/brand.css is the only modified file, 646 lines
plus one appended block. No copy touched, no content added, no markup edited.
WHAT WAS ACTUALLY BROKEN, measured before it was fixed by loading all 34 live pages at 10 widths and
comparing document scrollWidth to clientWidth, naming the outermost overflowing element:
THE HEADER, all 34 pages. The desktop header needs 1365px, brand 328 plus navigation 643 plus calls to
action 322 with padding and gaps, and the drawer did not take over until 980px. Every viewport from 981
to 1365 pushed the document 65 to 364 pixels wide. This is far larger than the brief assumed: it is not
a tablet problem, it is every 1280 laptop, and 1366 was the narrowest width that rendered clean.
/services/ AT 320, over by 18. Seven hard coded two column inline grids, grid-template-columns 1fr 1fr,
with no narrow fallback across four pages. Two columns cannot fit a 280 pixel content box, so the card
grew to 318 and took the page with it.
TAP TARGETS. The menu button was 35 tall on all 34 pages, accordion headers 37 on 53 measurements, and
the single checkbox on the site 13 by 13.
VIEWPORT META was already correct on all 34 pages; only the Google verification stub lacks it, which is
right because it is not a page. TABLES were already wrapped in scrolling containers on all three pages
that have one, so they were left alone rather than given a rule that would strip their semantics.
THE FIX, CSS only, every rule bounded to 1439.98px and narrower so the desktop cannot move: a 1200 to 1439
band keeps the real navigation and drops the brand wordmark, which is the design's own move already used
below 720px and frees 232px; below 1200 the existing drawer takes over; phones collapse the inline two
column grids; touch widths get 44 pixel targets.
RESULT, 340 measurements, 34 pages by 10 widths: ZERO horizontal overflow at 320, 375, 390, 414, 768, 1024,
1200, 1280, 1366, and 1440. All 34 pages answer 200. The drawer opens at 320, 390, 768, 1024, and 1199 with
all 18 links at least 44 tall, the T1 Recruiter Login link present at 49 to 75 tall pointing at
missionmatch.thatwebhostingguy.com, and no overflow with the drawer open; at 1200 the desktop navigation
takes over exactly as designed. The menu button is 44 by 44 and the checkbox row is 56 tall with a bound
label, so its effective target exceeds 44.
DESKTOP UNCHANGED, PROVEN BY PIXEL DIFF, not by assertion: 10 pages rendered twice, once against the
pre T2 stylesheet served by request interception and once against the live one, are byte identical at 1440
and at 1600, 10 of 10 each, and at 1920 after re running two pages whose first comparison was invalidated
by a non deterministic render and a failed interception.
TWO ERRORS IN MY OWN WORK, caught by verification and recorded rather than hidden. First, the initial diff
used a route pattern that missed the ?v=3 cache buster, so BOTH arms loaded the new stylesheet and the
identical result proved nothing; it was caught by a control that rendered the old sheet and still measured
no overflow, which was impossible. Second, once the interception worked it showed every desktop page had in
fact changed: an unbounded img[width] selector outranked .brand-lockup__img, forced height auto, and grew
the logo from 144x96 to 200x133, shifting the whole header. That rule was redundant with the base reset and
was deleted, the block was rebuilt from the pristine backup so nothing was applied twice, and every
remaining rule was bounded to non desktop widths.
CONTENT RULE HOLDS, checked against the live rendered HTML of all 34 pages: 0 pages contain Tom or
Glennette, Patricia Cox appears on 3, Brian Atwood on 3.
PHASE 5 NOTE: the careers brand template of amendment 2.5 item 4 must read as native to this site, so it
inherits this stylesheet. This is the mobile foundation for the future job pages candidates open on a
phone, which is why the header fix matters beyond today. | 2026-07-28
NOTED DEVIATION on T1 placement, per Section 0 item 5 | Amendment 2.2 item 3 says header AND hero. The
amendment assumed a missionMATCH MARKETING surface; Joseph's pick is the CLIENT's candidate facing site,
which in Phase 5 will also host the public /careers/ pages candidates land on. A hero Log in CTA there
would compete with FG Talent's own conversion path and would push the missionMATCH domain at candidates,
against decision 2.1.3 that candidates never see the missionMATCH domain for a mapped tenant. The smallest
change satisfying both is a site wide header link on every page, which is the prominence the amendment
wants, labeled Recruiter Login so a candidate is not misdirected, and no hero CTA. | 2026-07-28
SUPERSEDED, the original T1 ambiguity record | Enumerated the Bubbles vhosts per amendment
2.2 item 1. Finding: NO Mission Workforce Technologies marketing vhost exists. The only server_name matching
missionmatch is missionmatch.thatwebhostingguy.com, which is the APPLICATION itself, and amendment 2.2 item 1
explicitly excludes putting the button inside the app. The real marketing surfaces both live on thataiguy.org,
a live production website: /work/missionmatch/ is an 18110 byte case study titled "missionMATCH: A Cleared
Workforce Platform, Multi Tenant and Isolated" with an h1 of missionMATCH and NO existing login link, serving
200 over HTTPS; and the thataiguy.org homepage features missionMATCH as a Flagship card linking to that page.
Amendment 2.2 item 2 forbids guessing on a production website, so the pick is Joseph's. Task T1 is standalone
work outside the phase gates, so it does NOT block phase progress. | pending Joseph | 2026-07-28
## Drift audit, 28 July 2026
Audited this build against the directive at Joseph's request. Recorded here because Section 0 item 6
requires a deviation to be written down rather than carried silently.
CLEAN, verified by command, not by memory: the candidate scoring engine diff against the starting commit
9e77f65 is EMPTY (acceptance 23); db/migrations_eeo untouched; zero existing migrations 0001 to 0068
modified; zero Python files added (acceptance 20); no dashes in authored prose (acceptance 21, the single
grep hit is a pre existing llama.cpp CLI flag, which the rule permits). Nothing on the FORBIDDEN list was
done: no new repository, no new API binary, no second scoring engine, no fifth entitlement, no renamed or
restructured tables, no schema outside numbered migrations, no NL to SQL or model authored SQL, no secrets
outside the store, no scraping of candidate data.
DEVIATION 1, phase lock (Section 0 item 2). Phase 2 was started while Phase 1 still had open items: intake
screen 3 uploads, cmd/sam-attachment-sync, and Section 4 steps 4, 5, and 8 (full description retrieval,
attachment metadata, attachment queuing). Joseph deferred documents until client go live and directed the
move to Phase 2, so the deviation is authorized, but it was not recorded when it happened. It is recorded
now. The deferred items are blocked on real documents existing, not on engineering.
DEVIATION 2, Section 5.1 ordering, CORRECTED. Model normalization shipped before deterministic extraction,
inverting the directive's explicit "deterministic extraction first, before any model call". Corrected in
0da4c7c: ExtractDeterministic and ExtractSignals are pure, tested, and run with no model.
DEVIATION 3, Section 9 promotion routes unreachable, CORRECTED. ApproveDraft and RejectDraft were built and
race tested but never mounted, so the one path by which a named human turns a draft into a live requirement
could not be called. Corrected in 0da4c7c with the directive's own paths and the new requirement.approve
capability; the route contract test now guards them.
DEVIATION 4, route paths, ACCEPTED AND UNCHANGED. The tenant capture surface lives at /opportunities/tracked
rather than Section 9's /opportunities, because the public cache read already owns /opportunities/{noticeID}
and chi cannot host two wildcard names at one path position. Recorded with rationale.
CLARIFICATION, the external OpenAI lane. The backend, boot guard, egress gate, and audit are built and
proven, but the gateway's generation backend is config selected and currently points at the LOCAL model, so
REQUIREMENT_NORMALIZE has not executed against OpenAI end to end. Enabling it is a gateway config change
protected by the boot guard, not new code.
phase2 | drift corrections | requirement.approve at manager and above and scope.review at recruiter and
above added to the exhaustively tested matrix; promotion and scope review routes mounted and guarded by the
route contract test; deterministic extraction added ahead of the model per Section 5.1 with 11 module tests
green and ci-gates passing | 0da4c7c | 2026-07-28
## Phase 2, scope and requirements
phase2 | item5 the safety boundary tables | migrations 0076 opportunity_scope_items and 0077 requirement_drafts applied to production (goose 75 to 77), rls_forced both, 19 and 31 columns, 3 and 9 CHECK constraints. The Gate 2 rules are enforced in the SCHEMA: ck_scope_items_cited refuses uncited machine produced scope, ck_drafts_approved_has_requirement makes approved carry its result, ck_drafts_decided_has_reviewer refuses a decision with no named human so no automated path can approve, ck_drafts_skill_flags_align refuses a misaligned mandatory flag that would mis set a band cap | df6d7bf | 2026-07-27
phase2 | schema constraints PROVEN against production | six live tests, each refused correctly: uncited extracted item refused by ck_scope_items_cited, the same item WITH a citation accepted, a manual item accepted uncited, an approved draft with no requirement refused, an approved draft with no named human refused, mismatched mandatory flags refused | df6d7bf | 2026-07-27
phase2 | GATE 2 CONCURRENCY PASSED, eight racing promotions elect one winner | ApproveDraft takes FOR UPDATE on the draft row, records the named approver, writes the requirement and flips the draft in ONE transaction, and links only skills the existing ontology knows (never writes the global ontology). Raced against production: 8 concurrent promotions of one draft produced requirements_created=1 and draft_status=approved with the requirement recorded. Proven by racing it, not by reasoning about it | df6d7bf | 2026-07-27
phase2 | item3 model normalization into cited scope drafts | ValidateScopeCandidates is pure and exhaustively tested (6 tests green): rejects uncited items, out of vocabulary scope_type, oversized fields, out of range confidence, and fails closed on an unparseable payload. Its load bearing rule REJECTS A FABRICATED CITATION, a quote not found in the source text, which is stronger than the Gate 2 ask. cmd/opportunity-scope-normalize composes ONLY public SAM notice metadata, redacts contact shapes, and the scrub gate under REQUIREMENT_NORMALIZE has the final say. VERIFIED LIVE: gate allowed a 201 byte payload and logged audit metadata (backend, payload_class, byte_count, content_sha256, never content), the model returned one grounded item, it wrote with its citation, and the SAME item inserted uncited was refused by ck_scope_items_cited | 984a0ee | 2026-07-27
phase2 | GATE 2 status | PASSED so far: every extracted scope item carries a source citation (schema enforced); uncited model output is rejected, proven by test AND by a live insert refusal; eight racing promotions elect exactly one approved requirement (raced against production). REMAINING for the full gate: the scrub suite fixtures for a candidate row and tenant uploaded document text in an external bound payload, an ingest run during promotion, and the approved requirements are never rewritten by an ingest path test | 984a0ee | 2026-07-27
phase1 | amendment detection, versioning, and alerts | migration 0075 widens the alerts vocabulary with opportunity_amendment and opportunity_deadline (preserving collision from 0040); sam-sync compares the source payload hash per tracking tenant and on a change writes an immutable version, retires its predecessor in one transaction, cites the ingest manifest, and raises an alert through the existing alerts module. VERIFIED LIVE against production with a real org and a real SAM notice: run 1 wrote version=1 is_current=true with manifest cited and raised an opportunity_amendment alert | dd18ef1 | 2026-07-27
phase1 | GATE 1 CRITERIA PASSED against production | (a) a SAM.gov opportunity ingests TWICE without duplication: second run of 1403 notices left versions at 1. (b) tenant authored fields survive a source refresh BYTE FOR BYTE: md5 fingerprint of status, capture owner, title, source_type, incumbent identical before and after (7498222db3ca65ff...). (c) versions are IMMUTABLE proven by attempted mutation: UPDATE refused with "opportunity_versions is immutable except the is_current flag", DELETE refused with "DELETE is not permitted", while the documented is_current flip is allowed. (d) uploads and screens: intake screens 1 and 2 render and serve 200 through the public URL. Remaining for the full Gate 1: cmd/sam-attachment-sync and intake screen 3 uploads, deferred by Joseph until client go live | dd18ef1 | 2026-07-27
phase1 | migration gotchas recorded | two failures caught by verification, both safe (transactional, goose stayed at 74): rebuilding a CHECK from an OLD migration's vocabulary silently drops kinds a later migration added, so widen from the CURRENT constraint; and migrate-all-tenants embeds db/migrations with go:embed, so an edited .sql does nothing until the migrator binary is rebuilt | dd18ef1 | 2026-07-27
phase1 | AI orchestrator extended to the opportunity corpus | EXTENDED the existing orchestration layer, nothing built beside it: retrieval.go gains HybridSearchOpportunities (same fused contract as HybridSearch, RRF k=60, HNSW tuned, EEO guard on the question, no tenant predicate because the catalog carries none) and orchestrator.go gains isOpportunityQuestion plus answerOpportunityRAG, a branch INSIDE AnswerRAG reusing the same generate, finalize faithfulness gate, refuse path, and audit. The detector yields to any bench noun so a both planes question keeps its candidate corpus. Tests: market questions route, bench questions never regress, neutral unchanged, and the FULL ai package passes. The hand written RRF SQL was run against production before deploy and fuses both arms correctly. Deployed to BOTH nodes (sha c3501d7f identical), POST /api/ai/query returns 403 consistently on each node and 6 of 6 through the public pool | 6b716bc | 2026-07-27
phase1 | sam-sync writes ingest_manifest provenance | sam-sync accumulates a run hash over notice ids and raw payloads and writes one manifest row per run (source, url, license, fetched_at, record_count, content_hash); the write never fails the run. Deployed to Bubbles where the daily mm-sam-sync.timer runs it and executed live against the SAM.gov API: 1403 notices upserted across 7 NAICS, store now 2503, ingest_manifest went from 0 rows to 1 (SAM.gov, prod, records=1403, hash 30e9b801...). Directive Section 4 steps 1 through 3 satisfied | 084e521 | 2026-07-27