Amendment 2026-05-29: BI tool is Hex Apps, not Metabase Cloud. See WF-6.9 v1.1 change log for rationale. Phase C step “Metabase Cloud free trial” → “Hex Team trial”. Embedding mechanic (iframe in Astro page, URL stored in Cloudflare Pages env var) is unchanged. Two production dashboards (Daily BC Dashboard live 2026-05-20, Strategic Weekly Review live 2026-05-29) followed this pattern successfully. Substantive rewrite deferred.
WF-6.12 — Internal Portal Build Plan
Type: Implementation plan Value Driver: VD6 Financial Operations (delivery infrastructure) → consumers across VD1–VD5 Version: 1.0 Date: 2026-05-13 Owner: Roger Thompson (build); Claude (pair) — handoff to Vincent/Brandon as consumers when v1 ships Status: Active — WF-6.11 dependency cleared 2026-05-13 Companion to: WF-6.11 Website Infrastructure Decision (stack), WF-6.10 Dashboard Layer Plan (per-dashboard scope)
Purpose
Implementation plan for the internal portal at portal.cedarfell-logistics.com. This document is what Roger and Claude follow during the actual build. It picks up where WF-6.11 left off (stack approved) and stops where WF-6.6 picks up (operational governance once the portal is live).
Scope
In scope:
- Repo scaffold, Astro configuration, content collections
- Route inventory and information architecture
- Content migration from
/VD*/Workflows/*.mdand/Strategic/*.mdto portal pages - Cloudflare Pages project setup
- Cloudflare Access policy (Google IdP + exception list)
- Metabase dashboard embedding (deferred to WF-6.10 for per-dashboard detail)
- DNS migration from Google Cloud DNS to Cloudflare
- Cutover plan for the existing Squarespace site at the apex
- Per-CSA replication recipe
Out of scope:
- Per-dashboard content (WF-6.10)
- External public site routes (
/customers,/vendors,/careers,/about) — parallel workstream, separate doc when prioritized - Portal operations and change management once live (WF-6.6 proposed)
Preconditions
Before kicking off the build:
| # | Precondition | Owner | Status |
|---|---|---|---|
| 1 | WF-6.11 Website Infrastructure Decision approved | Roger | Pending sign-off |
| 2 | WF-6.9 Dashboard Tool Decision approved | Roger | Approved 2026-05-13 |
| 3 | Domain cedarfell-logistics.com owned + DNS access | Roger | Confirmed — DNS on Google Cloud DNS |
| 4 | Google Workspace + Cloud Identity directory in place | Roger | Confirmed — Roger/Vincent/Brandon on Workspace, Aaliyah on Cloud Identity |
| 5 | Cloudflare account created | Roger | TBD — single action at https://dash.cloudflare.com/sign-up |
| 6 | Metabase Cloud free trial started (for first iframe) | Roger | TBD — kicks off at the dashboard embedding step |
| 7 | Tensor emails for Jana + Sheryl collected | Aaliyah / Roger | TBD — Cloudflare Access exception list input |
Route inventory
The portal opens with six top-level routes. Each is described below with its content source, build cost, and dependencies.
| Route | Purpose | Content source | Build cost | Notes |
|---|---|---|---|---|
/ | Team home — single bookmark for the team | Hand-authored landing page | 0.5 day | Surface today’s key links: Daily BC Dashboard, Open SOPs, Recent Decisions, Help |
/dashboards | Embedded Metabase dashboards | Metabase iframes per WF-6.10 | 0.5 day for shell; per-dashboard cost in WF-6.10 | Iframe-only at launch (Starter tier); interactive embed at Pro tier (5+ CSAs) |
/sops | SOP corpus rendered as web pages | /VD*/Workflows/*.md (Cowork) | 1 day for collection schema + rendering; ongoing as SOPs are written | Astro content collections; index by VD; full-text search optional v1.1 |
/kpis | KPI Taxonomy as browsable site | /Strategic/KPI-Taxonomy-v1.1.md | 0.5 day | Render the 115 KPIs with filters by VD, layer, readiness state |
/decisions | Decision Catalogue + Decisions-Log | /VD6 - Financial Operations/Data/Decision-Catalogue.md, Decisions-Log.md | 0.5 day | Searchable archive; reverse-chronological default |
/handbook | Onboarding + role pages | Net-new content authored in markdown | 1 day for shell; per-page content over time | Team roster, system inventory, compensation framework, escalation paths |
Total v1 build cost (route layer): ~4 days of Roger/Claude pair-build time, not counting per-dashboard work.
Optional v1.1+ routes
| Route | Purpose | Trigger |
|---|---|---|
/changelog | Auto-generated from Decisions-Log entries | After 50+ decisions logged |
/search | Full-text search across all portal content | If BCs report scanning friction |
/admin | Roger-only admin links (Cloudflare, MotherDuck, Metabase) | Day 1 if Roger wants it |
Content migration approach
The non-obvious advantage of Astro for Cedarfell: the existing SOP corpus migrates with zero conversion.
How Astro reads /VD*/Workflows/*.md
Astro’s content collections accept any glob pattern. The portal repo (cedarfell-portal) will reference the SOP files in place rather than copy them. Two options:
Option 1 — Git submodule (recommended). The portal repo includes the Cowork workspace as a submodule. Astro’s content config globs into /cowork-workspace/VD*/Workflows/*.md. SOPs stay in Cowork as the source of truth; portal builds pull the latest submodule pointer at build time. Trade-off: requires submodule update + portal rebuild after SOP changes (5-minute manual step or scheduled daily build).
Option 2 — Build-time sync script. A pre-build script scripts/sync-content.mjs copies /VD*/Workflows/*.md into src/content/sops/ before Astro builds. Faster local dev; requires running the sync script. Cloudflare Pages can run this in the build command.
Decision for v1: start with Option 2 (build-time sync). Simpler operationally; no submodule mental overhead. Revisit submodules if multi-CSA scales create coordination friction.
Front matter expected by Astro
Astro content collections validate front matter against a Zod schema declared in src/content/config.ts. The schema for SOPs:
const sopsCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
valueDriver: z.enum(['VD1', 'VD2', 'VD3', 'VD4', 'VD5', 'VD6', 'Strategic']),
workflowId: z.string().regex(/^WF-\d+\.\d+/),
owner: z.string(),
version: z.string(),
status: z.enum(['Draft', 'Active', 'Archived']),
lastUpdated: z.date(),
}),
});
The Cedarfell SOPs (e.g., WF-4.4 Monthly Driver Scorecard SOP.md) already encode this metadata in their headers but not as YAML front matter. Migration step: add YAML front matter blocks to each SOP file in Cowork. One-time pass; ~30 minutes total for the existing corpus.
Example:
---
title: Monthly Driver Scorecard SOP
valueDriver: VD4
workflowId: WF-4.4
owner: BC on duty
version: 1.0
status: Active
lastUpdated: 2026-04-29
---
The body of each SOP stays untouched. Astro renders the markdown as HTML with theme-applied typography.
Rendering for human consumption
The /sops route shows:
- Index page: all SOPs grouped by Value Driver; filterable by status and workflow ID
- Detail page: one rendered SOP per page, with metadata header (owner, version, last updated, related KPIs auto-linked)
- Cross-references: workflow IDs in body text auto-link to other SOP detail pages
This is build-time work — no runtime JavaScript needed.
KPI Taxonomy rendering
The single KPI-Taxonomy-v1.1.md file (115 KPIs in one document) is more useful as a structured browsable site than a single long page. Migration step: a one-time pre-build script scripts/parse-kpis.mjs parses the markdown into a JSON file (src/data/kpis.json) with one record per KPI. Astro renders the JSON via a dynamic route /kpis/[id].astro for detail and /kpis/index.astro for the indexable list.
Schema per KPI record:
{
"id": "OP.VD4.10",
"name": "VEDR confirmed events per driver",
"layer": "Operational",
"valueDriver": "VD4",
"formula": "...",
"owner": "BC on duty",
"cadence": "Monthly",
"dataSource": "vedr_events",
"readiness": "P",
"target": "...",
"decisionsSupported": ["..."]
}
Decisions rendering
Same shape as KPIs. Parse Decisions-Log.md table rows into JSON; render as /decisions/index.astro (reverse-chronological table) and /decisions/[date]/[slug].astro (detail page per decision).
Authentication setup
Cloudflare Access policy
The portal subdomain portal.cedarfell-logistics.com is registered as a Cloudflare Access protected application. Configuration:
Identity provider. Single provider: Google. Cloudflare Access supports Google as a native OIDC integration. Setup:
- In Cloudflare Zero Trust dashboard → Settings → Authentication → Add new
- Provider type: Google
- Authorize Cloudflare to read identity from
cedarfell-logistics.comGoogle directory (Roger consents during setup) - Save
This works for both Workspace users (Roger, Vincent, Brandon) and Cloud Identity users (Aaliyah) — they’re the same Google directory under the hood.
Access policy. One application: portal.cedarfell-logistics.com. One policy attached: “Cedarfell team + Tensor bookkeepers.”
Policy rules:
- Include — emails ending in:
@cedarfell-logistics.com - Include — emails:
jana@<tensor-domain>,sheryl@<tensor-domain>(exact Tensor email addresses to be collected before build)
Effect: anyone hitting portal.cedarfell-logistics.com gets redirected to a Google sign-in page. After signing in with an @cedarfell-logistics.com address or one of the two whitelisted Tensor emails, they get the portal. Anyone else gets an “Access denied” screen.
Session duration. 24 hours default. Configurable per-policy if we want longer/shorter.
Break-glass access. Roger configures a service token in Cloudflare Access (Zero Trust → Access → Service Tokens) that grants emergency access if Google OIDC is down. Token is rotated annually; stored in Roger’s password manager. Never used unless real emergency.
User onboarding
When a user accesses portal.cedarfell-logistics.com for the first time:
- Cloudflare Access redirects to Google sign-in.
- User selects their Google account (or signs in if not in browser).
- Google returns identity to Cloudflare.
- Cloudflare matches against policy rules.
- If match → 24-hour JWT cookie set → user lands on portal.
- If no match → Access denied screen with support email (Roger’s address).
No new accounts created. No new passwords. Users sign in with the Google account they already use for email.
Roster confirmed (2026-05-13)
| Person | Identity type | Access | |
|---|---|---|---|
| Roger Thompson | Workspace | roger.thompson@cedarfell-logistics.com | Full |
| Vincent Tablet | Workspace | vincent.tablet@cedarfell-logistics.com | Full |
| Brandon Fankhauser | Workspace | brandon.fankhauser@cedarfell-logistics.com | Full |
| Aaliyah Monet | Cloud Identity | aaliyah@cedarfell-logistics.com | Full |
| Jana (JSJ Consultants — Tensor’s US bookkeeping arm) | External | jana@jsjconsultants.com | Full (financial dashboards primary use) |
| Sheryl (JSJ Consultants — offshore bookkeeping) | External | sheryl@jsjconsultants.com | Full (financial dashboards primary use) |
| Sanjay (Tensor.Financial — account manager) | External | sanjay@tensor.financial | Full (relationship oversight) |
Aaliyah’s access status revisits when she migrates to Swan Island. Likely outcome: she keeps the email + portal access until the operational transition completes, then either retains as cross-entity admin or rotates off.
Mechanic + driver access (deferred)
Mechanics (Isaac, Brian) and drivers don’t get portal access at v1. Mechanic-facing dashboards (PM compliance, fleet uptime) can be added in v2 if Roger sees value. Drivers consume the monthly scorecard via printed page (per WF-4.4), not the portal.
Dashboard embedding strategy
Detailed per-dashboard scope is in WF-6.10. This section covers the embedding mechanic — how Metabase content shows up inside the portal.
Two embedding modes (from WF-6.9)
Static iframe (Metabase Cloud Starter, 1 CSA). Each dashboard gets a public-with-token iframe URL that the portal embeds in a <iframe> tag. No interactivity from inside the portal — filters, drill-downs, and time-range changes require the user to open Metabase directly in a new tab. BCs see read-only snapshots refreshed at the underlying dashboard’s cache interval.
Interactive embed (Metabase Cloud Pro, 5+ CSAs). Cloudflare’s JWT can be passed to Metabase via signed embedding URLs; users get full interactivity (filters, drill-downs, custom date ranges) inside the iframe with row-level security applied based on identity. Adds ~1 week of integration work at upgrade time.
v1 decision: static iframes
For 1-CSA scale and Metabase Starter, static iframes are sufficient. Each /dashboards/<name> route is essentially:
---
import Layout from '../../layouts/Portal.astro';
---
<Layout title="Daily BC Dashboard">
<iframe
src="https://cedarfell.metabaseapp.com/public/dashboard/<token>"
frameborder="0"
width="100%"
height="900"
allowtransparency
/>
</Layout>
The iframe URL token is rotated when a dashboard is materially changed. Tokens are stored in Cloudflare Pages environment variables (not committed to git) so they don’t end up in public version control. Pages env vars are accessible at build time only.
Dashboard refresh cadence
Metabase Cloud refreshes dashboard data on a configurable schedule per question (default: every hour for SQL questions). For daily-focused dashboards (Daily BC, Daily Owner), we set refresh to every 15 minutes during morning hours (5 AM – 10 AM Pacific) and hourly the rest of the day. For weekly/monthly dashboards, once per day at 4 AM is sufficient. Details in WF-6.10.
Build sequence
The portal builds incrementally over four phases. Each phase produces a working artifact that the team can use before the next phase starts.
Phase A — Scaffold (Day 1)
Goal: Empty portal at portal.cedarfell-logistics.com rendering a single landing page, behind Cloudflare Access.
Steps:
- Roger creates
cedarfell-portalrepo on GitHub. - Roger creates Cloudflare account (if not already) at https://dash.cloudflare.com/sign-up.
- Roger creates Cloudflare Pages project: connect to
cedarfell-portalGitHub repo; framework preset = Astro; auto-deploy main branch. - Roger or Claude scaffolds Astro project:
npm create astro@latestwith minimal template. - Author placeholder
src/pages/index.astro(“Cedarfell Portal — under construction”). - Push to main; verify Cloudflare Pages auto-builds and serves at
<project>.pages.dev. - Add custom domain
portal.cedarfell-logistics.comin Cloudflare Pages settings. - DNS step (see DNS Migration section): add CNAME for
portalsubdomain pointing at the Pages project. Initially this is added in Google Cloud DNS (until full nameserver migration in Phase D). - In Cloudflare Zero Trust dashboard, add Google identity provider.
- Create Cloudflare Access application for
portal.cedarfell-logistics.com; attach the policy described above. - Roger tests: visit
portal.cedarfell-logistics.comfrom incognito → Google sign-in → portal landing page renders. - Have Vincent + Brandon test their sign-in independently.
Exit criteria: Roger, Vincent, Brandon, Aaliyah can all sign in. Anyone else gets denied. Page loads in under 2 seconds.
Time estimate: 0.5 day for Roger; 0.5 day Claude pair.
Phase B — First three SOPs as proof (Day 2)
Goal: Validate the content migration approach on three real SOPs.
Steps:
- Add YAML front matter to three SOPs in Cowork (suggest: WF-2.1 Morning BC Protocol, WF-4.4 Monthly Driver Scorecard, WF-6.4 Scorecard Data Acquisition Plan — one from VD2, one from VD4, one from VD6).
- In
cedarfell-portalrepo, scaffoldsrc/content/config.tswith the SOP collection schema described above. - Implement
scripts/sync-content.mjsto copy/VD*/Workflows/*.mdfrom the Cowork workspace intosrc/content/sops/. - Implement
src/pages/sops/index.astro(list view) andsrc/pages/sops/[...slug].astro(detail view). - Configure Cloudflare Pages build command to run the sync script before
npm run build. (Cloudflare Pages can mount the Cowork workspace via a git submodule or via a published mirror — to be decided in implementation; the simplest path for v1 is a published mirror.) - Push; verify
/sopspage lists three entries; click into each renders correctly. - Roger + Vincent + Brandon spot-check rendering on phones.
Exit criteria: three SOPs render correctly; navigation works; mobile readable.
Time estimate: 1 day for Roger; 1 day Claude pair.
Phase C — First dashboard embed (Day 3–4)
Goal: Daily BC Dashboard live at /dashboards/daily-bc.
Steps:
- Roger starts Metabase Cloud free trial (per WF-6.9 negotiation point #2): 14 days to validate.
- Connect Metabase to MotherDuck
cedarfelldatabase via JDBC. - Build the Daily BC Dashboard in Metabase per WF-6.10 spec (questions, layout, refresh schedule).
- Generate the public-with-token URL.
- Add the iframe page in
cedarfell-portal:src/pages/dashboards/daily-bc.astro. - Update
src/pages/dashboards/index.astro(list view) with link to Daily BC. - Push; verify iframe renders inside the portal at
portal.cedarfell-logistics.com/dashboards/daily-bc. - Vincent + Brandon test for one morning cycle.
Exit criteria: Daily BC Dashboard renders in portal; BCs validate the data matches what they’d see opening Metabase directly.
Time estimate: 1.5 days for Roger; 1.5 days Claude pair.
Phase D — Expand and migrate DNS (Day 5–7)
Goal: Remaining v1 routes operational; nameservers moved to Cloudflare for full Cloudflare-native control.
Steps:
- Add YAML front matter to the remaining SOPs in Cowork (~30 min).
- Build
/kpisroute with the KPI Taxonomy parsed from/Strategic/KPI-Taxonomy-v1.1.md. - Build
/decisionsroute with the Decisions-Log parsed. - Author
/handbookcontent — at minimum, team roster, system inventory, escalation paths. - Author
/(portal home) with quick links to today’s key resources. - Migrate nameservers from Google Cloud DNS to Cloudflare (detail below).
- Verify everything still works after DNS migration.
Exit criteria: all six routes live; team can use the portal as the daily single-bookmark home.
Time estimate: 2 days for Roger; 2 days Claude pair.
Total v1 build time: ~5 days of Roger time over ~2 weeks elapsed (DNS propagation and BC testing add elapsed time without consuming build time).
DNS migration plan
Current state (confirmed 2026-05-13)
- Registrar: likely Squarespace Domains (Google sold Domains business to Squarespace in 2023)
- DNS host: Google Cloud DNS (nameservers
ns-cloud-e1throughe4.googledomains.com) - Existing
www.cedarfell-logistics.comCNAME points to Squarespace-hosted site - Apex
cedarfell-logistics.comlikely also configured for the Squarespace site
Target state
- Registrar: unchanged (stays at Squarespace Domains, or optionally migrate to Cloudflare Registrar for at-cost domain renewal — separate decision; not blocking)
- DNS host: Cloudflare DNS
- Apex: routed to Cloudflare Pages external site (or to an interim landing page until external site is built)
portal.cedarfell-logistics.comsubdomain: routed to Cloudflare Pages portal project- Existing Squarespace site: stays up at a transitional URL or shuts down once external site is ready (Roger’s call — see “Squarespace cutover” below)
Migration steps (no outage if done in this order)
- Create Cloudflare account + add domain (5 min)
- In Cloudflare dashboard → “Add a Site” → enter
cedarfell-logistics.com - Cloudflare scans existing DNS records and imports them automatically
- Cloudflare provides two assigned nameservers (e.g.,
ada.ns.cloudflare.comandaiden.ns.cloudflare.com)
- In Cloudflare dashboard → “Add a Site” → enter
- Verify imported records (5 min)
- Confirm the
wwwCNAME (Squarespace) and any apex records imported correctly - Do NOT delete any records yet
- Confirm the
- Add new portal subdomain CNAME (2 min)
- In Cloudflare DNS → add CNAME for
portalpointing at the Cloudflare Pages project URL - Set proxied (orange cloud) so Cloudflare Access can gate it
- In Cloudflare DNS → add CNAME for
- Set TTLs low at the current DNS provider (5 min)
- In Google Cloud DNS, reduce TTLs on existing records to 300 seconds (5 min) before the switch — propagation in case of rollback is fast
- Wait 24–48 hours for low TTLs to fully propagate
- Switch nameservers (5 min + propagation)
- In Squarespace Domains console (or wherever the domain registrar lives) → update nameservers to Cloudflare’s two assigned NS records
- Propagation: typically 1–4 hours for most users, up to 24 hours for stragglers
- Monitor propagation (during 1–24 hours)
- Check at https://www.whatsmydns.net/ that resolvers return Cloudflare nameservers globally
- Verify existing
www.cedarfell-logistics.com(Squarespace site) still resolves correctly during transition - Verify
portal.cedarfell-logistics.comresolves to Cloudflare Pages once nameservers propagate
- Test Cloudflare Access on portal (30 min)
- Sign-in flow works
- Roster of allowed users tested
- Deny screen for unauthorized users tested
- Decommission Google Cloud DNS zone (cleanup, 5 min)
- Once Cloudflare is authoritative and stable for a week, the Google Cloud DNS zone for
cedarfell-logistics.comcan be removed - Optional; leaving it doesn’t cause harm
- Once Cloudflare is authoritative and stable for a week, the Google Cloud DNS zone for
Total elapsed time: 1–3 days. Total active work: ~1 hour.
Rollback plan
If anything breaks during nameserver migration:
- In Squarespace Domains console → revert nameservers back to Google Cloud DNS (
ns-cloud-eN.googledomains.com) - Propagation back to old state: 1–4 hours
- Investigate the failure in a non-production environment before re-attempting
The low TTLs set in step 4 ensure rollback propagates fast.
Squarespace site cutover
There’s currently a live Squarespace-hosted site at www.cedarfell-logistics.com. The new architecture replaces it. Two cutover paths:
Option 1 — Cutover at Phase D nameserver migration
Build the external Astro site (/, /customers, /vendors, /careers, /about) in the same cedarfell-portal repo (or a sibling repo cedarfell-public). Cloudflare Pages serves both: apex and www. go to the new Astro site, portal. goes to the protected portal.
When nameservers cut to Cloudflare in Phase D, the apex/www traffic starts going to the new Astro site, and the Squarespace site stops getting visitors. The Squarespace subscription can be canceled after 30 days of clean traffic data.
Trade-off: the new external site has to be ready by Phase D. If external site content isn’t ready, the apex/www would show a barebones Astro placeholder.
Option 2 — Cutover later (defer external site)
Keep Squarespace serving the apex/www in the new Cloudflare DNS. The CNAME stays www → ext-sq.squarespace.com; apex stays however it’s configured today. Only portal.cedarfell-logistics.com is new and Cloudflare-served.
When the external Astro site is ready (separate workstream), update the DNS records to point at Cloudflare Pages for the external project and decommission Squarespace.
Trade-off: Squarespace subscription continues (~$200/yr) until external site is ready. Two DNS zones to maintain temporarily.
Decision for v1: Option 2. Portal is the priority; external site is parallel work; don’t gate portal launch on external site readiness. Roger’s call.
Per-CSA replication recipe
A new CSA owner clones the portal in under half a day. Step-by-step:
- Clone the template repo (
git clone cedarfell-portal csa-name-portal) - Repoint content sources in
scripts/sync-content.mjsto the new CSA’s Cowork workspace path or content mirror - Update site URL in
astro.config.mjs(e.g.,site: 'https://portal.csa-name.com') - Update brand assets in
public/(logo, favicon, theme colors in Tailwind config) - Create Cloudflare account for new CSA (or invite into existing parent organization if portfolio is managed centrally)
- Create Cloudflare Pages project: connect to the new GitHub repo
- Add custom domain
portal.csa-name.com - Configure Cloudflare Access: identity provider = Google (the new CSA’s Google directory); policy =
@csa-name.comallowed + Tensor exception list - Subscribe to Metabase Cloud Starter for the new CSA (separate $1K/yr per WF-6.9)
- Connect Metabase to the new CSA’s MotherDuck database
- Rebuild dashboards for the new CSA’s data, or clone via Metabase JSON export
- Update dashboard iframe URLs in
src/pages/dashboards/*.astro - DNS: point new domain’s nameservers at Cloudflare
- Test full sign-in flow with new CSA team members
Total per-CSA setup: ~4 hours of work; ~24 hours elapsed time including DNS propagation and team testing.
Total per-CSA cost: $0 hosting + $0 auth (until 50 users) + Metabase Cloud ($1K/yr) + domain ($15/yr).
Open dependencies
These items aren’t blockers for the build but must be resolved before portal launch:
| # | Item | Owner | Trigger | Resolution path |
|---|---|---|---|---|
| 1 | Tensor exact email addresses for Jana + Sheryl | Aaliyah/Roger | Before Phase A Cloudflare Access policy created | Aaliyah asks Tensor next time she’s in contact |
| 2 | Cloudflare account creation | Roger | Phase A Day 1 | One-time signup at dash.cloudflare.com/sign-up |
| 3 | Metabase Cloud free trial start | Roger | Phase C Day 3 | One-time signup at store.metabase.com |
| 4 | YAML front matter added to existing SOPs | Roger | Phase B Day 2 | ~30 minutes in Cowork; one-time pass |
| 5 | Content mirror or submodule strategy for /VD*/Workflows/*.md | Roger + Claude | Phase B Day 2 | Implementation decision during Phase B; published mirror is simplest |
| 6 | Cutover decision for Squarespace site | Roger | Phase D Day 7 | Confirm Option 2 (defer external site) holds; decision can be revisited later |
| 7 | DNS migration timing | Roger | Phase D Day 5–7 | Run during low-traffic period (typical low traffic: weekends or evenings Pacific) |
Risk register
| # | Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| 1 | DNS migration causes brief outage of existing Squarespace site | Low | Medium | Low TTLs pre-cut; rollback path documented; staged during low-traffic window |
| 2 | Cloudflare Access policy mistakenly excludes a real user | Medium | Medium | Roster confirmed in writing 2026-05-13; staged user testing in Phase A Day 1 |
| 3 | Metabase iframe URL leaks publicly and exposes data | Low | High | Iframe URLs stored in Cloudflare Pages env vars (not git); rotated on material dashboard changes; portal route is Cloudflare-Access-gated anyway |
| 4 | SOP front matter changes break Cowork’s normal workflow for Roger | Low | Low | YAML front matter is invisible in markdown rendering and standard for static site generators; doesn’t interfere with Cowork |
| 5 | Build script can’t read /VD*/Workflows/*.md from Cowork in CI | Medium | Medium | Implementation decision in Phase B: use a published content mirror to a GitHub repo Cloudflare Pages can clone, OR use a build hook that pushes content on Cowork-side changes |
| 6 | BC adoption fails — they keep opening individual files instead of using portal | Medium | High | Daily BC Dashboard is the portal’s first compelling feature; bookmark replacement (portal.cedarfell-logistics.com as morning home page) is the behavior change |
| 7 | Cloudflare Pages free tier exceeded | Low | Low | At 1–5 CSA scale, well within the 500-builds/mo and unlimited bandwidth tiers |
| 8 | Mobile rendering bug surfaces post-launch | Medium | Low | Tailwind responsive defaults handle most cases; Roger spot-checks on phone after each phase |
| 9 | Google directory access revoked or scoped incorrectly | Low | Medium | Cloudflare Access supports multiple IdPs; if Google directory access is lost, add a backup OIDC provider |
| 10 | Existing Squarespace site has hidden content that the team uses | Low | Low | Take an inventory of the current Squarespace site during Phase A; migrate any actively-used content to the new external site before cutover |
Measurable outcomes
Portal v1 ships when:
| Metric | Target | Measurement |
|---|---|---|
| All six v1 routes live | 6 of 6 | Manual checklist |
| All four Cedarfell users + 2 Tensor users can sign in | 6 of 6 | Roster test pass |
| Daily BC Dashboard renders inside portal | Yes | Vincent + Brandon validate morning of |
| Average page load time | < 2 seconds | Cloudflare Pages analytics |
| BC daily usage | ≥ 5 portal visits per BC per week within 30 days | Cloudflare Access analytics |
Portal v1 is “successful” 30 days after launch if BC usage hits target. If not, the friction is investigated (not the portal abandoned) — usage gap is a signal that something specific in the portal isn’t earning the bookmark.
Cross-references
- WF-6.4 Scorecard Data Acquisition Plan —
/VD6 - Financial Operations/Workflows/ - WF-6.7 Warehouse Engine Decision —
/VD6 - Financial Operations/Workflows/(data layer) - WF-6.8 Gold View Map —
/VD6 - Financial Operations/Workflows/(gold views Metabase queries) - WF-6.9 Dashboard Tool Decision —
/VD6 - Financial Operations/Workflows/(Metabase Cloud) - WF-6.10 Dashboard Layer Plan (proposed) — per-dashboard scope for embeds
- WF-6.11 Website Infrastructure Decision —
/VD6 - Financial Operations/Workflows/(stack) - WF-6.6 Dashboard Operations SOP (proposed) — operational governance once live
- KPI Taxonomy v1.1 —
/Strategic/ - Decision Catalogue —
/VD6 - Financial Operations/Data/ - Scorecard Templates v1.0 —
/Templates/ - Decisions-Log.md — entry dated 2026-05-13 for this plan
Change log
| Version | Date | Author | Change |
|---|---|---|---|
| 1.0 | 2026-05-13 | Roger Thompson (with Claude) | Initial plan. Six v1 routes, four-phase build sequence, DNS migration plan from Google Cloud DNS to Cloudflare, Squarespace cutover Option 2 (defer external site), per-CSA replication recipe documented. Roster confirmed: Roger / Vincent / Brandon / Aaliyah at @cedarfell-logistics.com; Jana + Sheryl via Cloudflare Access exception list. |
Appendix A — File and folder layout
cedarfell-portal/ (new repo)
├── astro.config.mjs site config
├── tailwind.config.cjs styling
├── package.json
├── scripts/
│ ├── sync-content.mjs pulls /VD*/Workflows/*.md
│ ├── parse-kpis.mjs KPI markdown → JSON
│ └── parse-decisions.mjs Decisions-Log → JSON
├── public/
│ ├── favicon.svg
│ └── logo.svg
├── src/
│ ├── content/
│ │ ├── config.ts Zod schemas
│ │ ├── sops/ copied from Cowork at build
│ │ └── handbook/ handbook pages
│ ├── data/
│ │ ├── kpis.json generated at build
│ │ └── decisions.json generated at build
│ ├── layouts/
│ │ └── Portal.astro shared shell
│ ├── components/
│ │ ├── Nav.astro
│ │ └── Footer.astro
│ └── pages/
│ ├── index.astro /
│ ├── dashboards/
│ │ ├── index.astro /dashboards
│ │ ├── daily-bc.astro
│ │ ├── daily-owner.astro
│ │ ├── weekly.astro
│ │ ├── monthly-scorecard.astro
│ │ ├── monthly-pl.astro
│ │ ├── quarterly.astro
│ │ └── annual.astro
│ ├── sops/
│ │ ├── index.astro /sops
│ │ └── [...slug].astro /sops/wf-4-4 etc.
│ ├── kpis/
│ │ ├── index.astro /kpis
│ │ └── [id].astro /kpis/OP.VD4.10 etc.
│ ├── decisions/
│ │ ├── index.astro /decisions
│ │ └── [date]/[slug].astro
│ └── handbook/
│ ├── index.astro /handbook
│ └── [...slug].astro
└── README.md
Appendix B — Cloudflare Access policy reference
For copy-paste into the Cloudflare Zero Trust dashboard:
Application configuration:
- Application name:
Cedarfell Portal - Application type: Self-hosted
- Application domain:
portal.cedarfell-logistics.com - Session duration: 24 hours
- Cookie settings: HTTP only, Secure, SameSite=Lax
- App launcher visibility: Show (so users can find it from the Cloudflare Zero Trust launcher)
Policy configuration:
- Policy name:
Cedarfell team + Tensor bookkeepers - Action: Allow
- Session duration: same as application
- Include:
- Emails ending in:
@cedarfell-logistics.com - Emails (exact):
<jana>@<tensor-domain>,<sheryl>@<tensor-domain>
- Emails ending in:
- Require: (none for v1)
- Exclude: (none)
Identity provider:
- Type: Google
- Authorize against:
cedarfell-logistics.comGoogle directory - Refresh interval: default (1 hour)
Appendix C — Day-by-day plan
The four phases mapped to elapsed days:
| Day | Phase | Roger actions | Claude actions | Vincent/Brandon actions |
|---|---|---|---|---|
| 1 | A scaffold | Cloudflare signup, GitHub repo, Pages project, Access policy, first test | Pair through scaffold, write initial Astro config | Test sign-in once policy live |
| 2 | B SOPs | Add YAML front matter to 3 SOPs, push | Build content collection, sync script, list + detail pages | Click through /sops, confirm rendering |
| 3 | C dashboard | Start Metabase trial, build Daily BC dashboard in Metabase | Add iframe page to portal, push | (none — Metabase building) |
| 4 | C dashboard | Validate Daily BC dashboard data with Vincent + Brandon | Refine iframe layout, mobile check | Spend a real morning using it |
| 5 | D expand | Add front matter to remaining SOPs | Build /kpis, /decisions, /handbook, / | Spot-check all routes |
| 6 | D expand | Pre-DNS-migration: lower TTLs in Google Cloud DNS | Final mobile and accessibility review | (none) |
| 7 | D migrate | Migrate nameservers to Cloudflare, monitor propagation, decommission test path | Verify all routes work post-migration | (none — propagation hours) |
Beyond Day 7: WF-6.6 Dashboard Operations SOP defines the ongoing governance model (who edits what, how changes flow, refresh policies).