Runbook
Procedures, checklists, onboarding, secret rotation, incident response
Quick links
- Onboarding a new admin
- Secret rotation
- Incident response playbooks
- Pricing pipeline procedures (ADR-012)
- Day-to-day procedures (Deploy, Sync Troubleshooting, D1 Ops, Secrets, Emergency Recovery)
- Cadenced checklists (Daily / Weekly / Monthly / Quarterly / Annual)
- NetSuite navigation map
- Admin SuiteQL queries — see data-model → SuiteQL library
Onboarding a new admin
End-to-end provisioning from "this person exists" to "they can run sync, query D1, and deploy the Worker". Update the Decisions log (ADR) with the new admin's name and starting scope.
Provisioning a new admin from scratch
- Confirm the new admin's identity and need-to-know scope. Decide whether they should have NS Administrator or a scoped custom role (Controller / Sales & Ops Mgr etc).
- In NetSuite: Setup → Users/Roles → Manage Roles. Pick or create the role. Assign permissions per least-privilege — never copy Administrator wholesale.
- In NetSuite: Setup → Users/Roles → Manage Users → New. Set name, email, password reset link. Assign roles. Set Subsidiary = GFS, Default Role = the one above.
- In Cloudflare: dash.cloudflare.com → Manage Account → Members → Invite. Send invite with the minimum role needed (Reader for view-only, otherwise narrower built-in roles).
- Local tools (only if the new admin will run sync): install wrangler (npm i -g wrangler), run wrangler login on their machine, confirm they appear in wrangler whoami.
- Chartstone Pro: hand off the localhost API key. Set CHARTSTONE_SECRET in their ~/.zshenv. Verify with curl http://127.0.0.1:56411/health on their machine.
- Bookmarks: send this admin guide URL, GAPS_TO_CLOSE, and the Cloudflare dashboard link. Tag them in the Decisions log (decisions.html) so they know where decisions are recorded.
- Knowledge handoff: 30-min walk-through of admin-dashboard, runbook (this page), and the daily/weekly checklists below. Confirm they can run a D1 query via wrangler.
- Update the Personnel section of this runbook with the new admin's name and scope. Record the date and starting permissions in the Decisions log (ADR).
Secret rotation
Cadenced rotation of every Bearer token and API key. Record each rotation in the Decisions log so an audit trail exists.
API_KEY (Worker) — rotate Quarterly + on suspected leak
- Generate new key: openssl rand -hex 32
- wrangler secret put API_KEY (paste new value)
- Update any consuming clients (admin-dashboard checks reload automatically)
- Test: curl -H 'Authorization: Bearer $NEW' https://api.ai-globalfoodsolutions.co/api/kpis
- Record rotation in Decisions log with date and operator
CHARTSTONE_SECRET (local) — rotate Quarterly + on machine change
- Generate in Chartstone Pro UI → API → New Token
- Edit ~/.zshenv: replace the CHARTSTONE_SECRET= line
- Reload shell: source ~/.zshenv
- Test: ~/Desktop/gfs-platform/sync.sh manual run; tail -20 sync.log
- Record rotation in Decisions log
NetSuite TBA tokens — rotate Annually + on integration removal
- In NS: Setup → Users/Roles → Manage Authentication Tokens
- Identify active tokens (SuiteAttach id 11, Analytics Warehouse id 2)
- Create new token under the SAME integration record
- Update the consuming system with the new TOKEN_ID + TOKEN_SECRET
- Test the integration
- Revoke old token only after the new one is confirmed working
- Record rotation in Decisions log
Cloudflare API token (if used) — rotate Quarterly
- dash.cloudflare.com → My Profile → API Tokens
- Create new token with minimum scope (zone:read, worker:edit, pages:edit, d1:edit, etc — narrow as possible)
- Update local ~/.cloudflare.toml or env
- Revoke old token
Incident response playbooks
"X is broken — do these steps." Each playbook is independent and short enough to execute under pressure.
Sync stopped (D1 stale)
- Check sync.log: tail -50 ~/Desktop/gfs-platform/sync.log
- If localhost connection refused: open Chartstone Pro and verify it's running
- If 'Operation not permitted': run xattr -d com.apple.provenance sync.sh; re-launch via launchd
- If Bearer rejected: rotate CHARTSTONE_SECRET (see secret rotation)
- If wrangler errors: wrangler login; wrangler whoami
- Manual catch-up: run sync.sh from terminal; observe stdout
- Verify in D1: wrangler d1 execute gfs-netsuite --remote --command 'SELECT MAX(lastmodifieddate) FROM transactions'
D1 quota or write errors
- Check D1 size: Cloudflare dashboard → Workers & Pages → D1 → gfs-netsuite → Metrics
- If over 10GB: identify largest tables — usually transactions or invoice_lines
- Archive: DELETE FROM transactions WHERE year < 2020 (after exporting to R2 via wrangler r2 object put)
- If write rate-limited: pause sync.sh temporarily (sudo launchctl unload ~/Library/LaunchAgents/gfs-sync.plist)
- Resume: sudo launchctl load ~/Library/LaunchAgents/gfs-sync.plist
API_KEY leaked or suspected leak
- Immediately rotate: wrangler secret put API_KEY (new value)
- wrangler tail — watch for requests with the old key (will start 401-ing)
- Audit access logs in Cloudflare dashboard → Workers → gfs-platform → Logs
- Notify anyone who legitimately had the old key with the new one
- Record incident in Decisions log with timeline
Cloudflare Pages site down
- Check status: https://www.cloudflarestatus.com
- If global Pages issue: wait, no action
- If specific to gfs-netsuite project: dash.cloudflare.com → Workers & Pages → gfs-netsuite → Deployments → Roll back to previous
- If build failed: check deploy logs, fix locally, re-run wrangler pages deploy
NetSuite unavailable
- Check status.netsuite.com
- If NS down: sync.sh will fail silently (Chartstone returns empty results). D1 keeps the last-known data.
- Admin dashboard data quality grades remain valid; live KPIs (when implemented) will show 'stale'
- No action until NS is restored. After restoration, sync.sh catches up on its next 15-min run.
Period close blocked
- Run the period-close SuiteQL admin query (see data-model → SuiteQL admin library → Period close)
- Identify unposted transactions in the period
- Either post them OR move them to a different period after verifying impact
- Re-run close from NS UI: Setup → Accounting → Manage Accounting Periods → Lock the period
Suitelet 500 error (public broker portal)
- Check the Suitelet's script execution log in NS: Customization → Scripting → Script Execution Log
- Identify the error — usually a query that timed out or a NULL where a value was expected
- If a downstream consumer broke (a broker missing data), notify the broker contact
- Fix in the Suitelet code (File Cabinet folder 717413), redeploy
- If urgent: take the Suitelet offline (uncheck isonline=T) until fixed
Pricing pipeline procedures
Operating the customer pricing derive → review → push workflow. Reference: ADR-012, pricing roadmap, proposed schema.
Run the pricing derive job (rebuild proposed price list)
- Confirm last derive: GET /api/pricing/audit/summary → check last_derived_at
- Trigger fresh derive: GET /api/pricing/derive (Bearer auth, ~5–10 sec)
- Or via SQL direct: wrangler d1 execute gfs-netsuite --remote --file=migrations/002_run_derive.sql
- Verify: GET /api/pricing/audit/summary → derived_count should match expected (~600–800 for current data)
- Spot-check: GET /api/pricing/customer/539 (Driscoll Foods) → confirm proposed_count, sample row deltas
Review the three-bucket pricing diff
- Pull summary: GET /api/pricing/diff (all customers) or /api/pricing/diff?customer=:id (single)
- Listed+Recent: rows that exist in NS + were recently invoiced — drift check (sort by ABS(delta_pct) DESC)
- Listed+Stale: rows in NS but no recent invoice — candidates to deprecate (customer hasn't bought in 18+ months)
- Not-Listed+Recent: customer is buying but not on NS price list — biggest gap to close, captures negotiated SO-time prices
- Cross-reference with open SOs: any in-flight SOs for these customers may be affected at cutover
Identify open SOs that would be affected by a pricing cutover
- wrangler d1 execute gfs-netsuite --remote --command "SELECT entity_name, COUNT(*) AS sos, SUM(foreigntotal) AS total FROM transactions WHERE type='SalesOrd' AND status IN ('A','B','D','E','F') GROUP BY entity_name ORDER BY sos DESC"
- Currently 90 open SOs across 33 customers — these would need to either re-price or invoice on old prices
- Phase 4 will surface this list per customer before pushing to NS
- Decision: per-batch, either re-price open SOs (customer notification + admin update) or let them invoice on existing prices (cutover applies to SOs created after timestamp)
Push approved prices back to NS (Phase 4 — gated)
- Phase 4 not yet implemented. Steps below describe planned procedure.
- Pre-flight: confirm SuiteAPI RESTlet endpoint live + Bearer token rotated
- Pilot on 3 customers first: POST /api/pricing/push-to-ns with {customer_ids: [...]}
- Verify in NS UI: Customer → Item Prices → confirm rows replaced
- Create next-day SO and confirm pricing pulls from the new entries
- Bulk: push remaining approved rows in batches of 10–20 customers
- Each push writes a row to customer_pricing_history for audit
- Rollback if needed: customer_pricing_history holds the prior price; restore via SuiteAPI
Flag and resolve pricing anomalies (Phase 6 — gated)
- Phase 6 not yet implemented. Steps below describe planned procedure.
- AI review pass populates pricing_anomalies with severity (low/medium/high)
- Anomaly types: drift (proposed differs from current > threshold), peer_outlier (customer paying outside their program band), one_off_suspicious (last_paid below median by > 15%), stale (no order in 12+ months)
- Review queue: GET /api/pricing/anomalies?resolved=0
- Resolve: mark resolved=1 with note (accepted, investigated, corrected, deferred)
Day-to-day procedures
Recovered from the original ops dashboard. Each is a stepwise procedure for a recurring admin task.
Deploy Worker Changes
- Edit source:
- ~/Desktop/gfs-platform/src/index.ts
- Test locally:
- cd ~/Desktop/gfs-platform && npm run dev
- Opens local dev server at localhost:8787. Test endpoints.
- Deploy:
- npm run deploy
- Runs
- wrangler deploy
- . Deploys to gfs-platform.mikelevine.workers.dev
- Verify:
- Hit /api/health and one auth endpoint to confirm
Deploy Dashboard to Pages
- Prepare deploy folder:
- rm -rf /tmp/gfs-deploy && mkdir -p /tmp/gfs-deploy/docs /tmp/gfs-deploy/diagrams
- Copy files:
- cp guide/*.html guide/*.css /tmp/gfs-deploy/ && cp guide/diagrams/*.html /tmp/gfs-deploy/diagrams/ && cp dashboard/index.html /tmp/gfs-deploy/command-center.html && cp docs/04-Power-Tools.html /tmp/gfs-deploy/docs/
- Deploy:
- wrangler pages deploy /tmp/gfs-deploy --project-name gfs-system-guide
- Verify:
- Check gfs-system-guide.pages.dev
Sync Troubleshooting
- Sync not running
- Check launchd:
- launchctl list | grep gfs
- Should show two entries (sync every 900s, report at 7pm).
- Check Chartstone:
- Open Chartstone Pro, verify it's running on port 56411
- curl -s http://127.0.0.1:56411/health
- — should return 200.
- Check Full Disk Access:
- System Settings → Privacy → Full Disk Access → Terminal.app
- macOS Sequoia blocks launchd scripts without this.
- Check wrangler auth:
- wrangler whoami
- Should show mikelevine@gfs account. Re-login:
- wrangler login
- Check sync log:
- tail -20 ~/Desktop/gfs-platform/sync.log
- Sync running but no data
- Check Chartstone secret:
- grep CHARTSTONE_SECRET ~/.zshenv
- Test query manually:
- Run the SuiteQL from sync.sh against Chartstone directly
- Check D1 write:
- wrangler d1 execute gfs-netsuite --remote --command="SELECT COUNT(*) FROM sync_log"
D1 Database Operations
- Query D1 directly
- wrangler d1 execute gfs-netsuite --remote --command="YOUR SQL HERE"
- Backup D1 (manual export)
- wrangler d1 execute gfs-netsuite --remote --command="SELECT * FROM customers" --json > backup_customers.json
- Repeat for each table. No native D1 full-export yet. Automate with a shell script.
- Reload a table from SQL loader
- wrangler d1 execute gfs-netsuite --remote --file=sql/01_customers.sql
- Loader files use INSERT OR REPLACE. Safe to re-run.
- Check database size
- Current:
- ~28.3 MB of 10 GB limit (0.3% used). At current growth, years of runway.
- Monitor quarterly. D1 limit is 10 GB per database.
Secrets & Credentials
- Worker API_KEY
- Set via: wrangler secret put API_KEY
- Chartstone secret
- In ~/.zshenv as CHARTSTONE_SECRET
- Wrangler auth
- OAuth token — wrangler login to refresh
- NS credentials
- In Chartstone Pro config
- CF account
- mikelevine — dashboard.cloudflare.com
- NS account
- 4656898 — system.netsuite.com
- Rotation schedule
- API_KEY:
- Rotate quarterly. Update Worker secret + dashboard sessionStorage clears automatically.
- Chartstone:
- Rotate if laptop changes. Update ~/.zshenv.
- NS password:
- Per company policy. Update Chartstone config after change.
Emergency Recovery
- Worker down
- Check CF status:
- cloudflarestatus.com — verify no platform outage
- Check Worker logs:
- CF Dashboard → Workers → gfs-platform → Logs
- Redeploy:
- cd ~/Desktop/gfs-platform && npm run deploy
- D1 data corruption
- Reload from SQL loaders:
- Files in sql/ directory are the original full dataset
- Incremental data:
- entity backfill, GL accounts, vb_lines added post-load would need re-sync
- Laptop failure (total loss)
- Worker + D1 + KV + R2 are in Cloudflare
- — survive laptop loss
- Source code:
- once git pushed to remote, recoverable
- Until then: this is the single copy. git init + remote push is critical.
- Sync will stop
- — sync.sh runs on laptop. Server-side sync (Phase 3) eliminates this risk.
Cadenced checklists
Daily / weekly / monthly / quarterly / annual reviews. Recovered from the original ops dashboard.
Daily — Morning (7:00 – 9:00 AM) (20 items)
- Review KPIs on Command Center
- Open this dashboard → Dashboard tab. Check: Revenue YTD pace, Open AR total (flag if > $2.5M), Open SO count. Takes 2 min.
- Check sync health
- System tab → Sync Log. Verify last sync was
- 1 hour, check launchd:
- launchctl list | grep gfs
- Review new orders overnight
- NS → Transactions → Sales → Sales Order List. Filter: Date = Today, Status = Pending Fulfillment. Route to warehouse.
- Check overdue AR
- Collections tab → Aging table. Any new entries in 61-90 or 90+ columns = escalation needed. Flag for afternoon call.
- Process pending POs
- NS → Transactions → Purchases → Purchase Order List. Filter: Status = Pending Receipt. Verify with warehouse on expected deliveries.
- Check email for vendor confirmations
- Match PO confirmations to open POs. Update expected receipt dates if changed.
Daily — End of Day (4:00 – 5:00 PM) (15 items)
- Confirm all shipments processed
- NS → Transactions → Sales → Item Fulfillment List. Filter: Date = Today. Verify tracking numbers entered.
- Invoice shipped orders
- NS → Transactions → Sales → Invoice List. Create invoices for all fulfilled SOs. Verify terms match customer record.
- Apply received payments
- NS → Transactions → Customers → Accept Payment. Match checks/ACH/wire to open invoices.
- Bank deposit if applicable
- NS → Transactions → Bank → Make Deposits. Group payments received today.
- Review daily report at 7pm
- Auto-generated by daily-report.sh (email delivery coming in Phase 3). Compare to morning KPIs.
Weekly — Monday (18 items)
- Full AR aging review
- Collections tab or NS → Reports → Financial → A/R Aging. Identify accounts for escalation. Send reminder emails for 30+ days. Owner: Amanda S.
- Vendor bill reconciliation
- NS → Reports → Payables → Open Vendor Bills. Match against POs and receipts. Flag discrepancies. Owner: Amanda S.
- Inventory spot check
- NS → Reports → Inventory → Inventory Valuation. Check negative items list. Review pending WOs (currently 0). Owner: Elena M.
- CME pricing review
- Check CME block/barrel prices. Update Bongards formula calc (trailing week avg + 35% moisture). Notify affected customers of changes. Owner: Michael L.
- Sales pipeline review
- Revenue tab → Customer Ranking. Compare to prior week. Review open SOs > $10K. Update forecast. Owner: Sales team.
- Open PO follow-up
- NS → Transactions → Purchases → PO List. Filter: overdue POs. Contact vendors for ETA. Owner: Buyer.
Weekly — Friday (9 items)
- Week-end revenue snapshot
- Dashboard → Revenue YTD. Compare to last week's number. Calculate weekly run rate vs $29M annual pace.
- Prepare next week's ship schedule
- NS → Sales Order List. Filter: Ship Date = next week. Ensure warehouse capacity. Flag large orders.
- Vendor payment run
- NS → Transactions → Payables → Pay Bills. Select bills due this week. Process payment batch.
Monthly Close — By 10th of Following Month (69 items)
- Phase 1: Days 1-3 — Transaction Cleanup
- Final invoicing
- — invoice ALL fulfilled SOs from prior month
- Run saved search: Fulfilled SOs with no invoice. Create invoices. Zero should remain.
- Apply all payments
- — zero unapplied payments
- NS → Customers → Unapplied Payments. Match to invoices or create on-account credit.
- Bank reconciliation
- — match NS to bank statement
- NS → Transactions → Bank → Reconcile. All cleared items should match. Investigate discrepancies.
- Credit card reconciliation
- — verify all charges posted
- Phase 2: Days 3-5 — Accruals & Adjustments
- AP accruals
- — accrue for received-not-billed items
- Create JE: Debit expense, Credit accrued AP. Reverse in new period.
- Prepaid expense amortization
- — recognize monthly portions
- Inventory adjustments
- — resolve negative items, write-offs
- NS → Transactions → Inventory → Adjust Inventory. Document reason for each adjustment.
- Rebate accruals
- — estimate earned rebates by customer
- Calculate based on volume tiers. JE: Debit rebate expense, Credit accrued rebate liability.
- Phase 3: Days 5-7 — Revenue & Review
- Revenue recognition review
- — verify all revenue properly recorded
- Match invoice totals to revenue GL accounts. Investigate variances.
- Intercompany/related party entries
- — if applicable
- Review all journal entries
- — verify proper documentation
- NS → Reports → Financial → General Ledger. Filter: JE type, current period.
- Phase 4: Days 7-9 — Reports & Verification
- Trial balance review
- — all accounts balanced
- NS → Reports → Financial → Trial Balance. Compare to prior month. Investigate swings > 10%.
- P&L review
- — revenue, COGS, gross margin, operating expenses
- Compare to budget. Flag variances. Prepare management commentary.
- Balance sheet review
- — assets, liabilities, equity tie-out
- Cash flow statement
- — verify operating, investing, financing
- Phase 5: Day 10 — Close
- Close accounting period
- Setup → Accounting → Manage Accounting Periods → select month → Close All. Prevents further posting.
- Distribute management reports
- — P&L, Balance Sheet, AR Aging
- Update forecast
- — adjust annual projection based on actuals
Quarterly (16 items)
- Customer tier review
- — reclassify by volume
- Compare YTD volume to tier thresholds. Update pricing levels. Notify sales of changes.
- Vendor performance review
- — on-time delivery, quality, pricing
- Procurement tab → Vendor Spend. Compare to prior quarter. Flag underperformers.
- 1099 vendor review
- — verify eligibility and tax IDs
- NS → Lists → Relationships → Vendors. Filter: Is 1099 Eligible = Yes. Verify W-9 on file.
- Saved search cleanup
- — archive unused, consolidate duplicates
- 968 searches exist. ~300 are TAF/SII/Intrastat from unused bundles. ~20 are dead Zapier/Airtable.
Annual (16 items)
- 1099 filing
- — generate and file by January 31
- NS → Reports → Financial → 1099 Report. Verify all eligible vendors have tax ID.
- Customer contract renewal review
- Identify all contracts expiring in next 90 days. Schedule renewal discussions. Update pricing.
- NS role and access audit
- 97 roles, 116 employees. Verify terminated employees disabled. Review admin access (currently concentrated on 1 user).
- Budget preparation
- — build next year's budget in NS
- Use Revenue tab data + vendor spend analysis for informed projections.
- Data Quality Dashboard
- — Every entity type graded A through F based on field completeness. Each F-grade item has an assigned owner and remediation plan. Data quality directly impacts every workflow — bad data = broken processes.
Source
- data/runbook-source.json — procedures + checklists extracted from the original ops dashboard
- data/ns-navigation.json — NetSuite navigation map
- Onboarding, secret rotation, incident response playbooks authored in this build pass
Related surfaces
Admin dashboard
Live health, sync status, risk register, known issues, pricing pipeline status.
Infrastructure
Worker endpoints (per-endpoint detail), cron, bindings, secrets map, DNS records.
Decision log
11 ADRs covering platform architecture, AI stack, pricing pipeline.
Gaps to close
Open-items tracker across 4 tiers — Tier 1 critical includes Access on Pages.
Data model
D1 schema, ER diagram, FK relationships, example queries, SuiteQL admin library.