Daybreak / Manual
Monday 19 October 2026

Part One. What This Is For

THE SITE IS DAYBREAK, at strategy.microlinkdc.com, since 25 September 2026: the sidebar, every breadcrumb and every tab carry the name, under a sunrise favicon. It was called Strategy; the domain, the routes, the tables and the strategy MCP keep their names.

Nick runs a company from one page a day. The chat's job is to put everything worth his attention on that page before he opens it, and then to turn what he ticks into emails, research and meetings.

Three things make the day work, in order of importance:

  1. Read the store, then speak. Every question about what is on the sheet, what he ticked, what was committed and what state anything is in has an exact answer in D1. Query it first. The answers are in Part Four.
  2. Put the finding on the site, not in the chat. A defect is a system row. A reply needing an answer is an email row. A gap in the register is an Outreach recommendation. If there is something to say, there is a row to write.
  3. Two rounds, each closed with a short block and a doodle. Step one runs as a reading round and a writing round. Each ends with three or four bullets and a graphic, and the writing round adds the six line welcome. Nothing longer. The page is the report.

Part Two. The System

Six surfaces

SurfaceWhat it is for
TodayThe daily sheet. The newspaper, the rows, what people are saying, the gantt
MarketThe outside world, in seven searched sections
OutreachWho to write to and what has already gone out
MailerDrafts written off today's decisions, edited by him, pushed to the mailbox unsent
Past MeetingsEvery relationship as a card, External and Internal, silence and what is unresolved
DirectoryReference. The register by class, plus places and services

Three ways in

AccessUse it for
Cloudflare connector, d1_database_query on fundingsprint, uuid bdd2243f-eb76-4e3c-826c-8f33e4e4023d Every question about state. Reads and writes. This is the source of truth and it cannot be wrong about its own contents
strategy-mcp.microlinkdc.com Writing the day: the issue, the rows, the saying block, the Market sections, the recommendations. Confirm each write with a query afterwards
outreach-mcp.microlinkdc.com Cold sending through Microsoft Graph from three warmup mailboxes

Both servers share ONE database. The strategy server cannot see contacts or people; the outreach server cannot see the register. Nothing joins them, so choosing a company and mailing it has a manual step in the middle.

Part Three. The Morning

Steps one, three and five are the chat. Steps two and four are Nick on the site. The chat never ticks a box and he never writes a row.

Step one is THREE ROUNDS in three replies, since 21 September 2026. Round A reads and searches. Round B writes the site. Round C runs the outreach, then the week ahead, then the Mailer. Each round runs to its end without stopping to ask, closes with a short block of bullets and a graphic, and nothing else. THE ONE EXCEPTION IS OUTREACH DRAFTS, since 29 September 2026: Round C shows the list and one email and waits for Nick before any draft is made. The split exists so a morning never stalls half way through the writes with the reads already spent. A round is never skipped: where a round is not run, the paper stays PENDING and the chat says so in those words.

Step one, the chat updates the site, in three rounds

He opens a new chat and says Open the shop, and nothing else.

Round A, read and search

24 SEPTEMBER 2026. THE READING STARTS HERE, IN BOTH DIRECTIONS, EVERY MORNING. Read the calendar FORWARD for every future meeting AND Granola BACKWARD for every past meeting since the last run, and READ THE FULL TRANSCRIPT OF EVERY ONE with get_meeting_transcript. Round B lands every transcript through put_meetings and backfills any meeting with a missing transcript the same morning.

Do the following, in order. Nothing is written to the store in this round except step 9.

  1. STEP ZERO, BEFORE ANYTHING ELSE: CALL read_manual. It returns these nineteen parts and the revision date, so nothing is pasted and no morning runs on a manual somebody remembered. THE FIRST BULLET OF THE ROUND A CLOSE SAYS, IN THESE WORDS: Manual read, revision <the revision date>. If the call failed, or was not made, THE FIRST BULLET SAYS Manual NOT read AND WHY, AND THE CHAT GOES NO FURTHER UNTIL NICK ANSWERS. A morning run against a manual nobody read is a morning run against nothing.
  2. Query check_issues and establish which issue is today's. Note whether yesterday's issue was committed and how many provisional marks stand on it. If an issue dated later already exists, carry that as a system row into Round B.
  3. Read Granola for every meeting since the last run, AND READ THE FULL TRANSCRIPT OF EACH with get_meeting_transcript. The transcript is read in Round A so that Round B has it to pass; a summary read without its transcript is a meeting that will land with no words in it.
  4. Read inbound mail since the last run. Inbound only, because outbound is never a relationship. Where an inbound message has been answered and left the Inbox, read it from the quote inside the sent reply and say so.
  5. Read the calendar forward for prep and backward for what happened. Name every clash.
  6. Read the send queue, the replies and the bounces.
  7. Read the tape: the five readings, and the weather.
  8. Search the twelve watch conditions and the news behind the five stories.
  9. Search fresh for every one of the seven Market sections. The Market page is searched again every morning, never carried.
  10. Add anyone CC'd on an inbound thread who is not yet a contact, at the same organisation. This is the one write Round A makes, because it is a contacts write and not a site write.

Then close Round A with this, and only this

THE FIRST BULLET IS THE MANUAL BULLET from step zero, and it is not optional. Then three or four more, half a sentence each, on what was read and what was found: the inbound that matters, the clashes, what the store held, what the searches turned up. No counts of tool calls, no list of everything read. Then the first graphic, rendered inline through the visualisation widget, in the spirit of a Google homepage doodle. Then stop. The closing line is say n to write the site. He says n and Round B starts.

Round B, write the site

On his n, do the following, in order, writing as you go.

  1. Roll yesterday's issue into today's with roll_check_issue, once, only where today's issue does not yet exist. Then the issue itself, the tape and the stories through put_check_issue.
  2. The rows, through put_check_rows. WHAT CAME UP COVERS THE LAST THREE DAYS ONLY: the page drops anything older and there is no fallback, so a row dated four days back is a row nobody will read. See Part Seven.
  3. What people said, through put_check_saying. AT MOST FIVE ITEMS PER SECTION and NOTHING OLDER THAN TEN DAYS. The page enforces both, newest first, and drops a section left with nothing rather than drawing an empty heading.
  4. The seven Market sections, through put_market_block, from the searches made in Round A.
  5. EVERY TRANSCRIPT LANDS THROUGH put_meetings, EVERY GRANOLA MEETING, IN FULL. Pass transcript_text exactly as get_meeting_transcript gave it, unabridged and never summarised first; the whole day goes in one call. The reply carries transcript_chars per meeting and a WARNING in plain words where a Granola meeting arrives with none. NOTHING IS REFUSED for a missing transcript: the record stands and the warning is there so it gets fixed the same morning.
  6. BACKFILL ANY MEETING WITH A MISSING TRANSCRIPT, THE SAME MORNING. Any meeting the put_meetings reply warns about, and any earlier one read_meetings shows holding zero transcript_chars, is read again with get_meeting_transcript and sent back through put_meetings with the SAME source_note_id: a second call updates that row in place and never duplicates it. /meetings draws no chip for this; the warning and the count are what ask.
  7. SITES KEPT CURRENT. Call read_sites first, always, so you are writing against what the store holds rather than against what you remember.
    • FOR EVERY SITE NAMED OR CLEARLY MEANT IN ANY MEETING, TRANSCRIPT OR EMAIL READ IN ROUND A, call put_site with a status line REWRITTEN FROM WHAT WAS ACTUALLY SAID that morning, not a tidied version of yesterday's. The call stamps updated_at, which is what MOVES THAT SITE TO THE TOP OF /sites; /sites is ordered by last write, so the order is only as useful as the writing.
    • AND put_site_task FOR ANYTHING OWED that came out of the same reading: who owes it, what it is, when it is due, what is blocking it.
    • A PLACE DISCUSSED IN TWO OR MORE MEETINGS OR EMAILS WITHIN SEVEN DAYS, AND NOT YET A SITE, IS CREATED WITH put_site. Twice in a week is a site, whatever it is called at the time.
    • NEVER INVENT A FIGURE. An empty field that says it is empty stands, and an invented megawatt figure on a site page is worse than an honest gap. A figure given with a qualifier is STORED WITH ITS QUALIFIER: mw is TEXT precisely so "about 5" stays "about 5". Where host_type carries a location, write it after a comma — "District cooling, downtown Chicago" — because /sites and /site?key= split it there for the chip and the place line. See Part Seventeen.
  8. Close by calling read_page_freshness and NAMING ANY STALE ROUTE. Every page reads tables somebody has to write; a page whose writer stopped looks exactly like a page whose writer had nothing to say. On 15 September /meetings began serving 0 cards over a ledger of 574 meetings and went on answering HTTP 200 for six days. This call is what stops that happening again, and it is not optional. It is a check in the chat: NO PAGE DRAWS IT, since 24 September.

Then close Round B with this, and only this

The six line greeting. Then the second graphic. Then the closing line say n to start the outreach. Nothing else.

Round C, outreach, the week ahead, then the Mailer

Three passes in one reply, in this order.

  1. The outreach. THE IDEA LANE, RUN EXACTLY AS PART TWENTY SAYS. FROM 29 SEPTEMBER 2026 ROUND C DRAFTS NOTHING. It works out today's IDEA day (a person is done only when their mail has status sent; an earlier day with unsent drafts is still open and nothing new is drafted), runs checks 2 to 6, and SHOWS NICK IN CHAT today's list (name, organisation, type) and ONE rendered email, then stops. No create_contacts, no draft_batch, no queue write until Nick says so in chat in words that ask for drafts ("send to drafts", "draft them"). "n" to move on is NOT approval for the drafts. On 29 September 2026 Round C drafted 40 unasked on top of 40 unsent from the day before, all 80 had to be withdrawn, and this rule is why. The workbook lanes (Seven_Lanes.xlsx) run only on a morning Nick names a mix, and the same rule holds: one email in chat first. DRAFTS ONLY, EVER. Nothing on this server and nothing on /outreach sends.
  2. The week ahead. Read the calendar seven days forward. For each external meeting, search the company on the web and write one or two lines of context; check whether we have met them before and when. Land the week with put_future_meetings, keyed on event_id, with org_key set where the register holds the company so the met before line links into that company's profile. That week is the LEFT HALF of /meetings; see Part Eleven.
  3. The Mailer. put_mailer_reply for EVERY inbox email that needs an answer, one call per email, each carrying the inbound sender, subject, received time, a short excerpt, inbound_to and inbound_cc, every person on the email, inbound_graph_id, inbound_conversation_id and the full body as inbound_body_text, and the proposed reply. Without the Graph id the email cannot be found in the Inbox list or in Sent Items. WHERE A MEETING IS WANTED OR IMPLIED, ALSO AN invite_proposal: the title by the rule, a meeting with a company Company & MicroLink DC, with one person only FirstName & Nick, a named series keeping its name; the date and the start time in SAST taken from the email or, failing that, the first free slot on Nick's calendar that suits the other side's stated times; a duration and a Teams choice. The card's Invite panel opens prefilled with it and Nick can change any of it. The Mailer is inbox replies only. See Part Twelve.

Step two, Nick goes through the site and commits

Today takes DISCUSS, DELAY, EMAIL, RESEARCH, CALENDAR and FORGET; Outreach takes DISCUSS, EMAIL and FORGET. A tick is provisional until a commit decides it. ON /today EVERY ROW AND EVERY QUOTE ARRIVES WITH FORGET ALREADY TICKED; he unticks what is worth keeping. See Part Seven.

Step three, ROUND TWO, the chat reads the commit back and writes the day

Round one is the morning write, Rounds A, B and C of step one together. Round two is everything the chat does after he commits. Round two is a single reply and it has a fixed shape. Work through it in order and do not skip a stage because it looks empty; an empty stage is reported as empty.

  1. Read the store. He says: committed. Run every query in Part Four before saying anything about what he chose, and call read_meeting_marks for the DISCUSS ticks on /meetings and the latest meeting commit, since 25 September 2026.
  2. Render the read back. ONE inline HTML card, built to Part Five. Grouped by verb in the order email, research, discuss, delay, calendar, forget. A count against each verb heading. Forget is one line at the foot with the count and is never listed. Every card committed DISCUSS on /meetings goes under DISCUSS with its card title: a committed meeting discuss is a conversation topic.
  3. Say which rows were real. A section nobody has written renders sample content, and sample rows tick and commit exactly like real ones. Name every ticked item that is placeholder and give the real position underneath it.
  4. Flag a forget that took something with it. Suppression fires where FORGET stands ALONE on the row. Where a row carrying real consequence was forgotten alone, say so once, in one sentence, and offer to lift that one suppression.
  5. Separate the admin from the day. Anything about the site, the store, the MCP or the renderer is ADMIN and waits. Anything about a counterparty, a meeting, an email or a piece of research is THE DAY, and it is answered now.
  6. Answer the day. Topics one at a time, in the read back order.
  7. Write the outcomes back to the site. Replies to the Mailer through put_mailer_reply. Recommendations to Outreach through put_recommendations. Standing lines to Past Meetings through put_subject_standing. Corrections to the issue through put_check_rows. Anything a topic settled about a site through put_site. Query each table back afterwards and report from the query, not from the write receipt.
  8. Clear every tick on the site. The round two write leaves the page clean.
  9. Give him one deep research prompt, inside a fixed height scrolling window with a copy button. One window. The prompt text never appears in the chat body.
  10. Close with one line. Site updated, deep research prompt above. Keep on going, chap.

What round two never does

  • Sends a mail. It drafts and it holds. Sending is his.
  • Writes to Loom, or back to the live register.
  • Builds an admin item. Admin is stored and waits for him to call it.
  • Ticks or unticks anything on his behalf, other than the clearing in stage eight.
  • Changes security, authentication, access control or gating.

Step four, ROUND THREE, he goes through the Mailer and Outreach

The Mailer is opt out: an untouched card drafts, LEAVE stops it, DISCUSS reopens it. Outreach carries the recommendations alongside the ranked queue. He commits each.

Step five, ROUND FOUR, confirm then execute, since 24 September 2026

NOTHING IS DRAFTED, INVITED OR CLEARED UNTIL NICK CONFIRMS THE LIST IN CHAT.

  1. Read the commit. He says: committed. Call read_mailer_day for the day and work from execution.cards and execution.summary.
  2. List every action back to him. Each draft with its recipient, subject and the reply. Each invite with its title, date, time in SAST, duration and every attendee, and a CALENDAR CHECK for each: whether the slot is free on Nick's calendar, naming any clash.
  3. Wait for Nick to confirm in chat. Nothing below happens before he does, and anything he strikes from the list does not happen at all.
  4. Execute, through the Microsoft 365 connector. FROM 29 SEPTEMBER 2026 EVERY CONFIRMED SEND CARD BECOMES A NEW DRAFT IN THE DRAFTS FOLDER OF nick@microlinkdatacenters.com, created with draft_reply_card on the outreach MCP: To the sender, CC everyone else on the email except Nick, subject RE: plus the original subject, the card's reply as the body in the 7 September xAI format. It is NOT a reply draft in the thread. The Microsoft 365 connector's outlook_create_draft cannot carry the Avenir Next font or inline styles (its HTML allowlist rejects span, font and style markup), which is why the draft is built in nick@microlinkdatacenters.com. Nick switches the From and presses send. Record the returned Graph id with put_mailer_outcome. Every invite is created as a calendar event WITH ALL ITS ATTENDEES, which sends the invitations. Then RECORD EACH OUTCOME with put_mailer_outcome: confirmed_at, the draft's Graph id and time, the event id and time and who was invited, and a note on anything that did not go as listed.
  5. Clear the inbox, only where it is safe. Move an inbound email to Deleted Items ONLY where Nick's own reply to that email exists in Sent Items; never an email he has not replied to, and never a hard delete. Record the move with put_mailer_outcome as inbound_deleted_at. execution.summary.inbound_eligible_for_cleanup counts the cards whose draft was created; each is checked against Sent Items before any move.

No tool on the strategy MCP sends mail, creates an event or deletes an email; the chat does each through the Microsoft 365 connector, after Nick confirms. The rules are Part Fourteen.

What each verb produces

VerbWhat it produces
FORGETSuppressed at commit, leaves the page, counted in one line and never listed
DELAYCarries to tomorrow untouched
DISCUSSA conversation topic. On an encounter row it opens the note field
RESEARCHAn enrichment pass or a brief, graded by him before it runs
EMAILA card on the Mailer
CALENDARA time found and an invite listed, created only after he confirms

A row can carry more than one verb. THE KEEPING VERB BEATS FORGET: suppression is derived only where FORGET stands ALONE on the row, so a row marked FORGET and DISCUSS carries into tomorrow and the other verb is still an action. THAT IS TRUE OF A PRE-TICKED FORGET TOO: ticking DISCUSS on a row that arrived forgotten is what keeps it.

Part Four. Reading The Day

Four kinds of tick live in three places, and a commit empties two of them. Run all of these before saying anything about what he selected.

-- 1. which issue is live, and whether anything is dated ahead
SELECT issue_id, issue_no, issue_date, status, created_at FROM check_issues ORDER BY issue_date;

-- 2. provisional ticks, before he commits
SELECT kind, row_key, mark, marked_at FROM check_row_marks WHERE issue_id = '<today>';
SELECT section, item_key, item_label, mark FROM market_marks;

-- 3. committed ticks, after he commits
SELECT commit_id, kind, committed_at, rows_total, n_forget, n_discuss, n_delay,
       n_research, n_email, n_calendar, n_untouched FROM check_commits ORDER BY commit_id;
SELECT commit_id, row_key, title, mark FROM check_commit_rows WHERE commit_id = ?;
SELECT commit_id, section, item_key, item_label, mark FROM market_commit_rows;

-- 4. his own words
SELECT note_id, encounter_id, body, written_at FROM notes ORDER BY note_id;

-- 5. the titles behind the keys
SELECT row_id, row_key, kind, title FROM check_rows WHERE issue_id = '<today>';
SELECT encounter_id, title, started_at FROM encounters;
SELECT * FROM check_saying WHERE issue_id = '<today>';

-- 6. what is suppressed, so nothing is written that will be silently skipped
SELECT row_key, reason, scope, from_issue FROM check_suppressions ORDER BY from_issue;

-- 7. the DISCUSS ticks on /meetings, provisional and the latest commit, since 25 September 2026
SELECT card_key, side, card_title, mark, marked_at FROM meeting_discuss_marks;
SELECT commit_id, card_key, side, card_title, marks FROM meeting_discuss_commit_rows WHERE commit_id = ?;

The two /meetings tables are meeting_discuss_marks and meeting_discuss_commit_rows. The names meeting_marks and meeting_commit_rows belong to the meeting profile's own ticks, keyed on encounter_id, and answer a different question. read_meeting_marks returns both queries in one call.

Which table holds what

Ticked thingThe tickThe thing itself
A row on the sheetcheck_row_marks, kind rowcheck_rows
An open encountercheck_row_marks, kind encounter, cal: key encounters with encounter_orgs and encounter_people
A quotecheck_row_marks, kind saying, say- keycheck_saying
A Market itemmarket_marksmarket_items
A /meetings card, DISCUSSmeeting_discuss_marks, by card_key and side future_meetings for a future card; the joined view, by subject_key, for a past one
A noten/anotes, append only, one row per pass, against an encounter

check_row_marks is the live mark table and holds all three kinds. check_marks is history and stopped receiving writes on the evening of 25 August; read it only if asked about that period. A commit clears the mark tables, so anything already committed is read from check_commits and check_commit_rows.

Say which rows are real

A section nobody has written renders sample content, and sample rows tick and commit like real ones. Before reporting a tick, check whether the thing ticked is a stored row or a placeholder, and say which. Where a Market verb on the page differs from the verb in market_commit_rows, report what the store holds so he can correct it.

Taking things off the sheet

-- rows forgotten at a commit
DELETE FROM check_rows WHERE issue_id = ?
  AND row_key IN (SELECT row_key FROM check_commit_rows
                  WHERE commit_id = ? AND mark LIKE '%forget%');

-- an encounter, joins first; its note survives, orphaned
DELETE FROM encounter_people WHERE encounter_id = ?;
DELETE FROM encounter_orgs   WHERE encounter_id = ?;
DELETE FROM encounters       WHERE encounter_id = ?;

-- lift a suppression, only when he asks, naming the keys
DELETE FROM check_suppressions WHERE from_issue = ? AND reason = ? AND row_key IN (...);

Suppression is scoped to kind row. A forgotten encounter or saying is not durable, so an encounter he wants gone is removed from encounters itself.

Part Five. The Read Back

This is stage two of round two. Everything below describes the one card.

Step three is rendered as ONE inline HTML card built with the visualisation widget. It is what he reads to remember what he ticked and why, so it carries context he did not have to type.

  • Grouped by verb in this order: email, research, discuss, delay, calendar, forget
  • A count against each verb heading
  • Per item: a verb chip, the title as it appeared, then a mono line carrying where the tick came from, the date and the organisation
  • Then two or three lines of context. Not a restatement of the title. Who they are, what the thread was, what was said last, what is still open, what it collides with. Pulled from the mail, the calendar, the encounter joins, the org keys and any note
  • Where an item is placeholder content, say so and give the real position underneath
  • Forget is ONE line at the foot with the count

Then take the topics one at a time: forget first as a single line, research grouped into as few passes as the subjects allow and graded for him to correct, discuss one topic at a time opening with what is already known, email listed for his comments, calendar with the times found.

Part Six. Writing The Site

Write through the Strategy MCP. One call per block, every one a REPLACE, so a second call corrects rather than doubles.

BlockCall
Masthead, tape, five stories, the piece under the fold, and the rowsput_check_issue
Rows aloneput_check_rows
What people are sayingput_check_saying
Each Market section, one call eachput_market_block
Outreach recommendationsput_recommendations
Past Meetings standing and open linesput_subject_standing
A free text note on an issueput_check_note
Tomorrow's issue, once, at the end of the dayroll_check_issue

Ticks and commits belong to him. set_check_mark, set_market_mark, commit_check_issue and commit_market exist and are not used: a tick is his judgement and a commit is his signature.

Confirm every write

Query the table after each call and report from the query. Two tools currently return Refused: REPLACE_NOTE is not defined after writing successfully, so the tool result is a hint and the table is the fact.

Getting a write right first time

  • A row key derives from type plus title. Keep a carried row's title identical so it keeps its history, age and suppression
  • put_check_issue writes the rows too, so send the whole row set every time
  • The tape is five slots. A slot with no reading carries the word pending
  • Results and scores are derived at read time and are not sent
  • roll_check_issue copies the source foot line and carries rows only, so a rolled issue needs put_check_issue to fill it. Roll once, at the end of the day
  • Market section names match trimmed and case insensitively. Write the heading as it reads here
  • A section with stored rows shows those rows; a section left unwritten shows sample content, so an unwritten section is visibly unwritten

Part Seven. The Today Page

/today is the daily check inside the Strategy shell. It renders the issue for today's SAST date where one exists and the most recent issue otherwise, and THE MASTHEAD AND THE CRUMB BOTH NAME THE ISSUE ON THE SCREEN. Until 21 September the crumb printed the business day of the request while the masthead printed the issue's own date, so the page could read "Sunday 21 September" over an issue dated the 17th, and neither figure was wrong about what it had counted.

The page opens on the newspaper

THERE IS NO TASK STRIP ON THIS PAGE. A strip of open tasks from the immediate basket sat above the masthead from 21 September to 23 September and it is REMOVED. /today opens directly on MicroLink Daily.

Nick asked for that removal three times and it was made and undone twice, so it is now held in place by machinery rather than by memory: the rendered page is asserted to carry no such heading by a test in tests/marketpanels.test.js, and the LIVE page is checked for the same string by scripts/smoke.mjs after every upload. Reinstating it fails npm test and prints DEPLOY FAILED. The decision is written into DECISIONS IN FORCE in the project record as DO NOT REINSTATE, dated 23 September 2026.

NOTHING WAS DELETED TO DO IT. site_tasks and sites keep every row, put_site_task still writes them, and /sites and /site?key= render them exactly as they did. Open tasks live on the Sites pages, which is where Part Seventeen now points.

What came up: the last three days, and every row arrives forgotten

THE SECTION SHOWS ONLY ITEMS DATED WITHIN THE LAST THREE DAYS, SAST, read off each row's own age_days. IF NOTHING FALLS INSIDE THE WINDOW THE SECTION IS EMPTY. There is no fallback, and a sample block under a heading that says what came up would be the page inventing a day.

Two rules qualify it, and both are deliberate. A row carrying NO age at all is KEPT: an absent figure is not evidence that a thing is old, and dropping on silence is how a block empties itself. An OPEN ENCOUNTER is never windowed out: it is not an item that came up on a day, it is a meeting nobody has written up, it renders every day until it holds a transcript or a note, and a three day rule would quietly forgive one on its fourth day.

Nothing is deleted by this rule. check_rows keeps every row and the API still answers the whole issue; the window decides only what the page draws.

What people said: five a band, ten days, and every quote arrives forgotten

AT MOST FIVE ITEMS PER SUBSECTION, over Objections, Offers, Suggestions and any other band the day's rows name, and NOTHING OLDER THAN TEN DAYS. Newest first inside each band. A band left with no rows is dropped rather than drawn empty, because a heading over nothing says a thing was said. A quote with no age is kept and sorts as if it were today's. check_saying keeps every row.

How a forget looks, and the commit button, from 24 September 2026

WHAT CAME UP AND WHAT PEOPLE SAID RENDER FORGET WITHOUT STRIKETHROUGH. A FORGET tick never strikes through, greys or otherwise restyles a row title, a row body or a quote. The tick box shows the state; the text renders normally.

EVERY CHECKBOX READS check_row_marks AND NOTHING ELSE. A box is drawn checked only where that table holds the mark for the issue, never from the seed ledger.

COMMIT TO MEMORY IS ENABLED WHENEVER ANY MARK EXISTS FOR THE ISSUE, in each section: the button counts what check_row_marks holds for its section, seeded ticks included, and ships live with that count in it. It no longer waits for an unsaved click, which is what left the 24 September issue uncommittable with thirteen rows ticked.

THE FORGET PRE-TICK

EVERY ROW IN WHAT CAME UP AND EVERY QUOTE IN WHAT PEOPLE SAID RENDERS WITH FORGET ALREADY TICKED. The default is forget; you untick what is worth keeping, rather than ticking away what is not.

THE TICK IS REAL AND IT IS PROVISIONAL, exactly like one you make by hand. It is written through setMark(), the one writer both sections have always used, so it lands in check_row_marks with the same issue, row id, row key, kind and say key a click would have written, and the commit counts it the same way.

THE SEED READS THE TABLE, NOT ITS LEDGER, SINCE 24 SEPTEMBER. On every load, an item on the page carrying no mark in check_row_marks is ticked forget, unless it was CLEARED BY HAND or has ALREADY BEEN COMMITTED. So when a put_check_rows or put_check_issue rewrite drops the marks of rows still on the sheet, the next load puts them back. check_forget_seeds records what the seed did and, since this date, every hand clear: an untick, a row left carrying nothing, or Clear selection writes seeded -1, and an item so marked is never seeded again. An item written to the issue later in the day is seeded on the next load.

TWO THINGS IT WILL NOT TOUCH. An item that ALREADY CARRIES ANY MARK is left as it is. And sample quotes are not seeded: where check_saying holds nothing for the issue the block falls back to the illustrative quotes in the content file, and deciding those on your behalf is not the page's business.

OPEN ENCOUNTERS ARE NOT PRE-TICKED, and neither is Market, Outreach or the Mailer. An open encounter is a meeting nobody has written up, and pre-forgetting one would be forgetting a meeting before it had been read.

THE KEEPING VERB STILL BEATS FORGET AND NOTHING ABOUT THE COMMIT RULE CHANGED. A seeded forget standing ALONE suppresses the row at commit, exactly as a hand one does. A seeded forget beside DISCUSS or EMAIL does not: the row carries. See Part Eight.

No freshness strip

No page draws a strip, banner or notice about when it was last written, since 24 September. Page freshness is a check the chat makes with read_page_freshness. See Part Nineteen.

The rest of the page, unchanged

The masthead, the tape, the five story positions, the results column, the rail, the feed with its verbs, the commit strip, What people are saying with its ticks, and MY WORK with its lanes are all exactly as they were. The Sent today figure reads sent_today from the same warmup derivation a refusal to send uses.

Part Eight. MicroLink Daily

Masthead

Title, then issue number, weekday, date and CAPE TOWN. The number increments by one and never repeats. The date is the date of the issue.

The tape, five slots, fixed order

SlotWhatShown as
1S&P 500level and day change per cent
2Nasdaqlevel and day change per cent
3USD/ZARrate and day change per cent
4Power, ERCOT or PJM day ahead around the clockdollars per MWh, naming the market
5Henry Hub gasdollars per MMBtu and day change per cent

Five, always. A slot with no reading carries the word pending, never a stale figure and never a guess. EUR/USD came out on 26 August so the Henry Hub price has room to sit on one line, and the Dow and the 30 year Treasury came out earlier. None of the three comes back.

The stories, five, data centre only

PositionColumnSubjectLength
1One, mainThe overall data centre marketabout 120 words
2Two, secondarySuppliers, anyone selling into a buildabout 90 words
3ThreeA universityabout 50 words
4Right rail, under the pod imageA wastewater works or a district heating networkabout 50 words
5FourEnvironmentalabout 70 words
  • One idea per story. The headline states it and does not sell it
  • Published in the last seven days. Where nothing moved, state the current position and date the last movement
  • Every story carries a source and a published date
  • No story two issues running unless the situation changed
  • Nothing about MicroLink goes in the five

The piece under the fold

About sixty words on environment, space, science, engineering or a person. Not data centres, not MicroLink, not markets. Carries a source. It is the one slot chosen on taste.

Results and scores

Derived at read time from live queries, so nothing is sent for them. Rows, carried, ticks in, sends available, sent today, replies all time, commits recorded, orgs evidenced, objections repeating, live conversations, contested data centres, new builds, capacity shortfall. Every one is a number or the word pending, and pending never renders as zero.

Writing an issue

  1. Read yesterday's issue so nothing repeats and the carried rows are known
  2. Fill the five tape slots, the five stories, the piece under the fold and the rows
  3. Write it in one put_check_issue call
  4. Query it back and confirm every slot is filled

One issue a day. A correction is a new write of the same issue, not a new issue number. Where a slot has no honest content it carries the word pending, because a hole is better than an invention.

Part Nine. Market

Seven sections, all searched fresh, all written, every morning. Five items each. Each item carries a headline, a date, a source and a short chip.

SectionWhat goes in it
Announcements and buildsCapacity announced, campuses, leases, ground breaks, named MW and location
Blocked and contestedMoratoria, rezoning refusals, permits pulled, litigation, community objection
Queue and capacityInterconnection queues, thresholds, capacity auctions, what is under construction
Prices and curtailmentPower and gas prices, load, curtailment, storage and inventory
Lead times reportedTransformers, switchgear, CDUs, cooling, fibre, copper, anything with a quoted wait
What it opensRules, obligations and programmes creating a door for heat recovery or the pod
Money movingCapex, funding rounds, offtakes, guarantees, public awards

Same sourcing rules as the five stories: a published date on everything, primary sources preferred, and where nothing moved in seven days, the current position with the last movement dated.

THE MARKET PAGE CARRIES NO STALE STRIP, since 24 September 2026. Nothing is drawn above the sections about when the page was last written. Whether Market is current is a check the chat makes with read_page_freshness at the close of Round B.

Part Ten. Outreach

/outreach is three things in this order: today's plan, the narrative, then the funnels and the ranked queue. NOTHING ON THIS PAGE SENDS and there is no route on the site that could send from it.

The lanes workbook, from 25 September 2026

THE WORKBOOK IS NOT A RECORD OF WHO HAS BEEN WRITTEN TO. On 25 September 2026 its Contacted columns showed none of the organisations Nick had already written to by hand, and the address column held addresses belonging to other organisations. A round drafted off the workbook alone would have written a second cold email to Octopus, a live funding counterparty, and to NVIDIA, a live relationship feeding the NVIDIA HQ meeting. Everything below exists so that cannot happen.

When Nick uploads the workbook

THE ANSWER IS ONE TABLE AND NOTHING ELSE: Lane, People held, Email ready. One row per lane, every tab holding people included (Heat Recovery and Space Archive as well as the lanes on People, 5 slots each), a total row, and one line saying whether a People To Connect To tab exists. Then the Contact Targets reminder the Read Me asks for.

EMAIL READY IS COUNTED AFTER CHECKS 1 TO 5 BELOW, not off the workbook's own columns. A person counts only where every one of those checks passes. People held is every named row.

The duplicate and address check, in this order

  1. Workbook columns. A row is contacted where Contacted reads anything other than Uncontacted OR Already contacted holds anything. The two columns disagree on many rows; either one is enough.
  2. The outreach record. list_queue with no status filter, every mailbox including the deactivated ones. Any address in it is out, and so is every other person at an organisation that appears in it. On 25 September it held 329 messages, 325 sent.
  3. Bounces. read_replies. A domain that has bounced is out for every person on it: AEP Ohio (aep.com, aepohio.com), LA Sanitation, LACSD, GLWA, Cuyahoga County, Met Council, Syracuse, Nassau County, Port of Cleveland, Revol Greens, and any new one that morning.
  4. The address belongs to the organisation. The address domain must be the organisation's own. Where it is not, the row is not ready, however complete it looks. On 25 September all eight ready Wastewater rows failed this: Austin Water on austinenergy.com, Buffalo Sewer Authority's general manager on houstontx.gov, Albany County on geneticsignatures.com, City of Toledo on utoledo.edu, Baltimore DPW on bge.com, Portland on pgn.com, Madison MSD on madisonei.com, Clean Water Services on cleanfocus.com. Also out: an address whose local part names someone else (Aimee Valentinetti on amiralles@brightfarms.com) and a generic role box.
  5. Live relationships. An organisation with a live relationship in the store, the meetings ledger, the inbox or memory is out whatever the workbook says: Octopus, NVIDIA, Vertiv, Anthropic, Cushman & Wakefield, CenTrio, Vicinity, Ever-Green, Rondo, Skyven, Rebound, Lion AI, American Local Harvest and every counterparty with a meeting in the last ninety days.
  6. Outlook Sent Items, before any draft. For every organisation in the round, outlook_email_search with recipient set to its domain in Nick@microlinkdc.com. ANY hit takes the whole organisation out: a cold send, a follow up, a bounce or a meeting thread. This check is run per domain on every row about to be drafted and is never skipped for time; if it cannot finish, the drafts are not made and the chat says how far it got. On 25 September it removed nineteen organisations the first four checks had passed: NVIDIA, Nebius, Calpine, Competitive Power Ventures, Mastronardi, AeroFarms, Atlantic Sapphire, City of Boston, City of Holyoke, City of Austin, Penn State, Aalborg, Aalto, Binghamton, ETH Zurich, University at Buffalo, STACK, HEET, PG&E, IBM and Hima Seafood.
  7. One person per organisation per round.

The mix, then the drafts

When Nick names the mix, the only output is ONE draft email in the chat, plus the method above in a few lines where he asks for it. No mailbox drafts and no queue writes at that step.

THE COLD OUTREACH BODY IS THE 7 SEPTEMBER 2026 xAI FORMAT, since 28 September 2026. DO NOT REMOVE. Nick's 7 September xAI mail (messages rows 130 to 132, "MicroLink & xAI"), set as the format on 16 September, is the only accepted cold outreach body: one outer div in the font stack 'Avenir Next', 'Avenir Next LT Pro', 'Avenir', 'Nunito Sans', 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif at 11pt, line-height 1.45, letter-spacing 0.005em, weight 400; each body paragraph with margin 0 0 12px 0 and nothing else, no blank spacer paragraph, no MsoNormal, no Calibri and no other font anywhere; then one signature paragraph, margin 0 0 4px 0, 10pt, line-height 1.35, holding Warmly,, Nick Searra, CEO and the www.microlinkdc.com link at 9pt in #3A5A8C, underlined, joined by line breaks. The outreach worker checks it on a template when put_template stores it, on every rendered body before a draft is created, and on every draft read back from Graph; a failure is refused or recorded failed by name, never drafted. A template drafts more than ONE message only once Nick has signed off a real draft made from it, and changing its body clears the sign off. On 28 September five batches of 40 went out in a drifted Calibri template and were deleted; that is why.

On "do the N": run check 6 over the whole set, replace every row it removes from the next clean row in the same lane, and where a lane runs out fill from the other named lanes and say so. Create the contacts with create_contacts, draft ONE sample with sample_template, Nick opens it in Outlook, sign_off_template on his yes, then draft_batch from that signed off template, approve false, in nick@microlinkdatacenters.com. NOTHING IS SENT; Nick presses send. Then put_outreach_day_plan with planned and drafted per lane and a note naming what each lane lost and why.

The report back is short: the table of planned against drafted, then one line per reason rows were dropped, naming the organisations.

Today's plan

At the top: one row per lane, PLANNED beside DRAFTED, with the workbook the mix came out of named underneath. It reads outreach_day_plan for today's SAST date, written by put_outreach_day_plan in Round C every morning off the current lanes workbook, Seven_Lanes.xlsx from 25 September 2026.

Planned and drafted are kept apart deliberately: A PLAN THAT WAS NOT CARRIED OUT IS THE FACT WORTH READING, and one number covering both would hide it. A day with no plan says so in one line and names the tool that writes one; it does not draw an empty table. The tool upserts on the date plus the lane and issues no delete, so correcting one lane does not clear the day.

For reference, the mix drafted on 21 September 2026 was 45 drafts in nick@ Drafts: Institutional 5, Customer 5, District Heating 5, Public Sector 10, Wastewater 10, Aligned Employer 10. On 25 September it was 40: Wastewater 0, Institutional and Municipal 6, Public Sector 15, Heat Recovery 19. THE CUSTOMER LANE IS NOT BLOCKED and the Read Me body is used for every lane.

The narrative, computed

THE OPENING PARAGRAPH IS ARITHMETIC OVER THE STORE AT REQUEST TIME AND IS NOT READ OUT OF A TABLE. It counts the messages sent excluding test sends, the days they went out across, the organisations they reached, the first and last send date, and WHAT CAME BACK BROKEN INTO WHAT IT IS MADE OF: replies from a person, automatic replies and bounces.

It used to be prose. outreach_narrative held one row written on 25 August 2026 and the page printed it every day for twenty-seven days: "Sixty-seven messages have gone out across two days to nineteen organisations, and not one reply has come back." By 21 September 245 messages had gone out and 24 replies had come back. Every figure in that sentence was wrong and nothing on the page said so, because a sentence stored as prose has no idea what it describes.

The table is not dropped and the row is not deleted. PUT /api/outreach/narrative still writes it; the page simply stopped reading it.

A deactivated mailbox's messages ARE counted: 73 of the 245 went from the two mailboxes that are now closed, and those are people who were written to. The active filter is about what can SEND; this paragraph is about what was SENT.

It does not count how many replies were answered, and that is deliberate

The paragraph carried "N of them not yet answered" until 21 September 2026. It counted replies.read_state <> 'answered', and NOTHING HAS EVER WRITTEN 'answered' TO THAT COLUMN: the reply sweep hardcodes unread on insert and leaves the column alone on a re-run, and live the table holds one distinct value across all 24 rows. The clause was always true, so the figure was the reply count wearing a second hat.

IT WAS NOT REPLACED BY A DERIVED FIGURE AND THAT WAS TRIED. The candidate rule, "a reply is answered where a later message went to the same contact", returns zero over the live store and is wrong in both directions besides: messages is the outreach drip ledger and not Nick's mailbox, so a real answer written from Outlook can never appear in it, while a row that IS there is the next cold approach being counted as an answer. Seven of the 24 replies carry no message_id at all.

So the page prints what the store knows and no more. FIFTEEN OF THE TWENTY-FOUR ARE BOUNCES and two are automatic; seven are people. One figure covering all three read as twenty-four people writing back.

The funnels and the queue

Four class funnels, held through reachable, screened, contacted, opened and replied, with disqualified as an exit in its own right carrying its own figure and no drop. Then WHO TO REACH AND WHY, the recommendations, then the ranked queue, sixty rows, each taking DISCUSS, EMAIL and FORGET, with the commit strip at the foot. The capacity line is derived from the same warmup rule a refusal to send uses, and it counts SENDABLE mailboxes only.

Part Eleven. Meetings, One Page

/meetings IS ONE PAGE, IN FOUR COLUMNS ON A DESKTOP, since 24 September 2026. FUTURE MEETINGS SPANS THE FIRST TWO COLUMNS AND PAST MEETINGS THE LAST TWO, each half's cards flowing across both of its columns under ONE heading per half. On a narrow screen it collapses to one column with FUTURE FIRST, because on a phone the week ahead is what somebody opens this page for.

THE RULE FOR THIS PAGE AND FOR EVERY FUTURE PIPELINE: A NEW SOURCE JOINS THE EXISTING VIEW. A PAGE IS NEVER MOVED OFF THE TABLES THAT HOLD ITS HISTORY.

Why that rule is written down

Past Meetings read encounters, encounter_orgs and encounter_people and nothing else. On 15 September 2026 the profile pages landed a second meeting store, meetings with its own organisation and attendee joins, and the 256 encounters were carried into it; the old three were left empty, in place, still read by a page that still counted them. So the index served 161 cards on 8 September and 0 on 21 September while the ledger had grown from 256 rows to 574. NOT ONE ROW WAS LOST. The page was reading a table nobody writes any more, and it answered HTTP 200 for six days while doing it.

The repair was NOT to point the page at the new table, which is the same mistake with the names swapped: the next source would empty that one in its turn. It is one joined view.

The joined view, behind the Past half

ONE VIEW OVER ALL THREE LEDGERS, joined, never replaced:

  • encounters with its organisation and people joins, and meeting_transcripts for the transcript answer.
  • meetings plus meeting_orgs and meeting_people.
  • meeting_records plus meeting_record_orgs, which is where Granola lands with its transcript, its summary_md and its key_points_json.

Duplicates are folded in this order, first match winning: meeting_records.encounter_id; then meeting_records.calendar_event_id; then meetings.granola_id = meeting_records.source_note_id; then encounter_aliases. Whatever is written against an absorbed key is carried onto the survivor, the notes especially, and since 24 September the summary and key points too. A record that folds onto nothing keeps its own key and is a meeting in its own right rather than being dropped.

encounters holds 0 rows today AND IT IS STILL READ, which is the whole point: a restore into it appears on the page without a line of code changing. There is a test on exactly that.

ONE CARD COMPONENT, BOTH HALVES

The two halves are built from the SAME component, so they read as one design rather than two. Every card carries the company name, how many meetings it holds, the domain and the class chip; then a two cell strip; then its body.

A PAST CARD's strip is MET and SILENT. Its body is the latest meeting's title and date, linking to that meeting on the company page at its own anchor. UNDER THE TITLE, WHERE THE LINKED meeting_records ROW HOLDS THEM, THE SUMMARY AND THE KEY POINTS: the summary as plain text trimmed to about sixty words, then up to four key points. A meeting holding neither draws nothing there. Then the standing line where one is written; a company with no subject_standing row renders no standing block at all: not an empty box, not a dash.

A FUTURE CARD's strip is MET and WHEN, since 24 September; it carries no SILENT cell. WHEN is the first meeting's start in SAST, weekday, date and time, with the days until it beneath: today, tomorrow, in 3d. Its body is the week's meetings with that company, a line each: the time, the title and the date, then the external attendees, then the one or two lines of company context. Under them the MET BEFORE line, linking to /company?key= where the org_key resolves. TWO MEETINGS WITH ONE COMPANY ARE ONE CARD WITH TWO LINES, not two cards: the Past half is one card per company too.

MET ON A FUTURE CARD COMES OFF THE PAST LEDGER, looked up by the same org_key both sides canonicalise through orgAliases, so a card that says met before cannot lead to a page that says never met. A company never met shows an em dash rather than a zero: never having met somebody is not the same as having met them no times.

Discuss and commit on meetings

EVERY CARD ON BOTH HALVES CARRIES ONE TICK BOX, DISCUSS, since 25 September 2026, at Nick's request that day. It sits in the card's top right beside the chip, drawn in the same size, face and checked state as the DISCUSS box on /today, and it is written the moment it is ticked through POST /api/meetings/mark into meeting_discuss_marks, keyed by card_key and side: a past card by its subject_key, a future card by the org_key the Future half groups on, or a tilde and the title for a meeting with no company. No other verb sits beside it.

ONE BUTTON, top right on the headings' baseline, reads Commit, discuss N, N counting meeting_discuss_marks, disabled at zero; on a narrow screen it sits above Future, right aligned. It writes one meeting_discuss_commits row and one meeting_discuss_commit_rows row per ticked card, carrying the subject_key of a past card and the event ids of a future one, READS THEM BACK, and only on a match clears the marks it committed; on a mismatch nothing is cleared and every tick stands. The page reloads. The strategy MCP does the same through set_meeting_mark, commit_meetings and read_meeting_marks.

A COMMITTED DISCUSS IS A CONVERSATION TOPIC IN ROUND TWO, read under DISCUSS with its card title. See Part Three, step three. Nothing else was added to this page. The EMAIL and RESEARCH ticks and their Commit to memory strip are a separate feature and are unchanged.

WHAT WAS REMOVED ON 23 SEPTEMBER 2026

All of it at the owner's request, and each one asserted absent from the rendered page by a test in tests/meetingspage.test.js AND from the LIVE page by scripts/smoke.mjs after every upload. Reinstating any of them fails npm test and prints DEPLOY FAILED.

  • The Past Meetings and Future Meetings link pair, which is what made this two pages. There is one page now.
  • By company, By person and By date. The Past half is ALWAYS grouped by company, most recently met first, and that order is in the served bytes rather than applied by a script after the page arrives.
  • The class pills All, Host, Partner, Customer, Funder, Never written up and Silent 60d+, the sort select and the search box.
  • PEOPLE and WRITTEN UP from the card's stats strip, and the OPEN block from the card entirely.

NOTHING WAS DELETED TO DO ANY OF IT. people_count, written_up and open_line are still derived on every card and still rendered on /company; meetingsData() still builds the by-person and by-date sets on every request. Only this page stopped drawing them. On 24 September SILENT came off the future card the same way: silent_days is still derived and still drawn on every past card.

What did not move

THE EXTERNAL AND INTERNAL TOGGLE STAYS in the page header line, hard right, with the date and time to the left of it on that same line. IT FILTERS BOTH HALVES. The split is decided by the attendee domain test and never by the title; on the Future half a row left with no external attendee and no org_key is a meeting among ourselves, which is derived rather than stored.

Commit to memory with its two verbs is untouched, and so are the /company?key= profiles and the /meeting?id= permanent redirects, which resolve off the joined view so every url that ever worked still works. SO DO THE TWO OLD URLS: /meetings?when=future and /meetings?when=past are READ AND IGNORED rather than refused, both rendering this one page. Smoke checks each, and checks the four column layout and the WHEN cell.

The meetings that resolve to no company are still named and counted at the foot, linking to the list itself. Each company profile still shows every meeting with that company FROM ALL THREE SOURCES. /meetings draws no transcript missing chip; a Granola meeting holding no words is found and backfilled in Part Three, Round B.

Where the week comes from

future_meetings, written by put_future_meetings, keyed on event_id, read back by read_future_meetings. IT IS A SEPARATE TABLE AND NOT A DATE FILTER OVER THE PAST LEDGER, because a row in meetings says a meeting HAPPENED and a row here says one is in the calendar. When one of these happens, put_meeting writes it to meeting_records with its full transcript and the row here simply falls out of the window. Nothing migrates between them and nothing is deleted: the plan and the record are both true, of different days.

An empty week says so in one line rather than drawing seven empty headings, and a half left with nothing on the current side of the toggle says that too.

Part Twelve. Email And The Mailer

THE MAILER IS INBOX REPLIES ONLY, since 21 September 2026. It holds ONE CARD PER EMAIL IN NICK'S INBOX, each carrying a proposed reply to that exact email, and nothing else. NOTHING ON THE SITE OR THE STRATEGY MCP EVER SENDS.

What a card is

THE CARD SHOWS THE WHOLE INBOUND EMAIL, since 24 September 2026. Each card shows, in this order: the sender; the subject; the received time in SAST; then THE FULL INBOUND EMAIL as plain text in an inset block, with the quoted history below the first reply marker ("On … wrote:", an Original Message line, a From: header after a blank line, or the first line quoted with >) folded behind Show earlier messages; then the proposed reply editor underneath. Where the full body is not held the card falls back to the short excerpt, exactly as before. Everything inbound is text somebody else wrote and it reaches the page as text, never as markup.

THE OUTCOME LINE, since 24 September 2026. Once the chat has recorded what it did on a committed card, ONE plain line sits under the card's footer: Draft created and the time in SAST; Invite sent to N, date and time in SAST; Email cleared and the time in SAST. Only what happened is said, and a card with no recorded outcome draws nothing extra.

THE MAILER IS INBOX ONLY, NO FALLBACK DAY, since 28 September 2026. DO NOT REINSTATE. /mailer shows ONE CARD FOR EACH EMAIL THAT IS IN NICK'S INBOX (Nick@microlinkdc.com, folder Inbox) RIGHT NOW, AND NOTHING ELSE. An email that has left the inbox (replied, moved, archived, deleted) is never shown, whatever day its mailer_drafts row carries. There is NO fallback to an earlier day, ever. An empty inbox shows the breadcrumb, the sends left line and ZERO cards: no sample, no explanatory paragraph, no empty state message, no banner, strip, notice, chip or panel. If the inbox cannot be read, the page shows zero cards; it never falls back to stored rows. HOW IT KNOWS: the chat writes the whole Inbox list with put_mailer_inbox (each message's Graph id, internetMessageId, conversationId and receivedDateTime; an empty list is an empty inbox) into mailer_inbox, append only, the newest snapshot being the inbox (migration 0050). The page reads it on every request with a five second timeout and shows each mailer_drafts row whose inbound_graph_id or inbound_message_id matches, on any issue date, one card per email (the newest row where several answer it), newest received first, under the one line N emails in your inbox with a proposed reply. A card that already has a commit row is read only; POST /api/mailer/commit takes the cards shown and refuses by name any card not in the inbox now.

A COMMITTED DAY IS SHOWN AS COMMITTED, since 25 September 2026. A day holding a row in mailer_commits renders every card read only with the verbs its commit row recorded, nothing editable, no Save or Invite, a saved invite as its one line, and NO COMMIT BUTTON: the foot reads COMMITTED with the commit number, its time in SAST and what it decided. The commit button is offered only on a day with no commit row, and POST /api/mailer/commit refuses a day already committed, by name, writing nothing.

What is hidden and what is kept

EVERY DRAFT OF THE DAY SHOWN IS ON THE PAGE, since 25 September 2026; until then only rows carrying an inbound_message_id were. NOTHING IS DELETED: the twelve old cold outreach and follow up drafts sit on days of their own in mailer_drafts, every row mailer_commit_rows points at still resolves, and the messages and replies tables are untouched. THE COLD OUTREACH AND FOLLOW UP PATHS ARE OFF THIS PAGE. A first approach is decided on /outreach, which is the page that ranks who to write to next.

A commit decides the cards the page showed and no others: every draft of its day.

The controls, since 24 September 2026

EVERY CARD CARRIES LEAVE, DISCUSS, SEND, IN THAT ORDER, then Save and Invite. SEND MEANS SEND TO DRAFTS and it is TICKED BY DEFAULT: the page is opt out, so a card nobody has touched goes to drafts. SEND is exclusive of the other two: ticking LEAVE or DISCUSS unticks SEND, and ticking SEND unticks LEAVE and DISCUSS; LEAVE and DISCUSS may sit together. Each is stored the moment it is ticked, through the same mark route: leave_mark, discuss_mark and send_mark on mailer_drafts, where send_mark NULL means untouched (migration 0047). The strip reads Would send to drafts N, leave N, discuss N and the button Commit, send N to drafts. Clear selection puts every card back to untouched, which is SEND. A HELD card names why it is held and renders the same editor an unheld one does; it never goes to drafts. An unfilled placeholder blocks a card.

INVITE, BESIDE SAVE, ON EVERY CARD. It opens the card's own panel, inside the card in normal flow: attendees one per line; the title; the date; the start time in SAST; the duration in minutes, 30 by default; a Teams link, yes by default; and Save invite. Saving writes mailer_drafts.invite_json through POST /api/mailer/invite, and a malformed field is refused by name with nothing written. A saved invite shows on the card as ONE line, Invite: title, date, time SAST, duration, N attendees, with Edit and Remove. THE INVITE IS INDEPENDENT OF LEAVE, DISCUSS AND SEND.

EVERY PERSON ON THE EMAIL, since the sixth run of 24 September 2026. The attendees prefill with the sender, then every To recipient, then every CC, de-duplicated case insensitively, leaving out Nick's own microlinkdc.com address. MicroLink colleagues on the email STAY IN. The site's mailerAttendees and the MCP's read_mailer_day compute the same list by the same rule.

THE CHAT'S PROPOSED INVITE, PREFILLED AND EDITABLE. Where the chat set an invite_proposal on the card and no invite is saved yet, the panel opens with its title, date, start time, duration and Teams choice, and the card shows ONE line above its footer: Proposed invite: title, date, time SAST, duration, N attendees. EVERY FIELD STAYS EDITABLE; nothing is saved or sent until Nick presses Save invite, and a saved invite then replaces the proposal in the panel and on the card. The precedence is: the saved invite, then the proposal, then the defaults.

THE TITLE RULE, since 29 September 2026, where the chat proposed no title. A named series keeps its name, which the chat sets in the proposal. A company meeting is Organisation & MicroLink DC. No organisation is FirstName & Nick, FirstName from the card's contact name, else the address part before the first dot or @, capitalised; with more than one outside attendee and no organisation the sender's first name is used the same way. There is never a placeholder name. The To and CC lists are always stored as clean arrays of plain addresses. The title field spans the whole panel so a whole title shows.

The commit

A COMMIT RECORDS THE CHOICE AND CREATES NO DRAFT AND NO EVENT: commitMailer, and the strategy MCP's commit_mailer by the same rule, writes one mailer_commits row and one mailer_commit_rows row per card, reads them back, and resets the marks. A card is drafted only where SEND is on; otherwise left, discuss or none, or held or blocked. The row's marks list includes send, and THE CARD'S INVITE IS COPIED onto the row's invite_json. THE SITE HOLDS NO GRAPH CREDENTIAL AND NO ROUTE TO A MAILBOX OR A CALENDAR. After Nick confirms the listed actions in chat, the chat creates each confirmed SEND card as a NEW draft in the Drafts folder of nick@microlinkdatacenters.com, through draft_reply_card in the 7 September xAI format (To the sender, CC everyone else on the email except Nick, subject RE: plus the original subject, never a reply in the thread) and each invite as an event with its attendees, through the Microsoft 365 connector, and records each with put_mailer_outcome. The whole sequence is Part Three, step five; the rules are Part Fourteen.

The writers

put_mailer_drafts WRITES A WHOLE DAY AND, SINCE 25 SEPTEMBER 2026, EVERY INBOUND AND INVITE FIELD: inbound_message_id, inbound_graph_id, inbound_conversation_id, inbound_from, inbound_subject, inbound_received_at, inbound_excerpt, inbound_body_text, and the JSON fields inbound_cc_json, inbound_to_json, invite_json and invite_proposal_json, each a JSON string or an object; invite_json is stored in the shape Save invite writes. It had dropped every one of them silently. A replace carries each field from the same address and mailbox where a draft passes none, the rule graph_message_id follows. A FIELD IT DOES NOT KNOW REFUSES THE WHOLE CALL BY NAME BEFORE ANYTHING IS DELETED.

put_mailer_reply writes one card: the inbound message id (the internet message id, angle bracket form), sender, subject, received time and excerpt, the whole inbound email as inbound_body_text, inbound_graph_id (the Outlook Graph message id, which finds the email in the Inbox list and in Sent Items), inbound_conversation_id, inbound_to, every To recipient, and inbound_cc, every address copied on the email, the proposed reply as HTML, the mailbox, and, where a meeting is wanted or implied, invite_proposal {title, date yyyy-mm-dd, start_sast HH:MM, duration_min 5 to 600, 30 by default, teams, true by default}; a malformed proposal is REFUSED BY NAME with nothing written (inbound_to_json and invite_proposal_json, migration 0048). THE MORNING CHAT PASSES THE FULL BODY, BOTH IDS, THE TO AND CC LISTS FOR EVERY INBOX EMAIL IT ANSWERS; a second call that omits one keeps what is held. Without them the Invite panel prefills only whom it holds. THE MAILBOX IS REQUIRED AND IS NEVER GUESSED. It is idempotent on inbound_message_id.

put_mailer_outcome records what the chat did on one committed card, found by commit_id and inbound_message_id: confirmed_at, draft_graph_id, draft_created_at, event_id, event_created_at, invitees_sent_json, inbound_deleted_at and outcome_note. A field not passed is kept. A commit, card or commit row that does not exist is REFUSED BY NAME. It deletes nothing and sends nothing.

read_mailer_day returns every card's send_on, parsed invite, inbound_to_json, invite_proposal_json and parsed invite_proposal, and attendees, the computed list the panel prefills; counts would_draft by SEND and invites; and, for a committed day, THE EXECUTION VIEW: each committed card with its verbs, send state, invite in full, inbound ids, mailbox, recipient, subject, reply and every outcome column, and a summary of to_draft (SEND cards), to_invite (committed invites), drafted, invited, awaiting_confirmation and inbound_eligible_for_cleanup.

Sending, where it does happen

The outreach worker at outreach-mcp.microlinkdc.com is what drafts and sends outreach mail, on a paced drip, from an ACTIVE mailbox only, and every rule about a send lives on the site side behind its API. ONLY AN ACTIVE MAILBOX CAN SEND: a deactivated mailbox is refused BY NAME and is never silently replaced by another. That is a different path from this page and it is not reached from it.

Part Thirteen. Research

Two grades, proposed and corrected

Most research is ENRICHMENT: the same chat runs it inline with search and writes it to the profile. A BRIEF is a separate deep pass in its own chat, only where the question is genuinely large. Propose a grade per topic and let him correct it before anything runs.

Three tiers of depth

Tier one, machine enrichment. Every organisation through the screen. Firmographics only, no judgement, so no person. Apollo and Clay. Once, refreshed quarterly.

Tier two, desk research. A few hundred organisations, published sources only, the same four questions of each. Cohorts of twelve sharing a class and subtype, three sessions at a time, thirty six a round.

Tier three, deep research. Named targets only, under two hundred ever. The eight section brief, one document each.

The four questions

  1. What are they building. Announced projects with capacity, location and date. Permits, queue positions, tenders, roles being hired
  2. Where are they going. Markets today, markets named next, constraints on growth
  3. Who are they tied to. Parent, subsidiaries, investors, named suppliers and customers, shared directors, anyone already known inside
  4. What would make them move. The constraint said out loud, a dated window, who decides and who blocks, what they need from us in writing

Question four produces why_now, which is empty on every contact row and is the largest gap in the register.

The shape of a brief

Fixed context first, marked do not research. Then numbered returns, numbered continuously across sections so nothing is answered twice or missed. Then the rules: every fact carries a source name, a url and a published date; primary sources preferred and aggregators named as such; where an endpoint was checked and returned nothing, say what was checked and what came back; state plainly where evidence does not exist; numbers written the way the source wrote them; findings only, no recommendations; close with what could not be found and what was checked in trying.

How a fact lands

Field and value, source name and url, published date, captured date, a snapshot because urls rot, and the tier that produced it. A stale fact reverts to an open slot rather than showing an old value. What they said and dated events never decay; ownership, sector and geography decay slowly; headcount, funding and roles quarterly; titles and deliverability fast; programme windows and permits expire on their date.

Part Fourteen. Standing Rules

No page element unless he asks for it

24 SEPTEMBER 2026. NEVER ADD A PAGE ELEMENT OF ANY KIND UNLESS NICK EXPLICITLY REQUESTS IT: no banner, strip, notice, chip, badge, panel or anything else a page draws. A session that thinks a page needs one says so in the chat and waits. The stale strip was added unrequested on 21 September and removed at his request on 24 September; scripts/smoke.mjs fails the deploy if it returns.

Email, invites and deletion

24 SEPTEMBER 2026. THE CHAT NEVER SENDS AN EMAIL. Drafts only: from 29 September 2026 every confirmed SEND card is created as a NEW draft in the Drafts folder of nick@microlinkdatacenters.com through draft_reply_card, in the 7 September xAI format (Nick switches the From), To the sender, CC everyone else on the email except Nick, subject RE: plus the original subject, never a reply in the thread, and Nick presses send.

INVITES ARE THE ONE EXCEPTION, and they go out ONLY AFTER NICK CONFIRMS THE LISTED ACTIONS IN CHAT: the chat lists every invite with its title, date, time in SAST, duration, every attendee and whether the slot is free on his calendar, and creates none until he says so.

THE CHAT NEVER DELETES AN EMAIL UNLESS NICK'S REPLY TO IT IS IN SENT ITEMS, and then only MOVES it to Deleted Items. Never an email he has not replied to, and never a hard delete.

No tool on the strategy MCP server sends mail, creates events or deletes mail. Those actions stay with the chat through the Microsoft 365 connector, after Nick confirms, and each is recorded with put_mailer_outcome. See Part Three, step five, and Part Twelve.

Time

Store every timestamp as UTC. Derive every date, day boundary, age and cap window from SAST at a fixed UTC plus two, because South Africa runs no daylight saving. Display every time in SAST with the offset. Lagos is a travel fact, not a change of business day.

Concurrency

Four Claude Code sessions run on the repositories at once. A session declares its file scope and gates on that scope. It rebases before build and again before deploy, because the deploy ships the whole site and a stale tree reverts other sessions' shipped work. It stages named paths only. It re-reads the state file immediately before rewriting it. A conflict in the navigation keeps every change from both sides.

What belongs to him

  • Ticking and committing
  • Pressing send. The chat drafts, and mail leaves from his Outlook when he sends it
  • Confirming the listed actions after a Mailer commit, before any draft or invite is created
  • Grading a research topic
  • Asking for a deletion. Name the exact keys in the statement
  • Asking for a page element. Nothing is added to a page he has not asked for

What the chat writes

  • The check, market, recommendation and subject tables. Not the register itself, and never Loom
  • Unknown renders as pending, and pending is not zero
  • Class is a set, never one value
  • Security, authentication, access control and gating change only when he asks for it

Writing

  • A heading names its section and does not sell it
  • Metric first, imperial in brackets
  • Numbers written the way the source wrote them, and the source named
  • ERE alongside PUE. Hosts are partners
  • No dashes as separators outside the locked subject line

Part Fifteen. Where Things Live

The three repositories

RepositoryWhat it isWhere it runs
microlinkfundingsprintThe site, Daybreak since 25 September 2026 (it was called Strategy): every page in this manual, and the outreach API the worker callsCloudflare Pages, strategy.microlinkdc.com and fundingsprint.microlinkdc.com
microlink-strategy-mcpThe Strategy MCP, the tools a chat writes the site throughA Worker, strategy-mcp.microlinkdc.com
microlink-outreach-workerThe outreach MCP: drafting, the paced drip, the reply sweep. It holds NO D1 binding at all and reads the site's API. IT IS THE ONLY THING IN THE THREE THAT HOLDS A GRAPH CREDENTIAL.A Worker, outreach-mcp.microlinkdc.com

The record of record is in SharePoint

THE RECORD OF RECORD IS Claude Code Record / proj-strategy IN SHAREPOINT, since 21 September 2026. GIT .record/ IS THE MIRROR. EVERY SESSION WRITES BOTH, SHAREPOINT FIRST.

The library is the SharePoint site Microlinkdatacenters988, Shared Documents, folder Claude Code Record. It holds one folder per project. proj-strategy holds strategy_STATE.md, strategy_LOG.md and strategy_RUN.md, and those three cover all three repositories.

proj-outreach-hub, proj-fundingsprint and proj-strategy-mcp in the same library are SUPERSEDED and each says so on its first line. They are kept whole and nothing was deleted from any of them.

SharePoint is the copy other people and other machines can read; git is the durable copy with a history. Writing git alone leaves the record where only one laptop can see it, which is the failure this arrangement exists to end. A file that cannot be READ BACK FROM SHAREPOINT AT ITS NEW BYTE SIZE was not written.

The write route, and it is the only one

The library is NOT synced to this machine and cannot be reached through the filesystem. Proved on 21 September 2026: no OneDrive sync provider carries a namespace under that site, rclone, the m365 CLI and the az CLI are all absent, and Microsoft.Graph PowerShell is installed but its token cache holds an account and NO REFRESH TOKEN, so a connect falls to the Windows broker and fails asking for a window handle nothing can give it.

IT IS REACHED THROUGH THE MICROSOFT 365 CONNECTOR, an MCP server Claude Code already has authorised. Its tools are sharepoint_folder_search, sharepoint_create_folder, sharepoint_upload_file, sharepoint_update_file, sharepoint_copy_item and read_resource. Where a session does not carry those tools itself, it runs them in a print-mode subprocess, which loads the same connectors. Each repository's CLAUDE.md carries the folder id, the drive id and the exact command.

Dead paths

G:\My Drive\..., H:\My Drive\..., the Google Drive folder under the user profile, and any "try each drive letter in turn" instruction. All retired. The old tree under OneDrive - Microlink data centers / 00 Admin / Old drive / 04 Operations / 06 Project Record was copied into the SharePoint library on 21 September 2026 and is FROZEN: read it for history, never write to it.

The store

D1 fundingsprint, id bdd2243f-eb76-4e3c-826c-8f33e4e4023d, 145 tables on 21 September 2026. Migrations are applied by wrangler d1 execute fundingsprint --remote --file and never by d1 migrations apply, whose ledger is wrong about thirteen files.

page_contracts

One row per route the site serves, naming what it reads, who writes it, how often and what stale means. It is what the stale strip on each page reads, what read_page_freshness answers a chat with, and what the smoke test walks after a deploy. See Part Nineteen.

Deploying

Through npm run deploy and its predeploy guard, never by calling wrangler directly. The guard fetches, checks the branch, checks the tree is clean and checks the commit is already on the default branch on GitHub. DEPLOY_SCOPE names your own paths as EXPLICIT FILES, not directory prefixes, while another session's files sit in the tree. After the upload, scripts/smoke.mjs fetches every route and a failing route reports the deploy as FAILED.

Part Sixteen. State Of The Build

Generated from the live census of 6 October 2026 at about 06:45 SAST, taken AFTER that day's run had deployed (site Pages e5e06d90, commit f61e1b0; strategy MCP version ae096b1d, commit ffad023; outreach worker version 64eecd63, commit b3e76b0). Rewritten at the end of every Claude Code run on this site, with Part Nineteen, so this part cannot drift from the build. The 30 September to 5 October runs left no census here; figures below marked 29 September are as that census read them.

6 OCTOBER, THE SENT AND UNSENT LEDGER. DO NOT REMOVE. outreach_sent holds every address ever emailed, from nick@microlinkdatacenters.com and Nick@microlinkdc.com alike, rebuilt from the append only outreach_sent_events; outreach_unsent is everyone else. Every draft path refuses a sent address by name, with no override; the Funder and Academic lanes read sent from it; the worker reads Nick@microlinkdc.com Sent Items on the fifteen minute cron. Part Twenty-Three.

29 SEPTEMBER, TWO JOBS. DO NOT REMOVE EITHER. THE DRAFT GUARD: draft_batch refuses by name, drafting nothing, a batch of more than one row without Nick's instruction copied verbatim from today's chat (approval_quote, containing draft, drafts or send to drafts, and approval_date, today in SAST); refuses while any drafted, approved or held row of the same template in the same mailbox is from an earlier SAST day, with no override; and caps at 40 a day per template per mailbox. One row is exempt from the approval. Every message drafted carries its template and the approval (migration 0051). THE MAILER: the To and CC lists are always stored as clean arrays of plain addresses; the Invite panel leaves Nick out and keeps colleagues; a card with no organisation and no proposed title is FirstName & Nick, never a placeholder name; the signature check reads the reply body and passes the house signature and the 7 September xAI one; no greeting check where a card has no rung; and a confirmed SEND card becomes a NEW draft (from the third run in nick@microlinkdatacenters.com through draft_reply_card), To the sender, CC everyone else on the email except Nick, subject RE: plus the original subject, not a reply in the thread. Parts Three, Twelve and Fourteen carry it.

29 SEPTEMBER, THE THIRD RUN. THE FUNDER LANE IS HELD ON THE SITE, in outreach_funder_lane (0052, loaded from Funder_Lane_Ranked.xlsx: 223 ranked across Days 1 to 15, 30 held, Day 0 the 13 of 29 Sep), drawn on /outreach beside Today's plan with each row's state joined from the queue, and read and corrected one row at a time through read_funder_lane and put_funder_lane_row. Part Twenty-One. MAILER DRAFTS ARE BUILT IN nick@microlinkdatacenters.com through draft_reply_card, in the xAI format, because the Microsoft 365 connector cannot carry the Avenir Next font or inline styles; Nick switches the From.

28 SEPTEMBER: THE MAILER IS INBOX ONLY, NO FALLBACK DAY; THE COLD OUTREACH BODY IS THE 7 SEP xAI FORMAT, CHECKED ON EVERY DRAFT, AND NO BATCH WITHOUT A SIGNED OFF TEMPLATE. Parts Twelve and Ten. 25 SEPTEMBER: one DISCUSS tick on every /meetings card and one Commit, discuss N button (0049); the site is Daybreak. 24 SEPTEMBER, six runs: Today commits a seeded day, the stale strip is gone, Meetings is four columns, the Mailer shows the whole email and carries LEAVE, DISCUSS, SEND and Invite (0045 to 0048), and the nav is Today, Market, Meetings, Mailer, then REFERENCE. NOTHING WAS DELETED FROM THE STORE TO DO ANY OF IT.

The navigation

TODAY, MARKET, MEETINGS, MAILER, each with its icon, then the REFERENCE heading, once, then SITES, OUTREACH, DIRECTORY, MANUAL, Manual last, with the empty icon slot. src/strategy.js sidebar(); asserted by tests/nav24.test.js and by a smoke check on every route.

The routes the site serves

RouteRendersState
/ and /todayThe daily check: What came up, What people said, My work. NO TASK STRIP. A forget restyles nothing.Live. Issue 2026-09-29.
/meetingsONE PAGE, FOUR COLUMNS; ONE DISCUSS box on every card and ONE Commit, discuss N buttonLive. 197 cards, none ticked; no discuss commit yet.
/marketFourteen sections and one page wide commit bar.Live. Stale by read_page_freshness, last written 25 September.
/mailerINBOX ONLY: one card per email in Nick@microlinkdc.com's Inbox now, matched to mailer_drafts on any day. Each card: sender, subject, received time SAST, the whole inbound email, the reply editor; LEAVE, DISCUSS, SEND, Save and Invite; the checks row (words, paragraphs, the greeting check only where a rung is held, the signature check, placeholders); the outcome line under a committed card. A card with a commit row is read only.Live. 9 cards, all 9 committed and read only; every one reads signature exact.
/sitesONE GRID, newest write first; one line stat row.Live. 16 of 16 cards on one stat row.
/site?key=The organisation profile's components; the full stored MW text.Live.
/outreachToday's plan, the Funder lane (from 29 Sep), the computed narrative, four funnels, the ranked queueLive. Funder lane: Day 0 13 sent, Days 1 to 15 223 not drafted, Held 30.
/company?key= and /meeting?id=One company off the joined view; one meeting, or a 301 to its company anchored at itLive.
/directory, /org, /person, /browse, /place, /service, /candidates, /candidate, /corpusrecordThe register, the three profiles, places, candidates and the corpusLive. /directory stale by contract.
/manualThis document, twenty-one parts, out of manual_sectionsLive. Parts 3, 12, 14, 16, 19, 20 and 21 carry revision 2026-09-29; Parts 10 and 18 carry 2026-09-28; Parts 1, 4, 11 and 15 carry 2026-09-25; Parts 7, 9 and 17 carry 2026-09-24; Parts 2, 5, 6, 8 and 13 carry 2026-09-02.
/days and /mail-sentRemoved 21 September 2026404 each. Their tables are untouched.

The store

D1 fundingsprint, 156 tables (wrangler's count, 6 October). 0054, 6 October: outreach_sent, outreach_sent_events, outreach_sent_runs and the view outreach_unsent, append only; THE NEXT MIGRATION NUMBER IS 0055. 0053, 1 October: outreach_academic_lane, 54 rows. 0052, 29 September: outreach_funder_lane, 266 rows, append only. 0051, 29 September: template_id, approval_quote and approval_date on messages, all nullable, no row touched. Before it: 0045 to 0048 on 24 September, 0049 on 25 September, 0050 (mailer_inbox, append only) on 28 September. THE NEXT MIGRATION NUMBER was then 0053.

  • messages, 6 October: 728 sent, 50 drafted (781 to 830, drafted 6 October), 1 failed. Of the 105 drafted before 6 October, 100 moved to sent on their Nick@microlinkdc.com Sent Items times, outcome "sent from Nick@microlinkdc.com"; the last 8 at drafted or held (331, 338, 354, 356, 411 and the August tests 2, 24, 26) moved on Nick's override, outcome "sent per Nick 6 Oct 2026". Nothing deleted. Sent ledger: 1,112 addresses from 3,853 events.
  • mailer_drafts 33: 9 dated 2026-09-29, 8 dated 2026-09-25, 4 dated 2026-09-24 and 12 older; none deleted. The 9 of 29 September had their To and CC lists REPAIRED IN PLACE on 29 September (two columns, nine rows, nothing deleted). mailer_commits 4, mailer_commit_rows 27, 7 with an outcome. mailer_inbox 11 rows in 1 snapshot, written 06:42 SAST on 29 September.
  • sites 16, site_tasks 14, site_links 0. meetings 574; meeting_records 36, 4 with a transcript, 237,410 characters; future_meetings 22; encounters 0 and still read. page_contracts 8.

What is known to be empty, and why

Table or columnRowsWhy
messages.template_id, approval_quote, approval_date0 of 435Added 29 September by 0051; rows before it are matched to a template on their stored body by the draft guard.
meeting_records.transcript_text32 of 36 at 0 characters4 carry a transcript; Part Three Round B backfills the rest.
mailer_drafts.invite_proposal_json2 of 33Round C sets it where a meeting is wanted or implied.
mailer_drafts.inbound_to_json, inbound_cc_json17 and 16 of 33The inbox cards of 24, 25 and 29 September; older rows predate the columns, and a card with no CC holds none.
meeting_discuss_marks, meeting_discuss_commits, meeting_discuss_commit_rows0Nothing ticked yet.
encounters, encounter_orgs, encounter_people0Superseded on 15 September, read all the same.
site_links0Nothing has needed a join yet.

The strategy MCP

64 tools, version ae096b1d. From 6 October read_funder_lane and read_academic_lane read sent from the sent ledger. Before that: read_funder_lane and put_funder_lane_row added on 29 September (one UPDATE of one row, no insert, no delete). From 29 September put_mailer_reply and put_mailer_drafts take the To and CC lists as an array, an array of {name, email}, a JSON array string or one comma separated string and ALWAYS store a clean JSON array of plain addresses (src/addresses.ts, the same function as the site's src/addresses.js); read_mailer_day computes the attendees through it; and read_mailer_day, put_mailer_reply and put_mailer_outcome state the execute rule: a new draft in the Drafts folder of Nick@microlinkdc.com, not a reply in the thread (from the third run, in nick@microlinkdatacenters.com through the outreach MCP's draft_reply_card). NO TOOL ON THIS SERVER SENDS MAIL, CREATES EVENTS OR DELETES MAIL. Still refused by the write guard: organisations, contacts, person_profiles, people, messages, mailboxes, subjects.

THINGS THE MCP STILL GETS WRONG. set_check_mark and clear_marks record no hand clear, so an MCP untick is re-seeded on the next page load. The read_page_freshness description still claims a page strip. commit_mailer and read_mailer_day still work by the day.

The outreach worker

microlink-outreach-worker, version 64eecd63, commit b3e76b0, 30 tools. From 6 October check_sent, list_unsent and put_sent_items; every draft path refuses an address in the sent ledger by name; the fifteen minute cron reads Sent Items from 1 August 2026, To, Cc and Bcc, in nick@microlinkdatacenters.com and, on the narrow credential, Nick@microlinkdc.com; draft_batch reads the queue after that write back. Part Twenty-Three. draft_reply_card (src/replycard.ts, 29 September) makes one confirmed Mailer SEND card a NEW draft in nick@microlinkdatacenters.com, To the sender, CC everyone else except Nick, subject RE: plus the original, in the xAI format, checked before Graph and on the read back, nothing sent and nothing queued; proved live with the draft RE: Genoa Ohio to nick@microlinkdc.com. THE DRAFT GUARD (src/guard.ts) stands at the door of draft_batch after the template gate: the approval, the open earlier day and the cap of 40, each refused by name with nothing drafted. The day is the site's SAST day (GET /outreach/mailboxes and each row's created_sast_date, from src/sast.js); the worker holds no offset of its own. A row with no template_id is matched to a template on its stored body, placeholders as wildcards, and a row whose body the site did not return counts against the template: the guard fails closed. Proved live on 29 September: a call of 2 rows without approval_quote answered Refused: approval_quote is missing and drafted nothing. THERE IS NO DRY RUN PATH, so the pass case is proved by test only. Every cold draft still goes through one checked path (src/drafting.ts, the 7 Sep xAI format before Graph and on the read back). 14 templates in the TEMPLATES KV namespace: SIGNED OFF outreach-xai-format-2026-09-28, outreach-idea-2026-09-28 and outreach-funder-2026-09-29 (06:00 UTC 29 September, on Nick's words in chat that the funder emails were ready); RETIRED outreach-lanes-2026-09-24, outreach-lanes-2026-09-28, outreach-2026-08 and outreach-manual-v79; seven pass the format and are not signed off.

The tests

6 October. Site: 358 tests, 350 passing: 13 in tests/sentledger06.test.js (put_sent_items twice changes nothing, the events refuse UPDATE and DELETE, the store refuses a sent recipient by name, the send route and the Sent Items write back feed the ledger, list_unsent never returns a sent address, the Funder lane reads sent from the ledger, the microlinkdc match, the override leaves 781 to 830). The eight failures are all in tests/profiles.test.js, an untracked file another session left in the tree. Strategy MCP: 115 tests, all passing. Outreach worker: 39, all passing: 5 in tests/sentledger.test.ts (draft_message and draft_batch refuse a sent recipient by name, a ledger that cannot answer refuses, reconciliation runs before the open day read). Smoke: the /meetings absence check for "Sort" fails on the word "consortium" in a meeting written up on 4 October, a false positive of the case insensitive match, not a reinstated control.

A LESSON WORTH THE SPACE. A drafted row is not a done row. The guard is in the tool, not in a sentence a chat may skip: a rule Nick has asked for is written as a refusal, a test and a DECISIONS line in the same run, or it comes back.

Part Seventeen. Sites And Tasks

Sixteen sites live on 24 September 2026, from fifteen after migration 0044 added oberlin (Oberlin College) and centrio-chicago (CenTrio Chicago) on 23 September. WILLOW ISLAND IS INSIDE PLEASANTS and is not a site of its own.

/sites IS ONE GRID, MOST RECENTLY UPDATED FIRST

THERE ARE NO BASKETS. IMMEDIATE ATTENTION and NEEDS ATTENTION, their headings, their chips and their counts were removed on 23 September 2026 at the owner's request. One grid of blocks, three across on a desktop, ordered by sites.updated_at descending: the site somebody wrote about this morning is first. Their absence is asserted by a test and by scripts/smoke.mjs, so bringing them back fails npm test and the deploy.

sites.basket IS STILL A COLUMN, still written by put_site and still selected by the index query. Nothing renders it. A column nobody draws is not a column to drop.

THE TIEBREAK IS THE NAME. Rows sharing one updated_at stamp would otherwise be left in whatever order SQLite emits and a reload could reorder the page under you. As put_site moves rows every morning, the stamp does the work. A row with no stamp at all sorts last, because an unstamped row is not a freshly written one.

What a block says

It is the SAME CARD COMPONENT the columns of /meetings are built from, at the same size, so a site block and a company card read as one system rather than two.

  • The name, linking to the site's own page, with the host type as the chip and the place line under the name where one is held.
  • THE STAT ROW IS ONE LINE, since 24 September 2026: MW, Tasks, Updated. Label and value side by side on one baseline, in the card's body face at one size, labels in the secondary colour in sentence case, values in the primary colour at medium weight, evenly spaced, with no boxes, rules or borders. Tasks is the open task count; Updated is days since updated_at.
  • MW IS THE NUMBER ONLY. The first number in sites.mw, with "about" kept where it comes straight before it; where a later number is labelled expanded or full build, that one. A figure after a currency sign is money and is skipped. So "1.25 pilot, 40 expanded" shows 40, "400 MW grid ask; $110M at $220k per MW" shows 400, "21 across about 10 buildings, 1 to 3 each" shows 21, and "About 5" shows About 5. With no number the value is a single em space, never a dash or a word. THE STORED TEXT IS UNTOUCHED and /site?key= prints it whole.
  • The stage in the card's body face at medium weight, then the status line in the same face at regular weight, both at the card's line height.

NO PLACEHOLDER TEXT FOR AN EMPTY FIELD, since 24 September 2026. A site with no place line, no stage, no status line or none of the four renders nothing for what is missing: no italic note, no stand-in sentence, no dash. The same holds for the place line on /site?key=. Never an invented value either.

THE PLACE LINE IS READ, NOT INVENTED. sites has no place column and none was added. Where host_type carries a location clause after a comma, which is how the field is written — "District cooling, downtown Chicago"; "Campus geothermal loop, Hales Gymnasium and Crane Pool" — the part BEFORE the comma is the chip and the rest is the place line. With no comma the chip takes the whole field and NO PLACE LINE IS DRAWN. Nothing is ever inferred from a site's name.

/site?key= IS THE ORGANISATION PROFILE, ADAPTED TO A SITE

Rebuilt on 23 September 2026 out of the components /org?key= is built from, IMPORTED from src/render/profilepage.js rather than copied, so the two pages cannot drift into two designs.

  • Header: the initials tile, the site name, the host type chip, the MW chip, the tasks open chip, and the place line where one is held.
  • Four slot cards at the organisation page's size: HOST TYPE, MEGAWATTS, STAGE, STATUS. AN EMPTY FIELD SHOWS Open slot, exactly as the organisation page does.
  • Right rail KEY FACTS: host type, MW, stage, tasks open, organisations joined, and the record age off updated_at.
  • Right rail SILENCE: the last meeting touching this site and how long ago, read from BOTH site_links of kind encounter AND any meeting joined to one of the site's organisations. Reading only the first would show "no meeting joined" on a site we met about last week through the company, which is the false silence this rail exists to prevent.
  • Main column, in this order: WHAT IT IS, the status line; WHAT IS OWED, the open tasks GROUPED BY OWNER with the done and blocked counts; MEETINGS HERE, newest first, each linking to its meeting; ORGANISATIONS HERE; PEOPLE HERE; DOCUMENTS HELD.

NO STAGE RAIL. The organisation page draws one because an organisation moves through fixed steps; a site's stage is free text somebody writes, so a rail would be asserting steps nobody defined.

REMOVED ON 23 SEPTEMBER, and asserted absent by a test and by the smoke check: COMPLETENESS with its percentage; the SECOND STAGE BLOCK, which repeated what the slot card already said; POWER AND PERMITTING, which drew the same site_tasks rows WHAT IS OWED draws, so a power and permitting task appeared twice on one page; and the OPEN SLOTS block, which listed the fields the four slot cards already say are empty.

A bare /site is a 404 and a key naming no site is a 404. A join whose key the register does not hold STILL RENDERS, carrying its own label and no anchor, because a link that would 404 is worse than plain text.

The tools

Until 21 September 2026 the write guard refused sites, site_tasks and site_links BY NAME, so no tool could write any of them. NICK AUTHORISED THE WIDENING THAT MORNING and it was made at the door, in the guard, like the six before it.

ToolWhat it does
read_sitesREADS. Every site with its open tasks, its links, and WHICH OF ITS FOUR FIELDS ARE STILL EMPTY. Call it first.
put_siteUpsert on site_key: name, basket, host type, MW, stage, status line. A field not passed is KEPT. It stamps updated_at, which is what moves a site to the top of /sites.
put_site_taskUpsert. Leave task_id out to create, pass it to correct. A site the table does not hold is refused by name.
put_site_linkUpsert on site, kind and ref. kind is org, contact, encounter or document.

None of the four issues a DELETE of any kind, so the widest thing any of them can reach is the rows whose keys the caller named.

DO NOT INVENT A SITE FIGURE. An empty field is empty because no document, call or transcript in the record gives one, and an invented megawatt figure is worse than a field that says it is empty. A FIGURE IS STORED AS IT WAS GIVEN: mw is TEXT precisely so "About 5" and "21 across about 10 buildings, 1 to 3 each" keep their qualifiers rather than being tidied into numbers nobody said. The card shows the figure; the store and /site?key= keep the sentence. site_links holds 0 rows; an honest gap.

What counts as open

The closed words are done, closed, complete, completed, cancelled and canceled. Every other state, the empty string and a null included, is OPEN, because a task nobody has said anything about is still owed. The index and the site page count by the same list through the same module, so the two cannot print different figures for one store. THERE IS NO TASK STRIP ON /today and there is not to be one: open tasks are shown here and nowhere else. See Part Seven.

Part Eighteen. What Changed On 21, 24, 25 And 28 September 2026

The rules Nick set are FOLDED INTO THE PARTS THEY BELONG TO. This part is the dated list of what changed and where each change now lives, so a link into it still answers and a reader is never reading a rule twice in two places. Newest date first.

28 September 2026

What changedWhere it lives now
AT NICK'S REQUEST: THE COLD OUTREACH BODY IS THE 7 SEP xAI FORMAT, CHECKED ON EVERY DRAFT, AND NO BATCH WITHOUT A SIGNED OFF TEMPLATE. DO NOT REMOVE. Five batches of 40 drafted in a Calibri template that had drifted from the format were deleted that morning. The outreach worker's checkBodyFormat checks the format when put_template stores a template (a failing one is refused and nothing stored), on every rendered body before Graph, and on every draft read back from Graph (a failure records the row failed and leaves the draft in place). New tools sample_template (exactly one real draft), sign_off_template (only after Nick says yes in chat) and the read only check_draft_format. draft_batch refuses a retired template at any size and an unsigned one above one row; a body change clears the sign off. outreach-xai-format-2026-09-28 signed off; four failing templates retired with their reasons; nothing deleted. microlink-outreach-worker src/format.ts, src/drafting.ts, src/templates.ts, src/index.ts.Part Ten, Part Sixteen, Part Nineteen
AT NICK'S REQUEST, ASKED FOR ABOUT TWENTY TIMES: THE MAILER IS INBOX ONLY, NO FALLBACK DAY. DO NOT REINSTATE. /mailer shows one card for each email in Nick@microlinkdc.com's Inbox now and nothing else; an email that has left the inbox is never shown, whatever its day. The page no longer chooses a day and never falls back to an earlier one: on the 28th it had shown 8 cards of 25 September while the inbox was empty. An empty or unreadable inbox shows the breadcrumb, the sends left line and zero cards. The inbox list is written by the new strategy MCP tool put_mailer_inbox into mailer_inbox and mailer_inbox_writes (migration 0050, append only), because the site holds no credential the outreach worker accepts. The intro is one line, N emails in your inbox with a proposed reply., and none with no cards. A card with a commit row is read only; the commit takes the cards shown and refuses by name any not in the inbox. Smoke fails the deploy if the fallback's words return. src/mailerdata.js, src/render/mailer.js, src/strategy.js, src/api.js, scripts/smoke.mjs, tests/mailer-inbox.test.js; strategy MCP src/sitewrite.ts, src/index.ts.Part Twelve, Part Sixteen, Part Nineteen

25 September 2026

What changedWhere it lives now
AT NICK'S REQUEST, ONE DISCUSS TICK ON EVERY /meetings CARD AND ONE Commit, discuss N BUTTON, top right; nothing else added to the page. The tick is written on click and the commit reads back before a scoped clear. Migration 0049 creates meeting_discuss_marks, meeting_discuss_commits and meeting_discuss_commit_rows, since the names meeting_marks and meeting_commits were already the meeting profile's. POST /api/meetings/mark and /commit take the new payloads beside the old. Strategy MCP read_meeting_marks, set_meeting_mark, commit_meetings. Round two reads committed discusses under DISCUSS. src/meetingmarks.js, src/render/meetings.js, public/strategy.css; strategy MCP src/write.ts, src/store.ts, src/index.ts.Part Eleven, Part Three, Part Four
Later the same morning, at Nick's request. THE SITE IS DAYBREAK: sidebar, every breadcrumb root and every tab title (Page · Daybreak), under a sunrise favicon in three new files, favicon-daybreak.svg, favicon-daybreak-32.png and apple-touch-icon-daybreak.png; domain, routes, tables and MCP unchanged. ONE BREADCRUMB PER PAGE: /mailer and /meetings had drawn two. THE MAILER SHOWS TODAY wherever today holds any draft, whatever its inbound columns hold; A COMMITTED DAY renders read only with no commit button, and a second commit of a day is refused. put_mailer_drafts writes every inbound and invite field and refuses an unknown one by name; it had dropped them all, and the 8 drafts of 25 September were repaired from Nick's inbox. src/sitename.js, src/strategy.js, src/index.js, scripts/build.js, src/mailerdata.js, src/render/mailer.js; strategy MCP src/write.ts.Part One, Part Twelve, Part Fifteen

24 September 2026

What changedWhere it lives now
What came up and What people said render FORGET WITHOUT STRIKETHROUGH. A forget tick never strikes through, greys or restyles a title, a body or a quote; the box shows the state. src/render/checkbody.js.Part Seven
Every Today checkbox reads check_row_marks only, and COMMIT TO MEMORY IS ENABLED WHENEVER ANY MARK EXISTS FOR THE ISSUE, in both sections, seeded ticks included. src/render/checkbody.js, src/render/today.js.Part Seven
THE FORGET SEED READS THE TABLE, NOT ITS LEDGER. An unmarked item is ticked again on load unless it was cleared by hand, now recorded as seeded -1, or already committed; a rewrite that drops seeded marks is repaired on the next load. On the morning of the 24th the ledger held 13 rows seeded while the table held 0 row marks. src/forgetseed.js, src/check.js.Part Seven
THE STALE STRIP IS REMOVED FROM EVERY PAGE, with nothing in its place. read_page_freshness and GET /api/page-freshness stay as the chat's check. src/pagecontracts.js, src/strategy.js, public/strategy.css.Part Nine, Part Nineteen
STANDING RULE: never add a banner, strip, notice, chip, badge, panel or any other page element unless Nick explicitly requests it. scripts/smoke.mjs fails the deploy if the stale strip returns.Part Fourteen
/meetings is FOUR COLUMNS: Future across the first two, Past across the last two, one heading per half, one column on a narrow screen with Future first. src/render/meetings.js, public/strategy.css.Part Eleven
The future card reads MET and WHEN, not SILENT: WHEN is the start in SAST with the days until it. src/render/meetings.js.Part Eleven
A past card prints the latest meeting's summary, trimmed to about sixty words, and up to four key points, where meeting_records holds them. src/meetingledger.js, src/meetingsdata.js, src/render/meetings.js.Part Eleven
Round A reads the calendar forward for every future meeting AND Granola backward for every past meeting, and the full transcript of every one; Round B lands every transcript through put_meetings and backfills a missing one the same morning.Part Three
The second run of the day. THE SITES CARD STAT ROW IS ONE LINE: MW, Tasks, Updated, label and value on one baseline in the card's body face, labels secondary, values primary and medium, no boxes or rules. The stage line medium, the status line regular, one line height. src/render/org.js siteBlock, public/strategy.css.Part Seventeen
MW ON THE CARD IS THE NUMBER ONLY, by one rule in mwFigure(): the first number, "about" kept, a later number labelled expanded or full build preferred, a currency figure skipped, an em space where there is none. The stored text stays whole in sites.mw and on /site?key=. src/render/org.js.Part Seventeen
NO PLACEHOLDER TEXT FOR AN EMPTY FIELD: the empty place line, stage and all-empty site render nothing on /sites, and the empty place line renders nothing on /site?key=. src/render/org.js.Part Seventeen
THE MAILER CARD SHOWS THE WHOLE INBOUND EMAIL: sender, subject, received time in SAST, the full body as text in an inset block with earlier messages behind Show earlier messages, then the reply editor; the excerpt where no body is held. Migration 0045 adds mailer_drafts.inbound_body_text, nullable; put_mailer_reply takes it and the morning chat passes it for every inbox email it answers. src/render/mailer.js, src/mailerdata.js, strategy MCP src/sitewrite.ts.Part Twelve
The third run of the day. THE MAILER COMMIT, CONFIRM AND EXECUTE FLOW: read the commit, list every draft and every invite with a calendar check, wait for Nick to confirm, then create reply drafts in the thread and invites with attendees, record each outcome, and clear from the inbox only emails whose reply is in Sent Items.Part Three
Migration 0046: mailer_drafts gains inbound_graph_id, inbound_conversation_id and invite_json; mailer_commit_rows gains invite_json, confirmed_at, draft_graph_id, draft_created_at, event_id, event_created_at, invitees_sent_json, inbound_deleted_at and outcome_note, all nullable. put_mailer_reply takes the two ids. New tool put_mailer_outcome. read_mailer_day returns the execution view. Strategy MCP src/sitewrite.ts, src/store.ts, src/index.ts.Part Twelve
The OUTCOME LINE under a committed Mailer card: Draft created, Invite sent to N, Email cleared, all in SAST. src/render/mailer.js, src/mailerdata.js.Part Twelve
STANDING RULES: the chat never sends an email, drafts only; invites are the one exception and go out only after Nick confirms the listed actions in chat; the chat never deletes an email unless Nick's reply to it is in Sent Items, and then only moves it to Deleted Items.Part Fourteen
The fourth run of the day. EVERY MAILER CARD CARRIES LEAVE, DISCUSS, SEND. SEND means send to drafts, is ticked by default and is exclusive of the other two; the strip reads Would send to drafts N, leave N, discuss N and the button Commit, send N to drafts. A commit drafts only SEND cards and records send in the commit row. Migration 0047 adds mailer_drafts.send_mark, NULL meaning untouched. src/mailerdata.js, src/render/mailer.js; strategy MCP commit_mailer in src/write.ts, by the same rule.Part Twelve
INVITE BESIDE SAVE ON EVERY CARD: the card's own panel with attendees from the sender and every CC, title without Re or Fw, date, start SAST, duration (30), Teams link (yes); saved to mailer_drafts.invite_json through POST /api/mailer/invite, shown as one line with Edit and Remove, and copied onto the commit row. read_mailer_day returns each card's invite and to_invite counts committed ones. Migration 0047 adds inbound_cc_json, which put_mailer_reply fills from its new inbound_cc input.Part Twelve
The fifth run of the day. THE NAV IS TODAY, MARKET, MEETINGS, MAILER, then REFERENCE over SITES, OUTREACH, DIRECTORY, MANUAL, Manual last. Mailer carries an outline envelope from the same inline icon set as the other three; the reference four carry the reference styling and no icon. Routes and the active state unchanged. src/strategy.js sidebar(); asserted by tests/nav24.test.js and by smoke on every route. This supersedes the 21 September nav row below.Part Sixteen, Part Nineteen
The sixth run of the day. THE INVITE PANEL PREFILLS EVERY PERSON ON THE EMAIL: the sender, then every To, then every CC, de-duplicated case insensitively, Nick's own microlinkdc.com address left out and MicroLink colleagues kept. THE CHAT'S PROPOSED INVITE opens the panel prefilled, every field editable, with one line above the footer, Proposed invite: title, date, time SAST, duration, N attendees. THE TITLE RULE where none is proposed: one external attendee and no organisation is FirstName & Nick, otherwise Organisation & MicroLink DC; the title spans the panel. Migration 0048 adds mailer_drafts.inbound_to_json and invite_proposal_json, nullable; put_mailer_reply takes inbound_to and invite_proposal, refusing a malformed proposal by name; read_mailer_day returns both and the computed attendees. Round C passes every person and the proposal. This supersedes the fourth run's title from the subject. src/mailerdata.js, src/render/mailer.js, public/strategy.css; strategy MCP src/sitewrite.ts, src/store.ts, src/index.ts.Part Three, Part Twelve

21 September 2026

What changedWhere it lives now
The morning is THREE rounds in three replies. Round A reads and searches, Round B writes the site and closes by calling read_page_freshness and naming any stale route, Round C runs the outreach, then the week ahead, then the Mailer.Part Three
Every Granola meeting goes in through put_meeting with its FULL transcript. The reply now carries transcript_chars per meeting and WARNS in plain words where one arrives empty; /meetings draws a transcript missing chip. Nothing is refused.Part Three, Part Eleven
"The Customer lane stays blocked" is REMOVED. The Five Lanes Read Me body is used for every lane.Part Ten
The outreach day plan: put_outreach_day_plan writes the lane mix each morning and TODAY'S PLAN renders at the top of /outreach.Part Ten
The Outreach narrative is COMPUTED at request time from the store. The stored paragraph of 25 August was wrong in every figure by 21 September.Part Ten
What came up covers the LAST THREE DAYS only, with no fallback.Part Seven
What people said: at most FIVE items per section and NOTHING OLDER THAN TEN DAYS, newest first.Part Seven
The Today task strip, open tasks from the immediate basket capped at eight, named in Part Seventeen since 2 September and built on the 21st.Part Seven, Part Seventeen
The Sent today figure read a key that has never existed on a mailbox row and printed 0 whatever had been sent. One word.Part Seven
PAST MEETINGS IS ONE JOINED VIEW over encounters, meetings and meeting_records. The page had served 0 cards since 15 September over a ledger of 574 meetings.Part Eleven
FUTURE MEETINGS, the next seven days, with company context and a met before link, written by put_future_meetings.Part Eleven
THE JOIN, NEVER REPLACE RULE: a new source joins the existing view, and a page is never moved off the tables that hold its history.Part Eleven
THE MAILER IS INBOX REPLIES ONLY. One card per inbox email, each answering that exact email. A row with no inbound id is hidden, not deleted. Nothing sends.Part Twelve
Days and Mail Sent are GONE: routes, nav entries and links. Their tables and their data stay, and src/mailsentdata.js stays because it is the contacted record.Part Sixteen
The nav is TODAY, MARKET, MEETINGS, SITES, OUTREACH, MAILER, then REFERENCE over DIRECTORY and MANUAL.Part Sixteen
The strategy MCP write guard admits sites, site_tasks and site_links, AUTHORISED BY NICK ON 21 SEPTEMBER 2026, with put_site, put_site_task, put_site_link and read_sites.Part Seventeen
THE PAGE CONTRACT and the stale strip, read_page_freshness, and the smoke test that walks every route after a deploy and reports the deploy FAILED where one does not answer.Part Nineteen
ONE RECORD for all three repositories, proj-strategy, rewritten from the census rather than carried forward.Part Fifteen
The standing rule: every run ENDS by rewriting Part Sixteen and Part Nineteen from the live census.Part Nineteen
THE RECORD OF RECORD MOVED TO SHAREPOINT, Claude Code Record / proj-strategy. Git .record/ is the mirror, and every session writes both, SharePoint first. The library IS reachable: the morning's earlier reading that it was not was wrong about the route, not about the filesystem.Part Fifteen
The proven write route named in all three CLAUDE.md files: the Microsoft 365 connector, through a print-mode subprocess where a session does not carry those tools itself. The dead G:\My Drive mirror paragraph removed from all three.Part Fifteen
The Outreach narrative no longer claims a reply was answered. read_state has only ever held unread and NOTHING HAS EVER WRITTEN answered, so the clause was always true and the figure was the reply count wearing a second hat. It prints what came back and what it is made of instead: replies from people, automatic replies, bounces.Part Ten
THE MAILER NO LONGER CLAIMS THE COMMIT CREATES THE OUTLOOK DRAFT, because it does not. A commit records the choice; the morning chat creates the drafts afterwards. The page, the undo line and the put_mailer_reply description all say so.Part Twelve
read_manual returns the NEWEST revision any part carries rather than part one's, matching the site. It had answered 2026-09-02 while ten parts read 2026-09-21.Part Fifteen
Round B backfills any meeting showing the transcript missing chip.Part Three

The second block of rows in the 21 September table is the follow up run of the same afternoon. Rows in that table describe that day; where a later date changed a rule, the later table says so and the part it names holds the rule as it now stands.

Nothing was dropped, deleted or truncated to make any of this true. Every table named above still holds every row it held on the morning of 21 September 2026.

Part Nineteen. The Route Map And The Page Contract

Every page on this site reads tables that somebody or something has to write. Until 21 September 2026 nothing said which, or how often, and A PAGE WHOSE WRITER STOPPED LOOKED EXACTLY LIKE A PAGE WHOSE WRITER HAD NOTHING TO SAY: both render an empty block and both answer HTTP 200. /meetings served 0 cards for six days over a ledger of 574 meetings and nothing anywhere said so.

page_contracts is one row per route. It is read TWO ways, one computation between them: read_page_freshness on the strategy MCP, which the chat calls at the close of Round B, and GET /api/page-freshness, which scripts/smoke.mjs prints beside every route after a deploy. NO PAGE DRAWS IT. The stale strip that sat at the top of each page from 21 September was removed at Nick's request on 24 September, with nothing in its place; freshness is a check the chat makes, not an element on a page. See Part Fourteen.

THIS PART IS THE ROUTE MAP. The record points here rather than holding a third copy, and this part is regenerated from the live census at the end of every run. THE CODE IS THE AUTHORITY; this is the map.

The navigation, since 24 September 2026

The sidebar title reads Daybreak over MICROLINK, since 25 September 2026, and every tab reads Page · Daybreak.

TODAY, MARKET, MEETINGS, MAILER, each with an icon, then the REFERENCE heading, then SITES, OUTREACH, DIRECTORY, MANUAL, with no icons, Manual last. The table below follows the same order. Every route sits where it did; only its place in the nav moved.

The contract, route by route

RouteReadsWritten byCadence
/todaycheck_issues, check_rows, check_saying, check_row_marks, and check_forget_seedsthe morning Round B: put_check_issue, put_check_rows, put_check_saying. The forget seed writes marks on every page load and is not a writer of content.24 hours
/marketmarket_items, market_commitsthe morning Round B: put_market_block over the seven news sections24 hours
/meetingsmeetings, meeting_orgs, meeting_records (with summary_md and key_points_json), encounters, AND future_meetings, in one payload: one page, four columns; and meeting_discuss_marks for the DISCUSS box on every card, from 25 Septemberput_meeting and put_meetings for Granola, the calendar sweep for the rest, put_future_meetings for the week ahead; the page's own DISCUSS ticks and commit (POST /api/meetings/mark, POST /api/meetings/commit with discuss true), and set_meeting_mark and commit_meetings24 hours
/mailermailer_inbox, from 28 September: the newest snapshot named by mailer_inbox_writes is Nick@microlinkdc.com's Inbox, and ONLY a mailer_drafts row answering an email in it is drawn, on any issue date; then mailer_drafts (inbound body, ids, To and CC lists, the three verbs, invite_json and invite_proposal_json), mailer_slots, and mailer_commit_rows with mailer_commits for each card's commit row, invite and outcome columnsput_mailer_inbox, the whole Inbox list, every time the chat reads the inbox; put_mailer_reply, which the morning chat calls with the full body, both ids, the To and CC lists of every inbox email it answers and, where a meeting is wanted, an invite proposal (from 29 September the To and CC lists are always stored as clean arrays of plain addresses); the page's own ticks, Invite panel (POST /api/mailer/invite) and commit of the cards shown; put_mailer_outcome, after Nick confirms and the chat acts, each SEND card becoming a new draft in the Drafts folder of nick@microlinkdatacenters.com through draft_reply_card24 hours
/sitessites (the card shows the figure out of mw, the store keeps the text), site_tasksput_site and put_site_task. put_site stamps updated_at, which is the order /sites renders in168 hours
/outreachmessages, replies, outreach_day_plan, and outreach_funder_lane from 29 September and outreach_academic_lane from 1 October, drafted, replied and bounced joined from messages and replies by email; from 6 October SENT FROM outreach_sent, the sent ledger (Part Twenty-Three)the outreach worker, put_outreach_day_plan for the lane mix, and migration 0052 with put_funder_lane_row for the Funder lane. From 28 September every cold draft goes through the worker's checked path: the 7 Sep xAI format before Graph and on the read back, and no batch of more than one without a signed off template (Part Ten). From 29 September draft_batch refuses by name a batch of more than one without Nick's verbatim approval from today, while an earlier day of the same template is unsent, or above 40 a day per template; each message records its template and the approval (0051). From 6 October every draft path refuses an address in outreach_sent by name, and the ledger is fed by the send route, the Sent Items write back, the worker's read of Nick@microlinkdc.com Sent Items (POST /api/outreach/sent-items) and put_sent_items; read through /api/outreach/sent/check and /api/outreach/unsent24 hours
/directoryorganisations, person_profiles, places, corpus_documentsthe register and corpus loaders, by hand720 hours
/manualmanual_sectionsput_manual_section, one part per call720 hours

Not in the contract and served all the same: /company?key=, /org?key=, /person?key=, /meeting?id=, /site?key=, /browse, /place, /service, /candidates, /candidate and /corpusrecord. /days and /mail-sent were removed on 21 September and both answer 404; their tables are untouched.

Live on 29 September at 10:30 SAST, by GET /api/page-freshness: /today, /meetings, /sites, /outreach, /mailer and /manual fresh; /market and /directory stale. The chat names what is stale; the pages do not.

What stale means

The FRESHNESS of a route is the latest write across its source tables, read off whatever stamp each table actually carries: updated_at, then created_at, then written_at, then the date-shaped column some of these tables use as their day. A TABLE CARRYING NO STAMP AT ALL CONTRIBUTES NOTHING rather than counting as fresh, which is what would let a stopped writer hide behind a column that never existed.

A route is STALE where that latest write is older than its cadence, and A ROUTE NOTHING HAS EVER WRITTEN IS STALE RATHER THAN UNKNOWN. A contract naming a table that has gone missing answers stale rather than throwing.

The smoke test, and what it asks

npm run deploy runs scripts/smoke.mjs after the upload. It prints 17 lines: 11 pages, the discuss probe, the three icon files and the two removed routes. It fetches the pages with a cache buster and asks of each: HTTP 200; no uncaught error text in the body; and A NON-EMPTY DATA REGION. It also checks that /days and /mail-sent answer 404, and that /meetings?when=future and /meetings?when=past both still answer 200 with the one page. From 25 September it fetches the three sunrise icon files, /favicon-daybreak.svg, /favicon-daybreak-32.png and /apple-touch-icon-daybreak.png, each of which must answer 200 with its own type.

41 ABSENCE CHECKS

The 17 removals of 23 September, asserted against the LIVE page: on /today, the task strip's heading; on /market, the four work panels; on /meetings, the three grouping buttons, the two standing pills, the sort, the search and the WRITTEN UP tile; on /sites, the two basket headings; on /site?key=, COMPLETENESS and POWER AND PERMITTING. FROM 24 SEPTEMBER, THE STALE STRIP ON EVERY ONE OF THE 11 PAGES, two strings each, AND THE PLACE LINE PLACEHOLDER ON /sites AND /site?key=. A string found prints DEPLOY FAILED, names the string and the date, and says the owner asked for the removal. It prints ABSENCE CHECKS n of 41 on a pass.

6 PRESENCE CHECKS, which an absence check cannot make

  • Document order: Future meetings before Past meetings in the served bytes, which is what puts Future first when the columns stack.
  • The /sites order: the Updated day figures, computed per block, must come out ascending, which a dropped or reversed ORDER BY would break.
  • /sites, from 24 September: every site card carries the one line stat row, MW then Tasks then Updated, and no card carries the boxed grid.
  • /today, from 24 September: no strike through rule for a forget row; and where a section's boxes are drawn ticked from the store, its Commit to memory is not disabled.
  • /meetings, from 24 September: the four column layout and a WHEN cell on the future cards; from 25 September, exactly one DISCUSS box on every card and the one Commit, discuss button.
  • /mailer, from 24 September: every card carries LEAVE, DISCUSS, SEND in that order, and every card without a commit row an Invite button; it reports how many carry SEND ticked. From 28 September, it fails the deploy if either phrase of the removed day fallback is on the page: THE MAILER IS INBOX ONLY, NO FALLBACK DAY.

THE NAV CHECK, ON EVERY ROUTE, from 24 September

On each of the 11 routes it fetches, smoke reads the sidebar and fails the deploy unless it reads Today, Market, Meetings, Mailer, Sites, Outreach, Directory, Manual in that order, with icons on the top four and on no other, and REFERENCE drawn exactly once; and, from 25 September, the tab title Page · Daybreak, the sidebar title Daybreak, the sunrise favicon link, and EXACTLY ONE BREADCRUMB with the root Daybreak. It prints NAV CHECKS n routes on a pass. tests/nav24.test.js asserts the same at build time, with the routes and the active state.

The Mailer's outcome line, the invite write, the attendee prefill, the proposal and its line, the title rule and put_mailer_outcome are covered by tests/maileroutcome24.test.js, tests/mailersend24.test.js and tests/mailerinvite24.test.js on the site and tests/mailerreply.test.js on the strategy MCP; the inbox rule by tests/mailer-inbox.test.js on the site and put_mailer_inbox by tests/mailerinbox28.test.js on the strategy MCP; from 29 September the clean address lists, the attendees, the title rule and the card checks by tests/mailer29.test.js on both; the Funder lane by tests/funderlane29.test.js on both; not by smoke, because smoke does not write. The /meetings DISCUSS tick and commit are covered by tests/meetingsdiscuss25.test.js and tests/meetingdiscuss.test.js; smoke probes the two routes with a refused mark and a dry commit, both of which write nothing.

WHY THESE ARE HERE AND NOT ONLY IN A TEST. The things they guard had each been removed before and rebuilt, because the decision lived in a commit message. Tests catch a reinstatement at build time; these catch it on the page the owner actually looks at, including a deploy that shipped an older build.

A LESSON: public/strategy.css, every inline script and every manual part are served as page text, COMMENTS AND ALL, so text naming a removed thing will fail these checks, correctly. Name removed things in the renderers, in scripts/smoke.mjs and in the tests.

THE STANDING RULE. Every Claude Code run on this site ENDS by rewriting Part Sixteen and Part Nineteen from the live census: the routes, the tables, the tools, and what is known to be empty and why. The manual cannot then drift from the build, because the last thing a run does is make it agree.

Part Twenty. The IDEA Lane

FROM 28 SEPTEMBER 2026 THE IDEA LANE IS THE MAIN OUTREACH. It replaces the source named in Part Three, Round C, step one: the morning outreach is drawn from this lane. The workbook lanes run only on a day Nick names a mix for them. For this lane only, these rules replace Part Ten checks 1 and 7; checks 2 to 6 and the xAI format in Part Ten stand unchanged.

THIS LANE DRAFTS ONLY IN ROUND C OF OPEN THE SHOP, ONE DAY PER MORNING, AND ONLY AFTER NICK HAS SEEN THE LIST AND ONE EMAIL IN CHAT AND ASKED FOR DRAFTS. No other chat, and no other moment, drafts from it. Day 1 was drafted on 29 September 2026 after Nick's approval in chat.

1. The list, and where it lives

IDEA_Lane_Ranked_28Sep.csv in OneDrive, 00 Admin / Today / 00 Outreach (drive b!QR3CfYWATUWFqyC_zlN8NhIA2cXTCuRFl8nhHYnXgqDeI8lEV1WzS5axp_vAE_4f, item 013VFQUNN26BOVQUUPH5D3EBAUTUF5SJSF, 44,225 bytes). Read it with read_resource; it is plain text and pages cleanly. Columns: Rank, Day, Section, Company, Name, Email. Section is C Campus, O Operators, S Engineering and Services, M Manufacturers. 632 people at 100 organisations. THE FILE IS NEVER EDITED BY A CHAT. Progress lives in the outreach worker, not in the file.

Built from IDEA_Member_Directory.xlsx (IDEA connected community, captured 28 September 2026, 3,229 people) by IDEA's own member type: Campus is Physical Plant/Campus Energy System; Operators is Utility North America plus ESCO/CHP; Engineering and Services is Service Providers to the Industry; Manufacturers is Manufacturer/Supplier.

2. How it was ranked

  • Geography. NY, PA, OH highest; IL, MI, MN, WI, MA, NJ, CT, RI, IN next; TX, WA, OR, CO, MD, VA, NC after; other US states below; outside the United States lowest.
  • Seat. Campus: energy, utilities, plant or facilities director or manager highest. Other sections: CEO, President, Chief, Owner, Founder, Principal highest; VP, Director, General Manager, Business Development next.
  • Depth. More people at the organisation in IDEA ranks higher.
  • Top 25 clean organisations per section; an organisation's rank is its best person's.

3. Held out

  • Live: CenTrio, Vicinity Energy, Ever-Green Energy, District Energy St. Paul, Oberlin College, NYSERDA, MicroLink, David Woodson, and every Part Ten check 5 name.
  • Already in the outreach queue when the list was built, not in the file: Consolidated Edison, Cordia, Corix, Detroit Thermal, ENGIE North America, Enwave (all three), Lansing Board of Water and Light, Markham District Energy, Rochester District Heating Cooperative, Constellation, San Antonio Water System, McKinstry, NORESCO, Ramboll, Wendel, Reshape Infrastructure Strategies, Google, Johnson Controls, Darcy Solutions, Dartmouth College, Hennepin County, Indiana University, University of New Hampshire. Bounced domain: Stanford University.
  • In the file but found in Sent Items on 28 September 2026 (whole organisation out, every day): RED-Rochester, Salas O'Brien, University of Rochester, Columbia University, Cornell University, National Grid, Emerson, Brooklyn Navy Yard Cogeneration (Axium), Citizens Thermal, University at Albany. ElectroCell Systems rows 41 and 42 fail check 4 (addresses on acscompanies.com and iesgcorp.com). Check 6 still runs every morning on every domain.
  • Found in Sent Items on 29 September 2026: Penn State Health (psu.edu, Penn State Lehigh Valley mail, June 2026) and Patterson-Kelley (spx.com, SPX Cooling mail, June 2026). Check 4 on 29 September: BC Energy's Matthew Bette (powerbycogen.com), U.S. Chiller Services' Dan Mizesko (dalkiasolutions.com), Tricon's Randy Lane (energytaskforce.com), ElectroCell's Jack McTigue (iesgcorp.com), Advanced Thermal's purchasing@ (role box).

4. Two waves, and the day number

  • Wave one, Days 1 to 11: the five highest ranked people at each organisation, 419 people.
  • Wave two, Days 12 to 19: the remaining 213, ONLY at organisations where nothing came back in wave one (rule 6).
  • The Day column is the plan: ten per section, at most two per organisation, the cap lifted only where a day would otherwise fall short of 40. A day number is an order, not a date: a morning with no outreach just means the next morning takes the lowest Day still open.

5. Round C, every morning. SHOW FIRST, DRAFT ONLY ON NICK'S WORD (29 September 2026)

Round C never drafts on its own. Steps 1 to 4 run every morning and end with the list and one email shown in chat. Steps 5 to 8 run ONLY after Nick asks for drafts in chat in words that ask for them ("send to drafts", "draft them"); "n" to move on is not approval. On 29 September 2026 Round C drafted 40 unasked, counted an unsent Day 1 as done and moved to Day 2; all 80 drafts were withdrawn and Day 1 was redrafted on Nick's word.

  1. Read this part and the file.
  2. list_queue with no filter. A person is done only when their message has status sent. Drafted, approved or held is NOT done. If any IDEA draft from an earlier day is still unsent, that day is still open: say so in chat, name how many are waiting, and draft nothing new. Otherwise the lowest Day with anyone not sent is today's day.
  3. Take today's day's people who are not done and not held (rules 3 and 6), then top up from the next Day, in rank order, to 40, ten per section.
  4. Run Part Ten checks 2 to 6 on those 40, Sent Items per domain included, never skipped. Check 4 removes an address on another organisation's domain and any role box (for example purchasing@). Replace each person removed by the next clean person in the same section, at most two per organisation. Then SHOW Nick the 40 in chat (name, organisation, type) with the holds named, and ONE email rendered for the first person, and STOP.
  5. Only after Nick asks for drafts in chat: create_contacts for the 40 (a person already on file comes back existing, with their id).
  6. draft_batch WITH approval_quote, Nick's instruction copied verbatim from today's chat and containing draft, and approval_date, today in SAST; since 29 September 2026 the tool refuses by name above one row without them, refuses while an earlier IDEA day is unsent, and caps at 40 a day per template. Template outreach-idea-2026-09-28, values first_name (the first word of Name) and company (the plain trading name: Merck not MERCK, Rock Energy Storage, GEA, Indeck, Syska Hennessy, Preferred Utilities, Bard, Rao + Athanas; no leading "the"), mailbox nick@microlinkdatacenters.com, approve false. NOTHING IS SENT; Nick presses send.
  7. put_outreach_day_plan: four rows, lanes IDEA Campus, IDEA Operators, IDEA Engineering and Services, IDEA Manufacturers, source file IDEA_Lane_Ranked_28Sep.csv, planned beside drafted, the note naming every organisation held that day and why.
  8. Report: IDEA Day N, wave one or two, planned against drafted per section, one line per hold reason naming the organisations, people left in the wave.

6. Stop on contact

Any of these at an organisation holds EVERY undrafted person there, both waves: a reply from a person, an automatic reply naming a successor or colleague, a meeting request, a live thread found in Sent Items or the inbox. A bounce holds every person on that domain. Found each morning from read_replies, the inbox and Sent Items; named in the day plan note.

7. The mail

Template outreach-idea-2026-09-28, subject MicroLink & {{company}}, 7 September xAI format, agreed with Nick in chat and signed off 28 September 2026 (sample message 411):

Hi {{first_name}}, / I hope you're well. I saw your details on the International District Energy Association site, and we have actually been following the work you are doing at {{company}}. / We are an energy reuse data center company, operating for a few key customers, and the first data center company built on community first values. We would love to talk through the possibilities of working together. / Would be great to find a gap to chat. / Warmly, Nick Searra, CEO, www.microlinkdc.com

A body change clears the sign off.

Part Twenty-One. The Funder Lane

FROM 29 SEPTEMBER 2026 THE FUNDER LANE RUNS BESIDE THE IDEA LANE, AND THE SITE HOLDS IT. The source of truth is the site table outreach_funder_lane (migration 0052), loaded on 29 September 2026 from Funder_Lane_Ranked.xlsx. The workbook is a copy for people; progress is never written back to it. Like the IDEA lane, it drafts only in Round C, one day per morning, and only after Nick has seen the list and one email in chat and asked for drafts.

1. The list, and where it lives

outreach_funder_lane: rank, day, first and last name, title, organisation, email, domain, city, country, LinkedIn, sent_items_checked, held and held_reason, contact_id, source and when it was added. 223 ranked people across Days 1 to 15 (15 a day, 13 on Day 15), held 0; 30 held, each with the reason as the workbook gave it (25 address domain belongs to another organisation, 5 in Sent Items from the July investment campaign). Day 1 carries sent_items_checked Clear 29 Sep and contact ids 44154 to 44167 and 5869. DAY 0 is the 13 funder emails of 29 September 2026, queue 452 to 464, sent from Outlook at 08:05 to 08:07 SAST. The workbook sits in OneDrive, 00 Admin / Today / 00 Outreach, beside IDEA_Lane_Ranked_28Sep.csv.

2. Where it shows, and how its state is known

/outreach draws the Funder lane beside Today's plan, in the same box: per day the names with organisation, the type Funder and the state; then the Held list with each reason. THE STATE IS NEVER STORED. It is joined from the outreach queue by email on every request, the newest message to that address deciding it: replied, bounced, sent, drafted (drafted, approved or held in the queue), else not drafted. read_funder_lane on the strategy MCP returns the same rows and states, one day at a time, or the Held list.

3. Round C, every morning. SHOW FIRST, DRAFT ONLY ON NICK'S WORD

  1. read_funder_lane with no day: the lowest day with anyone not sent is today's day. If a funder draft from an earlier day is still unsent, that day is still open: say so, name how many are waiting, and draft nothing new (draft_batch refuses it anyway).
  2. read_funder_lane for that day: its 15. Run Part Ten checks 2 to 6 on them, Sent Items per domain included. A person removed is held with put_funder_lane_row (held true and the reason); a clean Sent Items check is written there as sent_items_checked, for example Clear 30 Sep. Both are one UPDATE of one row; nothing is inserted or deleted.
  3. SHOW Nick the day's names in chat (name, organisation, type Funder) with the holds named, and ONE email rendered for the first person, and STOP.
  4. Only after Nick asks for drafts in chat: draft_batch, template outreach-funder-2026-09-29 (signed off 29 September 2026, sample message 452), mailbox nick@microlinkdatacenters.com, approve false, WITH approval_quote (his words, verbatim, containing draft) and approval_date (today, SAST). At most 40 a day per template. NOTHING IS SENT; Nick presses send.
  5. put_outreach_day_plan: one row, lane Funder, source file Funder_Lane_Ranked.xlsx, planned beside drafted, the note naming the day, the template, the mailbox and every hold.

4. Stop on contact

As Part Twenty, rule 6: a reply, an automatic reply naming a colleague, a meeting request, or a live thread in Sent Items or the inbox holds every undrafted person at that organisation; a bounce holds every person on that domain. Each hold is written with put_funder_lane_row.

Part Twenty-Two. The Engineering, Seed Vault And GDR Lanes

FROM 30 SEPTEMBER 2026 THREE MORE LANES RUN BESIDE THE IDEA AND FUNDER LANES: ENGINEERING, SEED VAULT AND GDR. When Nick calls up daily outreach, these three are offered as outreach options with IDEA and Funder, one row each, with how many are still undrafted. They draft only in Round C and only after Nick has named the mix, seen one email per lane in chat and asked for drafts.

1. The list, and where it lives

Engineering_SeedVault_GDR_Lanes.csv in OneDrive, 00 Admin / Today / 00 Outreach (item 013VFQUNPWOGO3Q6N5HNCYGNWXH4NYATRV), beside Funder_Lane_Ranked.xlsx. Columns: Lane, Order, First Name, Last Name, Title, Organization, Email, Held, Held Reason. Only people with an email are in it: Engineering 94, Seed Vault 64, GDR 101. Order is the pick order; GDR is in the research rank order of GDR_Outreach.xlsx. Engineering is United States only; Seed Vault is global; the two lists are separate. A row with Held Yes is never drafted and its reason says why. 26 rows are held as loaded: Jacobs (live MOU thread with Dave and Maddy), AECOM (live thread with Joao and Vincent), Clayco (June meeting with Jessica Francisco), Pulse Fund (Tenzin Seldon, GDR Task Force lead), HEET (Sent Items), and every organization already in the outreach queue (McKinstry, Southland, Ramboll, Syska Hennessy, Microsoft, CoreWeave, Codema, Enwave). The file is a list, not a record: THE STATE IS NEVER STORED IN IT. It is joined from the outreach queue by email, as for the Funder lane: replied, bounced, sent, drafted, else not drafted.

2. The emails

Three templates, all in the 7 September xAI format (Part Ten), subject MicroLink & {{company}}, placeholders first_name and company, signed off by Nick on 30 September 2026. Nothing else is sent to these lanes.

LaneTemplateSampleThird paragraph opens
Engineeringoutreach-engineering-2026-09-30465, Robert Sty, HDRWe would love to be working with {{company}} on a few upcoming data center projects.
Seed Vaultoutreach-seedvault-2026-09-30466, Morten Rasmussen, NordGenWe would love to find a way to be contributing to the Seed Vault.
GDRoutreach-gdr-2026-09-30467, Maggie Lewis, Indianapolis City County CouncilWe would love to find a way to be contributing to GDR.

Every body reads: Hi {{first_name}}, / Hope this mail finds you well. / [the lane sentence] MicroLink is a U.S. heat recovery data center company operating for a few key customers. / It would be great to find a gap to chat. Just let me know a time that could work for you. / Warmly, Nick Searra, CEO, www.microlinkdc.com. DO NOT REWORD IT. Nick rejected every rewrite on 30 September; a body change clears the sign off.

3. Day 0

30 September 2026, 40 drafts in nick@microlinkdatacenters.com: Engineering 15 (queue 465, 468 to 481), Seed Vault 10 (466, 482 to 490), GDR 15 (467, 491 to 504). Approval quote "Okay put these all in draft".

4. Round C, every morning. SHOW FIRST, DRAFT ONLY ON NICK'S WORD

Read the file. For each lane, the next rows by Order that are not held and not in the queue by email. If a draft from an earlier day of that lane's template is still unsent, that lane is still open: say so, name how many are waiting, and draft nothing new in it (draft_batch refuses it anyway).

Offer the three lanes beside IDEA and Funder. When Nick names the mix, run Part Ten checks 2 to 6 on the picked rows, the Outlook Sent Items search per organization domain included, one person per organization per round, and replace every removed row with the next clean row in the same lane. SHOW Nick the names (name, organization, lane) with the removals named, and ONE rendered email per lane, and STOP.

Only after Nick asks for drafts in chat: create_contacts, then draft_batch per lane from its signed off template, mailbox nick@microlinkdatacenters.com, approve false, approval_quote his words verbatim containing draft, approval_date today SAST. At most 40 a day per template. NOTHING IS SENT; Nick presses send. Then put_outreach_day_plan, one row per lane, source file Engineering_SeedVault_GDR_Lanes.csv, planned beside drafted, the note naming the template and every removal.

5. Holds and stop on contact

As Part Twenty, rule 6: a reply, an automatic reply naming a colleague, a meeting request, or a live thread in Sent Items or the inbox holds every undrafted person at that organization; a bounce holds every person on that domain. A new hold is written into the file (Held Yes and the reason) and the file is uploaded again to the same folder with replace, and the chat says which rows it held.

Part Twenty-Three. The Sent And Unsent Ledger

Added 6 October 2026. Outreach is drafted in nick@microlinkdatacenters.com and Nick sends much of it from Nick@microlinkdc.com, switching the From in Outlook. Until today the Sent Items write back read only the microlinkdatacenters mailbox, so mail that left from microlinkdc was invisible: on 6 October 105 queue rows still read drafted though every one had been sent, draft_batch refused new days because of them, and a chat re-enriched and nearly re-emailed people mailed on 1 October (the Google DeepMind and Google academic round, and IDEA Day 2: Miami University, Precis, Bard Rao + Athanas).

NICK'S RULE, 6 OCTOBER 2026. TWO DATABASES, UNSENT AND SENT. THE MOMENT A PERSON IS EMAILED THEY ARE IN SENT AND OUT OF UNSENT COMPLETELY. EVERY MORNING THE SENT ITEMS ARE CHECKED BEFORE ANY LIST IS BUILT. A person in the sent ledger is never drafted again: draft_message, sample_template and draft_batch refuse them by name, with the first sent date and the mailbox, and create nothing. There is no override flag.

The tables, migration 0054 (outreach_sent)

NameWhat it holds
outreach_sent_eventsOne row per sent message per recipient (To, Cc and Bcc). APPEND ONLY: two triggers refuse every UPDATE and DELETE. Keyed on the message's internetMessageId (or queue:<id>), the address and the source, so the same message fed twice adds nothing.
outreach_sentTHE SENT LEDGER. One row per address ever emailed, lower cased: first_sent_at, last_sent_at, send_count, last_subject, last_mailbox, last_internet_message_id, contact_id where known, and source. Rebuilt from the events, never edited by hand.
outreach_sent_runsOne row per feed run, with what it read and wrote, whether the read was complete, and any error. Append only.
outreach_unsent (a view)THE UNSENT LIST: every contact with an address whose lower cased address is not in outreach_sent.

The operator's own domains, microlinkdc.com and microlinkdatacenters.com, are never written to the ledger: they are colleagues and test addresses.

The four sources

  • queue: a queue row moving to sent writes its event and its ledger row in the same batch as the UPDATE, on the send route, on the Sent Items write back and on the microlinkdc match.
  • sent_items_microlinkdatacenters: every outbound message the outreach worker reads in nick@microlinkdatacenters.com Sent Items, matched to a queue row or not, from 1 August 2026, on the fifteen minute cron.
  • sent_items_microlinkdc: Nick@microlinkdc.com Sent Items, READ BY THE WORKER ON THE SAME CRON through its narrow Daybreak Mail Drafts credential (a read, nothing else). Proved live on 6 October: 1,476 messages read, complete, no error. A queue row whose recipient and subject match a microlinkdc send made after the row was queued moves to sent with that send's time, outcome "sent from Nick@microlinkdc.com". The 6 October backfill moved 100 of the 105 that way.
  • nick_override: Nick's word, below.

The tools

On the outreach MCP: check_sent(emails) answers sent yes or no for each address, with the first and last sent dates, the mailbox and the number of sends, and returns the latest run of every feed. list_unsent(lane, limit) gives the next unsent people of a lane in that lane's own order, never a sent address and never a held row: funder, academic, and the contact tracks host, partner, customer and funder_track. put_sent_items(messages) takes up to 500 Sent Items messages, {internet_message_id, sent_at, subject, to, cc, bcc}, read through the Microsoft 365 connector. It is append only and idempotent on internet_message_id. reconcile_sent now feeds the ledger too. The site answers them at POST /api/outreach/sent-items, GET|POST /api/outreach/sent/check and GET /api/outreach/unsent. The Funder and Academic lanes, on /outreach and through read_funder_lane and read_academic_lane, read sent from the ledger, not from the queue's state.

THE FILE LANES ARE FILES. IDEA, Engineering, Seed Vault, GDR, Fluidstack and the workbooks live in CSV and xlsx files, not in the store, so list_unsent cannot order them. Their names are cleared with check_sent, every one, before the list is shown.

The morning rule, in Round C, before any list is shown

  1. Call check_sent with the day's candidate addresses and read its feeds. If the sent_items_microlinkdc run is less than 30 minutes old with no error, the cron ran: go on.
  2. If it is older, or carries an error, read Nick@microlinkdc.com Sent Items since the last run through the Microsoft 365 connector and call put_sent_items with them, up to 500 a call, then call check_sent again.
  3. Every address check_sent answers yes is DROPPED from the list, whatever lane or file it came from. Only the rest is shown to Nick, and only the rest can be drafted.

The 6 October override

Nick's words, 6 October 2026: "All of those emails are sent completely." After the backfill had moved 100 of the 105 on their real Sent Items times, the 8 rows still at drafted, approved or held with queued_at before 2026-10-06T00:00:00+02:00 were moved to sent: 331, 338, 354 and 356 (25 September), 411 (a test to Nick), and the August test rows 2, 24 and 26. No Sent Items match was found for any of them, so sent_at is queued_at. Each carries the outcome "sent per Nick 6 Oct 2026", and the four with a real recipient went into the ledger as nick_override (the test rows are on the operator's own domain). Rows 781 to 830, drafted on 6 October, were not read and stay at drafted. AN UPDATE OF STATE ONLY: nothing was deleted. The script is scripts/sent-override.mjs in the site repository.

The count on 6 October, after the backfill

Ledger: 1,112 addresses. Events: 3,853 (queue 706, sent_items_microlinkdatacenters 621, sent_items_microlinkdc 2,522, nick_override 4). Queue: 728 sent, 50 drafted (781 to 830), 1 failed. outreach_unsent: 28,821 contacts.

KNOWN LIMIT. When Nick switches the From, Outlook sends a new message with a new internetMessageId, so the queue event and the microlinkdc event of one send are two events, and send_count reads 2 for such an address. first_sent_at, last_mailbox and the sent yes or no are right.

23 parts, revision 2026-10-06, read from manual_sections at request time