You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The problem — milestone 1 needs its boundary written down in one place, so that adding something is a deliberate act rather than a discovery halfway through a branch.
The fix — this issue is that list: a scope tree of what is in, what is deliberately out, and the delivery order. 18–23.5 engineering days, roughly five to six weeks. It owns the boundary only — docs/scope.md owns cost, the spec owns the model, CONTEXT.md owns the vocabulary, and where they disagree with this issue they win.
To decide — where an enquiry lands. docs/scope.md and the spec contradict each other, and Wire up the contact form #2 cannot be built until it is settled.
Full context — the reasoning, the constraints and what has already been ruled out. This is the part a coding agent should read.
Milestone 1 is the smallest thing that is a product: a visitor lands, finds a practitioner, and can contact them; a practitioner signs themselves up and Bluehex vouches for their credentials. This issue is the list. Anything not on it is not committed to milestone 1, and adding something here is a deliberate act rather than a discovery halfway through a branch.
This issue owns the boundary, not the cost or the design.docs/scope.md owns the estimates and the delivery order, docs/spec/profile-and-credentials.md owns the model and the DDL, CONTEXT.md owns the vocabulary, docs/adr/ owns the decisions. Where this issue and any of those disagree, they win and this issue is the bug.
The scope tree
Grouped by what the thing is rather than when it lands, so it survives the milestone and can carry the roadmap forward. The delivery order is further down and is a different question. This tree replaces the flat list that stood here — one list, so there is nothing to keep in sync.
Bluehex
│
├── Site
│ ├── Landing page and directory shell ..................... shipped
│ ├── Directory reads real data ............................ #53
│ └── Contact
│ ├── /contact page, mailto stopgap .................... shipped
│ └── The enquiry actually sends ....................... #2 ← destination unsettled
│
├── Identity
│ ├── Admin authority: role, admins, access token hook ..... shipped, #47
│ ├── The hosted project's copy of the hook ................ #41
│ └── Accounts: sign up, sign in, sessions, protection ..... #83
│
├── Profiles
│ ├── Schema
│ │ ├── Core: practitioners, contacts, review notes ...... #49
│ │ ├── Credentials, verification, badge clearing ........ #50
│ │ ├── Credentials come from a fixed list .............. folded into #49, #50
│ │ └── Withdrawal, account deletion, erasure ............ #52
│ ├── What the practitioner does
│ │ ├── Submission, and the verification step ............ #14
│ │ ├── Profile editor .................................. #71
│ │ ├── Scheduling URL, as a button ..................... #85
│ │ └── Portfolio links: website, GitHub, LinkedIn ...... #84
│ └── What Bluehex does
│ └── Review queue, and approval ...................... #72
│
├── Assurance
│ ├── The schema's invariants have tests .................. #45
│ └── The #35 proof transcript, re-expressed as tests ..... #55
│
├── Delivery
│ └── Migrations pushed to the hosted project from CI ..... #48
│
└── Meetup
└── The next event, on the home page .................... #60
Where an enquiry lands is contradicted across two documents, and #2 cannot be built until it is settled.docs/scope.md says the enquiry reaches Bluehex, closed by elimination on 2026-08-18. docs/spec/profile-and-credentials.md says the enquiry form "remains the only route to a practitioner's inbox", and src/app/contact/contact-form.tsx records the spec as superseded on 2026-08-16 in favour of delivering to contact_email — which is why it says #2 needs a server-side send rather than a hosted form service.
The scope document's elimination argument does not hold: what is out of scope is publishing the address, and a server-side send to contact_email publishes nothing. By the precedence rule the spec wins and docs/scope.md is the bug, but this needs deciding rather than inferring, because it decides #2's implementation. Either way a published address stays out, and visitors who want the practitioner directly use the scheduling and portfolio links.
Outside the tree
Everything below is real work that is not in milestone 1. It is listed against the same branches so that a later milestone extends this tree rather than starting a new one:
├── Site
│ └── Contact
│ └── Proxied enquiry address, copying both parties ... milestone 2 candidate
├── Profiles
│ └── Service catalogue, and promoting into it ............ #78
└── Assurance
├── Vitest .............................................. #5
└── React Testing Library ............................... #56
(unbranched) Automated dependency updates ................... #12
#9 looks closeable — the domain model it asked for is written and settled across docs/spec/profile-and-credentials.md, CONTEXT.md and ADR-0001.
Four things are out on purpose rather than unscheduled, and each is close enough to something in the tree to be inferred from it. They are the next section.
Gaps, and what closed them
Three pieces were committed with nowhere to be tracked. Two are now cut and one turned out to be already done:
Authentication (phase 2b) — now Authentication: sign up, sign in, sessions, and route protection #83. Supabase Auth: accounts, sessions, sign-up with email verification, password reset, route protection. 3–4 engineering days, the largest single piece in the milestone, and everything in 2c is blocked behind it. Admin role, the admins table, and the access token hook #47 landed admin authority in the database, which is not the same thing: there is no @supabase/ssr dependency, no middleware.ts, no sign-in route, and src/lib/supabase.ts is an anonymous client built with persistSession: false.
The spec amendment those fields needed — done.docs/spec/profile-and-credentials.md now carries scheduling_url, website_url, github_url and linkedin_url in the DDL and in every grant list, and answers all three questions the 2026-08-18 reversal opened: not attested, anon-visible only on approved profiles through the existing policy, shape-checked with no host allowlist. It no longer blocks Profile core: contacts, practitioners, review notes, policies, guard and admin RPCs #49.
Explicitly out of scope
Each is out on purpose, and each is close enough to something in scope to be inferred from it:
Cross-verification against Skilljar, LinkedIn or this site. The Skilljar half is closed, not merely unscheduled — the API is tenant-scoped to Anthropic. The rest is out because the badge means a human looked, and an automated match either adds nothing to that or quietly replaces it.
A full bookings calendar. A scheduling URL is a string that leaves for a tool the practitioner already runs. The moment Bluehex holds availability, or a booking exists as a row here, that is the line.
A Meetup events page. The card is in, the page is out, and Meetup's own page is the link. Moved out deliberately after discussion.
A practitioner's email address on the profile. Out rather than deferred, and it survives the reversal that let every other kind of link through. practitioner_contacts has no anon grants by any route.
Delivery order
The phases are a dependency chain, not a preference. Profile reads need no auth and can come first, which puts the directory on real data early. Profile writes cannot precede auth: profiles carry an account foreign key that does not exist until the user table does, and building writes first means rewriting every mutation and every policy afterwards.
Curated intake — profile arrives by mail or pull request, Bluehex checks the credentials, commits it. No database, no accounts, no secrets. 1–1.5d.
The Meetup card (#60) and the contact form (#2) are independent of the chain and can land at any point.
Total: 18–23.5 engineering days, roughly five to six weeks elapsed at the 4 engineering-days-a-week capacity assumption. Read it as the sum of the sections in docs/scope.md rather than as an independent estimate — if one of them moves, this moves with it.
The number that moves this most is attention, not typing speed. Work labelled afk compresses hard because it is close to transcription from a settled design; work labelled hitl barely compresses at all, and 2b and 2c are mostly hitl.
TL;DR
docs/scope.mdowns cost, the spec owns the model,CONTEXT.mdowns the vocabulary, and where they disagree with this issue they win.docs/scope.mdand the spec contradict each other, and Wire up the contact form #2 cannot be built until it is settled.Full context — the reasoning, the constraints and what has already been ruled out. This is the part a coding agent should read.
Milestone 1 is the smallest thing that is a product: a visitor lands, finds a practitioner, and can contact them; a practitioner signs themselves up and Bluehex vouches for their credentials. This issue is the list. Anything not on it is not committed to milestone 1, and adding something here is a deliberate act rather than a discovery halfway through a branch.
This issue owns the boundary, not the cost or the design.
docs/scope.mdowns the estimates and the delivery order,docs/spec/profile-and-credentials.mdowns the model and the DDL,CONTEXT.mdowns the vocabulary,docs/adr/owns the decisions. Where this issue and any of those disagree, they win and this issue is the bug.The scope tree
Grouped by what the thing is rather than when it lands, so it survives the milestone and can carry the roadmap forward. The delivery order is further down and is a different question. This tree replaces the flat list that stood here — one list, so there is nothing to keep in sync.
Where an enquiry lands is contradicted across two documents, and #2 cannot be built until it is settled.
docs/scope.mdsays the enquiry reaches Bluehex, closed by elimination on 2026-08-18.docs/spec/profile-and-credentials.mdsays the enquiry form "remains the only route to a practitioner's inbox", andsrc/app/contact/contact-form.tsxrecords the spec as superseded on 2026-08-16 in favour of delivering tocontact_email— which is why it says #2 needs a server-side send rather than a hosted form service.The scope document's elimination argument does not hold: what is out of scope is publishing the address, and a server-side send to
contact_emailpublishes nothing. By the precedence rule the spec wins anddocs/scope.mdis the bug, but this needs deciding rather than inferring, because it decides #2's implementation. Either way a published address stays out, and visitors who want the practitioner directly use the scheduling and portfolio links.Outside the tree
Everything below is real work that is not in milestone 1. It is listed against the same branches so that a later milestone extends this tree rather than starting a new one:
#9 looks closeable — the domain model it asked for is written and settled across
docs/spec/profile-and-credentials.md,CONTEXT.mdand ADR-0001.Four things are out on purpose rather than unscheduled, and each is close enough to something in the tree to be inferred from it. They are the next section.
Gaps, and what closed them
Three pieces were committed with nowhere to be tracked. Two are now cut and one turned out to be already done:
@supabase/ssrdependency, nomiddleware.ts, no sign-in route, andsrc/lib/supabase.tsis an anonymous client built withpersistSession: false.docs/spec/profile-and-credentials.mdnow carriesscheduling_url,website_url,github_urlandlinkedin_urlin the DDL and in every grant list, and answers all three questions the 2026-08-18 reversal opened: not attested,anon-visible only onapprovedprofiles through the existing policy, shape-checked with no host allowlist. It no longer blocks Profile core: contacts, practitioners, review notes, policies, guard and admin RPCs #49.Explicitly out of scope
Each is out on purpose, and each is close enough to something in scope to be inferred from it:
practitioner_contactshas noanongrants by any route.Delivery order
The phases are a dependency chain, not a preference. Profile reads need no auth and can come first, which puts the directory on real data early. Profile writes cannot precede auth: profiles carry an account foreign key that does not exist until the user table does, and building writes first means rewriting every mutation and every policy afterwards.
The Meetup card (#60) and the contact form (#2) are independent of the chain and can land at any point.
Total: 18–23.5 engineering days, roughly five to six weeks elapsed at the 4 engineering-days-a-week capacity assumption. Read it as the sum of the sections in
docs/scope.mdrather than as an independent estimate — if one of them moves, this moves with it.The number that moves this most is attention, not typing speed. Work labelled
afkcompresses hard because it is close to transcription from a settled design; work labelledhitlbarely compresses at all, and 2b and 2c are mostlyhitl.