Made/MADE-ID
Internal
Plan → production

What’s left, who has to do it, and the things only Made can clear

The five stages describe the system as it is. This page is the plan to get it to production: the questions still open, the seven blockers that needed a Made-side action — one now cleared — each with step-by-step instructions, the optimizations we can offer, and the build order. The honest headline: the 33-task critical path contains zero Made-blocked tasks — we can build the identity spine, the review loop, the custodian and the backlog drain without waiting on anyone.

33
critical-path tasks · 0 blocked on Made
6
blockers still needing a Made action
39
open questions · 8 for Made
60%
SFDC accounts still unmatched

Where we actually are

live-measured 2026-09-09 22:00 UTC

Three findings from measuring the running databases directly — rather than reading our own older documents — reordered this entire plan. The short version: the part we cannot touch is working, and the part that is broken is the part we can fix ourselves.

Working — and current

Ingestion is healthy. Mirrors were fetched this evening (21:15–21:39 UTC) against source records modified the same day. Salesforce and HubSpot are flowing.

The loop is live, not dormant. Account proposals were written at 08:30, a human recorded a decision at 09:04, the audit log is active, and the custodian ran again at 22:00.

Consequence  This is a consolidation of a running system, not a build from zero. Ingestion hardening moved later in the plan, not earlier.
!

Broken — and ours to fix

1,941 of 3,209 Salesforce accounts have no MADE-ID match (60%). 7,244 of 17,843 HubSpot contacts have no derived company (41%).

public.contacts holds 0 rows. Two rival contact-proposal tables hold ~24k rows between them. Both learning stores are empty. Two match queues have had nothing enqueued since May/July while their 10-minute drains still run.

Consequence  Identity consolidation became the critical path — and it needs nothing from Made.

Just repaired

Three silent-failure defects, all clustered on the empty contacts table. The largest: lead-list generation reads person fields from that empty table, so any list it produced would be empty while reporting success. Across 65 campaigns / 5,207 members the old path resolves 0 leads; the repaired path resolves 4,869 across 62 campaigns.

These were latent, not active. That function is deployed on neither database and no schedule calls it; the HubSpot sync function last actually ran 2026-08-06. So all three were caught before they could cost anything — the honest framing, and a correction to our own first write-up of it.

Status  Fixed and type-checked. The HubSpot sync fix is deployed. The lead-list fix is held pending a smoke test, because deploying it would add an outbound capability rather than repair running code.

The numbers behind that

MeasureValueWhat it tells us
companies (canonical)4,719100% carry a made_id — zero without. The company anchor is sound.
sfdc_accounts · unmatched3,209 · 1,94160% open The real account backlog. This is the number to move.
hubspot_contacts · no company17,843 · 7,24441% open The real contact backlog.
public.contacts0empty The intended rich person record was never populated. Canonical person IDs live in made_id_contacts (109,324).
er_identity_map9,336Account crosswalk links resolved to canonical.
er_contact_identity_map112,617Person identity links — the deepest store in the system.
er_decisions1,919live Most recent 2026-09-09 09:04. Humans are reviewing today.
made_id_contact_match_proposals12,489authoritative Current to 2026-09-06.
er_contact_identity_proposals11,446superseded Stopped 2026-08-26. A half-finished migration — two producers still coexist.
made_id.match_queue3,731 pendingabandoned Nothing enqueued since 2026-05-15; drain still runs every 10 min.
made_id.contact_match_queue2,963 pendingabandoned Nothing enqueued since 2026-07-29; drain still runs every 10 min.
name_aliases · er_learned_rules0 · 0unbuilt The “Learn” skill has no store in use. Learning is designed, not running.
hubspot_v2_* (all six tables)0dead The MSS rebuild is empty — yet mss-watchdog still runs every 30 minutes guarding it.
er_custodian_runs69Last 2026-09-09 22:00. Runs nightly; an apply run occurred 2026-08-26.

The single most important correction

An earlier version of this plan proposed a “budgeted blitz” to drain the two match queues. That would have burned reviewer capacity on 6,694 rows that are four months stale while the genuine backlog — 1,941 accounts and 7,244 contacts — sat untouched. Abandoned inventory gets retired; the live backlog gets worked. Those are different operations.

Why ingestion moved later, not earlier

Reporting’s scheduler contains no Salesforce or HubSpot contact job at all — core object extraction runs in Joe’s n8n Cloud, which we cannot edit. It is also the piece that is working correctly. Hardening it is worth doing, but doing it first would have parked the critical path behind the one dependency we do not control.

Open questions

8 for Made · 31 already decided on our side

Thirty-nine questions came out of this work. We answered the thirty-one that are ours to answer, so Made is left with the eight that genuinely encode your risk appetite, business meaning, and people’s time. Every one carries a default we will proceed on if the answer is slow — nothing here stops the build.

For Made — the eight

#QuestionWhy it’s yours, not oursWhat we do while we wait
M-Q1What is an acceptable wrong link? If we auto-link two companies and get it wrong, does that corrupt a report someone acts on, or is it a shrug?Only Made knows the downstream commercial cost. This one number drives every automation threshold.Assume expensive. Human review for anything short of a unique authoritative ID match.
M-Q2Should ~1,941 unmatched accounts become ~1,941 new canonical companies? Or are many duplicates, dead records, or out of scope?It changes what your account universe means.Mint only on unique-ID evidence; everything else queued for review.
M-Q3When Salesforce and HubSpot disagree on a company’s name, domain, industry or address — which wins?A data-ownership policy, not a technical one.Salesforce for account facts, HubSpot for marketing engagement; human overrides beat both.
M-Q4Who reviews, and how much time per week?Your staffing. This sets the entire backlog drain rate — it is the hard ceiling on throughput.Assume very limited; optimize for a small high-value queue over volume.
M-Q5What CRM data may leave the CRM, be retained raw, or enter a model’s context? Especially notes, activity bodies, personal contact details.Privacy and compliance exposure is yours.Named fields only. No free text into model context. Raw retention minimized.
M-Q6How fresh does this need to be? Daily is current behavior — is that fine, or is near-real-time needed?Drives whether change-data-capture is worth the complexity and extra permissions.Daily incremental polling. No CDC.
M-Q7Do you want automated linking at all — or is a well-run human queue with good evidence the actual goal?A philosophy call that changes the target system.Build the queue and the evidence first; automation stays shadow-only until M-Q1 is answered.
M-Q8Who owns this after handover? Named on-call owner, escalation path, and who may approve a repair.Operational ownership cannot sit with a vendor indefinitely.We document runbooks and operate it transitionally.

If you only answer three

M-Q1 (acceptable wrong-link rate), M-Q4 (reviewer capacity) and M-Q5 (data and privacy scope). Those three change what gets built. The other five change how it is tuned.

Already decided on our side — the thirty-one

Listed for transparency, not as an ask. Each was resolved toward correctness over coverage, reversibility over speed, and no autonomous mutation. Any of them can be overridden — but none is waiting on anyone.

#QuestionOur decision
A-Q1Audience for this documentation siteMade leadership with per-person accounts and server-side auth; internal-only until that exists.
A-Q2Acceptable false-link rateTarget ≤1 in 1,000 for automated classes, measured on a labeled holdout; effectively zero for merges. Made may override — M-Q1.
A-Q3Which fixtures define “correct”Shared domains, role mailboxes, parent/subsidiaries, employer changes, cross-CRM duplicates, campaign ID mismatch, source deletion, duplicate approvals, denied fields.
B-Q1The canonical rich person recordPublish a v_person_canonical view over made_id_contacts + census + mirrors; repoint consumers. Physical table only if performance demands it.
B-Q2Which identifier the UI and exports showMADE-ID everywhere a human reads or exports; internal UUIDs stay internal.
B-Q3What happens when a CRM record is deletedSoft-tombstone: keep the ID, mark the source binding inactive, retain history. Never delete canonical identity.
B-Q4The legacy account crosswalkBypass it; keep it read-only behind a compatibility view until every consumer is traced.
B-Q5How reversible a merge must beSoft merge only — redirect, preserve both IDs and before-images, fully reversible. Physical consolidation is later, separate work.
B-Q6Where the Reporting→Transfer outbox livesChange journal in Reporting; Transfer pulls and owns the checkpoint.
B-Q7Quarantine visibilityQuarantine plus a visible count and a stale-quarantine alert. Invisible quarantine is how the abandoned queues happened.
C-Q1Which review path becomes the single oneKeep the currently-live path (1,919 real decisions) and port the better scoring ideas into it.
C-Q2Where reviewer identity comes fromSupabase Auth user + role claim, derived server-side. A client-supplied reviewer name is not attribution.
C-Q3Is bulk approval allowedYes, bounded: same rule class, cap of 50, per-item outcomes recorded. Never unbounded select-all.
C-Q4If evidence changes after approvalReturn to needs_review. An approval applies to the evidence reviewed, not the record forever.
D-Q1Match before a semantic-search key existsYes. Deterministic and fuzzy generation now; semantic is a recall upgrade, not a prerequisite.
D-Q2Does the custodian get write authorityNever. It proposes; a separate restricted executor applies approved decisions.
D-Q3What the custodian may retry aloneBounded retries of preapproved idempotent jobs only. Never secrets, grants, schema, deploys or cursor resets.
D-Q4Who approves a learned rule promotionHuman review plus a versioned evaluation. No automatic promotion after N confirmations.
D-Q5Model and budget for the agentBC-absorbed for build and shadow; move to a Made key with a monthly cap before steady-state.
D-Q6Who holds the kill-switchBrian and Joe. It halts approved execution and agent runs, never source ingestion. Fails closed.
E-Q1The 6,694 abandoned queue rowsArchive with a record; re-derive from the current mirror. Their four-month-old evidence is untrustworthy. Never delete.
E-Q2Minting policy when no match is foundAuto-mint only on a unique authoritative external ID with no plausible candidate; otherwise review. Made may override — M-Q2.
E-Q3Backlog pass shapeCanary of 100 → measure precision → budgeted batches sized to reviewer capacity, with a stop condition.
E-Q4Drain to zero, or leave ambiguity visibleLeave it visible and counted. “100% dispositioned” is the goal; “100% matched” corrupts the data.
F-Q1Secret storageEdge Function secrets for EF-run feeds, n8n credentials for n8n-run feeds — matching where the code runs. No third store.
F-Q2Fix the ignored upsert error now or laterNow. done A failed write could report success; it now fails loudly.
F-Q3mss-watchdog over an empty pipelineRetire the watchdog; document the tables as dead. A watchdog guarding nothing trains people to ignore alerts.
G-Q1Can one person have multiple employersOne current employer canonically, with full relationship history retained. Otherwise advisors and board members break the model.
G-Q2Free text and notesOnly named fields with a stated purpose, never into model context without redaction. Privacy dimension — M-Q5.
H-Q1Where the review console livesBuild on bc-made behind server-side auth, port into the Made app once proven. Keeps a merge off the critical path.
H-Q2Which roles can do whatViewer / reviewer / steward / admin, enforced server-side and tested with real tokens.

The blockers — and exactly how to clear each

These are the only items we cannot do ourselves. Each one below is written to be executed without a follow-up call. They are not equally urgent, and we have deliberately marked which are not — two of the seven are genuinely low-impact, and treating all seven as emergencies would waste your attention on the wrong ones.

Read this first

None of these blocks the 33-task critical path. All of them sit in the last third of the plan. We can build the identity spine, the review loop, the custodian and the backlog drain to completion before any of them is cleared. What they unblock is depth (more CRM fields), consolidation (retiring duplicate writers), and scale (automated linking).

1

Salesforce integration credential

real impact

Blocked: we hold no Salesforce credential. The current pipeline uses a username/password grant whose secrets live outside our hands.

Who clears it: a Salesforce admin.

Steps

  1. Create an External Client App (or Connected App) for server-to-server use.
  2. Choose JWT bearer (certificate) or client credentials — either is fine; tell us which so we build to it.
  3. Create a dedicated API-only integration user — not a person’s login.
  4. Grant a read-only permission set covering only: Account, Contact, Lead, Opportunity, OpportunityContactRole, Quote, QuoteLineItem, Campaign, CampaignMember, and the stage/field-history objects already in use.
  5. Confirm org edition, API limits, and whether an API-only license is available.
  6. Name the owner of certificate and secret rotation.
Unblocks  The real field catalog — a describe run under a genuine integration identity. Cost of waiting  Field coverage stays inferred from whatever the current pipeline happens to pull; we cannot prove coverage or detect a field we are silently missing. Not an outage risk — the existing feed keeps working.
2

HubSpot integration credential

real impact

Blocked: no HubSpot credential in our hands. The contacts feed pulls ~18 hand-picked properties rather than enumerating what exists.

Who clears it: a HubSpot super admin.

Steps

  1. Create a scoped app credential with read scopes for contacts, companies, campaigns / marketing email assets, and associations.
  2. Do not grant any Deals scope. Deals are out of scope and we do not want the permission.
  3. No write scopes — ingestion is pull-only.
  4. Tell us the Marketing Hub tier (Pro or Enterprise).
  5. Tell us whether the Leads object (type 0-136) is actually used in your portal.
  6. Note: Service Keys are a public beta, REST-only, and cannot support webhooks — a supported app token is likely the better choice.
Unblocks  Real property enumeration, the Leads question, engagement scope. Cost of waiting  HubSpot depth stays contact-centric and hand-picked; we cannot distinguish “field not used” from “field not permitted.”
3

n8n Cloud access decision

medium

Blocked: core CRM extraction runs in Joe’s n8n Cloud. We can read those workflows but editing our repo copies does not push back, so we cannot fix an extraction defect at source.

Who clears it: Joe.

Steps — pick one

  1. Either grant editor access to madescientific.app.n8n.cloud;
  2. or agree to a parallel-run model: our feeds run alongside yours, and yours are disabled only after proven field-level parity.
Unblocks  Fixing extraction at source; retiring duplicate writers. Cost of waiting  Two ingestion paths coexist indefinitely; defects can only be worked around downstream. Our fallback  Parallel run, which we prefer anyway — it is the safer cutover.
4

Merge the review console to production

largely cleared

Was blocked: merging to main deploys the app, and the branch policy was ambiguous.

Cleared 2026-09-10

  1. The branch conflict is settled. main is the integration branch. develop had diverged, so it has been pointed back at main and the two are now identical — a PR can no longer be misrouted there.
  2. A correction worth recording: we had reported that the MADE-ID review UX was stranded off production. It was not. develop had changed nothing since its merge-base, and every made-id file was already on main, byte-identical — the review surfaces have been live all along. That conclusion had been drawn from commit counts rather than content.
  3. All MADE-ID work is now merged to main and building clean, so this is no longer a queue.
Still Made’s call  whether the review console eventually lives inside the Made app or stays on bc-made. Not urgent, and deliberately off the critical path.
5

Embedding key and model budget

low impact

Blocked: semantic candidate generation is off — there is no VOYAGE_API_KEY on the box, and no agreed model-spend cap.

Who clears it: the Made account owner.

Steps

  1. Provide the key and confirm the embedding model (we believe voyage-3).
  2. Set a monthly model-spend cap and an alert threshold.
Be sceptical of anyone calling this urgent  Exact external IDs plus normalized name, domain and email cover the large majority of a ~31k-record universe. This raises recall on hard cases. We are proceeding without it and treating it as a quality upgrade, not a dependency.
6

The eight business answers

real impact

Blocked: automation scale, field expansion and reviewer throughput all depend on decisions that encode your risk appetite.

Who clears it: Joe plus a business owner and a privacy owner.

Steps

  1. Answer M-Q1–M-Q8 on the Questions tab.
  2. If time is short, answer just M-Q1 (acceptable wrong-link rate), M-Q4 (reviewer capacity) and M-Q5 (data and privacy scope).
Cost of waiting  Automation stays shadow-only and field expansion stays conservative. The system still works — it just stays manual, and the backlog drains at whatever rate your reviewers can sustain.
7

Approval to retire legacy producers

medium

Blocked: dead crons keep running and two proposal tables keep diverging, but we will not disable anything in your production without approval.

Who clears it: Joe, after we present parity evidence.

Steps — approve after review

  1. Retire the made_id.match_queue and made_id.contact_match_queue drain crons — dead since May/July 2026.
  2. Archive the superseded er_contact_identity_proposals path once every outstanding decision is accounted for.
  3. Retire mss-watchdog and the empty hubspot_v2_* surface.
Explicitly staying  Your pg_cron assignment and auto-merge drains are the working foundation and are not touched. Cost of waiting  Alert noise, divergence, and wasted compute.
What we are not asking for

We are not asking for wider database access, and we are not asking you to lower any safety threshold

The ≥0.999 auto-merge floor stays exactly where it is. Nothing here requires new privileges for us — in fact the final stage of this plan reduces our access and hands you a documented consultant-access model. See the Build order tab, Stage I.

Optimizations we can offer

upside — none of these blocks anything

Options at each stage, ordered by return on your effort. These are offers, not requirements. The first one is the highest-leverage thing on this page relative to what it costs you.

The cheapest high-value move available

Grant a metadata-only CRM credential — describe and properties access with no record access at all. It unlocks the entire field catalog for both systems with almost no data-exposure risk, because it can read the shape of your CRM without reading a single customer record. If you do one thing from this page beyond answering M-Q1, do this.

StageOptimizationPayoff
A — baselineMetadata-only CRM credential (describe/properties, no record access)Unlocks the whole field catalog at near-zero exposure risk. The cheapest unblock available.
B — identityConfirm the account system-of-record policy early (M-Q3)Removes rework across canonical field selection, UI display and conflict handling.
C — reviewNominate one reviewer as decision owner for ambiguous casesTurns an unbounded queue into a triaged one. The biggest throughput lever that needs no automation.
C — reviewApprove bounded bulk approval for one low-risk rule classMultiplies reviewer output where the risk is lowest.
D — custodianOne holdout labeling session — ~200 pairs, roughly an hour of a reviewer’s timeProduces the precision evidence that makes every later automation decision defensible instead of speculative. Without it, thresholds are guesses.
E — backlogTell us which of the 1,941 unmatched accounts are known dead or out of scopeCould remove a large fraction of the backlog before a single match is computed.
F — ingestionMove CRM secrets into managed credential storage during the same session as blockers 1–2Rotation and least privilege land at zero marginal cost.
G — fieldsConfirm HubSpot tier and Leads usage in one messagePrevents us building against endpoints your portal does not have.
H — handoverName the on-call owner before cutoverAvoids the classic post-handover gap where nobody is watching the queue.

Build order — nine stages

Sequenced so that what is broken and unblocked comes first, and what is working but gated comes last. An earlier draft had this backwards — it started with ingestion, which is both healthy and the one thing we cannot edit.

A · Ground truth

Reproducible baseline; protect the review surface

B · Identity spine

One person record, one proposal path, real lineage

C · Review loop

Durable, replay-safe, attributable approvals

D · Custodian

Five skills, shadow first, holdout evaluation

E · Backlog

Work the real 1,941 + 7,244

F · Ingestion

Contracts + ownership — gated

G · Field depth

Catalog + relationships — gated

H · Registry + UX

Invariants, console, steady state

I · Narrow access

Least privilege + handover

Who can actually do what

StageTasksWe can do nowNeeds a decisionBlocked on Made
A — Ground truth and safety5320
B — Consolidate the identity spine9720
C — Review loop and approved execution6510
D — Custodian, five skills8710
E — Work the real backlog5320
Critical path subtotal (A–E)332580
F — Ingestion hardening and ownership8323
G — Field depth and relationships8413
H — Registry acceptance, UX, steady state6321
I — Narrow access and hand over4310
Total5938147

The point of that table

Stages A–E contain zero Made-blocked tasks. Eight need a decision, but every one of those can be built and shadow-tested to the point where the decision is a yes/no on evidence rather than a request for permission to start. We do not need to wait to make progress.

Stage I — reducing our own access, deliberately last

Stated plainly

Our working credential currently has broad rights on both databases. That is wider than a consultant should hold in steady state. We are keeping it for now because it is precisely what lets us build the 33-task critical path without putting a request in your queue for every step — and we are scheduling the reduction as a committed deliverable, not a vague intention.

TaskWhat we will do
I1Document the real grant and correct the stale access claims in our own documentation.
I2Define the least-privilege roles the finished system actually needs — a restricted ingestion writer, a review/executor role, a read-only analyst role — and prove the system runs on them.
I3Move our automation onto the narrowed roles, retaining logged break-glass only.
I4Hand you a consultant-access model: scoped tokens, rotation schedule, attribution, and an offboarding checklist.

Why last and not first: narrowing before the roles are known means guessing at permissions, then repeatedly asking Joe to widen them — slower and less secure than building first and narrowing once, from evidence of what the system genuinely requires.

How this plan was built

This is the fourth pass over the same problem. Each pass was run with a different model and a stricter evidence standard, and each one found that the previous pass had been too confident. That history is worth stating, because it is the reason to trust the current version — and the reason to keep treating parts of it as unverified.

PassMethodWhat it produced — and what was wrong with it
Auditfreeze-and-mapMapped the live system: producers, crons, proposal tables, agent internals. Correctly found that the matching logic was sound and the runtime was fragmented. Established the five-stage picture this site documents.
Revision 1AstraThe first seven-phase roadmap. Structurally sound, but it inherited undated claims as if they were current facts — a field count from a code comment, a “never wrote proposals” status that was already out of date, and a stale table treated as proof that a whole subsystem was broken.
Revision 2Astra — evidence reviewIntroduced evidence classes: inspected code vs dated notes vs vendor docs vs proposal. Corrected roughly a dozen overconfident claims, found the field-mapping collisions and the discarded upsert error, and separated scorer eligibility from write authority. Its sequence, however, was still wrong.
Revision 3Opus 5 — live measurementMeasured both production databases directly instead of reading our own documents. That inverted the plan: ingestion turned out healthy, the identity layer turned out to be where records are lost, the “backlog” turned out to be four months of abandoned rows, and three silent-failure defects surfaced — including lead-list generation that would have produced empty lists while reporting success.
Revision 3aOpus 5 — self-correctionChecking deployment state before shipping those fixes showed that none of the three defects was actually executing: one function is deployed nowhere, one has not run since 2026-08-06, one hook is unreferenced. Our own first write-up had called them live production data loss. Corrected here, in the roadmap and in the register — the same standard we applied to the older documents, applied to ourselves.
Consolidation2026-09-10 — landed on mainEverything above is now merged to main and building clean: the defect fixes, this site, the roadmap and register, and the whole research corpus (five wiki deep-dives plus the review deck). A second correction came out of it — we had reported the MADE-ID review UX as stranded off production; it never was. develop had changed nothing since its merge-base and every made-id file was already on main, byte-identical. The divergence has been closed by pointing develop back at main. Also rescued in passing: a completed commit that existed on exactly one branch and no other reference, one branch-delete away from being lost.

What each pass caught that the last one missed

1→2

Confidence → evidence

A column existing does not prove it is populated, current, copied downstream, or read by anything. A function name does not prove its behavior. An old audit is not a measurement. Revision 2 made every claim carry its evidence class.

2→3

Documents → the database

Revision 2 was still reasoning from repository files and dated notes. Measuring the running system contradicted several of them — including one of our own access documents, which described capabilities we no longer had, and which had quietly shaped the previous plan.

3

Plan → repair

The measurement pass was not supposed to change code. It found three defects that were failing silently in production — so those were fixed, verified against live data, and are recorded on the Where we are tab.

What is still not verified — stated deliberately

Deployed function bodies have not been diffed against the repository. Tenant permissions and API entitlements are unconfirmed (that is blockers 1 and 2). The real CRM field metadata is unknown for the same reason. We have not established why enqueueing to the two match queues stopped in May 2026. And the “593 fields” figure that appears in older notes is a code comment, not a measured count — we have not reproduced it.

The standard we are holding to

A number on this site carries a date, or it does not belong here

Every figure on this page was measured on 2026-09-09 at 22:00 UTC. Several of them moved during the day we measured them — the account and contact counts each shifted by one between the morning and evening pulls. That is normal for a live system, and it is exactly why an undated number is a liability rather than a fact.

MADE-ID plan to production · Stages A–I, 59 tasks, 6 Made-side blockers remaining (blocker 4 cleared 2026-09-10). All source documents, this site, and the research corpus are committed to main. Prepared for Made Scientific by BioCreative Strategies. All figures live-measured against Reporting xyopyttkhoxvnyeyijzb + Transfer jrfcfayphcmaxsixxupu, 2026-09-09 22:00 UTC. Source documents: docs/made-id-consolidation/03_OPTIMIZATION_ROADMAP.md (revision 3) + 04_DECISION_REGISTER.md. Internal — not for external share.