toga-ai 1.0.324 → 1.0.326

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@
9
9
  | [Assortment Name Translation (AssortmentTranslations sidecar)](features/assortment-name-translation.md) | Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language **sidecar** table `AssortmentTranslations`, reusing the platform's | _underscore/Model/Client/AssortmentTranslation.php, dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql, dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql, dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql |
10
10
  | [Asynchronous Query Execution (writes-only, via Worker)](features/async-query-execution.md) | `_Query` can run a **write** query asynchronously so a long/slow write does not hold a request-scoped DB connection open long enough to hit **"MySQL server has | _underscore/Query.php, worker2/Worker/Infrastructure/Database.php, worker2/Worker/Team/Transcripts.php |
11
11
  | [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/ShipmentRequest/ShipmentRequest.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
12
+ | [_Cloud S3 helpers (copy / get / delete / list)](features/cloud-s3-helpers.md) | `_Cloud` centralizes AWS SDK S3 usage for the 2.0 stack so the `S3Client` never leaks into workers or app code. | _underscore/Cloud.php |
12
13
  | [_Component_*/_Model_* project-namespace registration (autoloader) & backslash-qualify traps](features/component-model-namespace-registration.md) | Every **project-local** `_Component_*` and `_Model_*` class in a 2.0 app **must declare the project namespace** at the top of the file: ```php namespace <NAMESP | _underscore/Loader.php, worker2/_.php, api2/_.php, worker2/Component/Forecast/Db/Db.php, worker2/Component/Forecast/SaleImport/SaleImport.php, api2/Component/Api/Netsuite/Netsuite.php |
13
14
  | [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
14
15
  | [Error Reporting — Issue/Event Aggregation (agreed POST-to-receiver design)](features/error-reporting-issue-event.md) | Platform-wide error-reporting infrastructure for TOGA 2.0, built around a two-table **Issue / Event** aggregation model in the shared **Core Logs DB**. | _underscore/Error.php, _underscore/Model/Core/Logs/Issue.php, _underscore/Model/Core/Logs/Event.php, dbchanges2/Logs/2026-07-06 - Issue and Event tables.sql |
@@ -0,0 +1,54 @@
1
+ ---
2
+ title: _Cloud S3 helpers (copy / get / delete / list)
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-07-13
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - _underscore/Cloud.php
13
+ related:
14
+ - ../../worker2/features/oneuptime-worker2-monitoring.md
15
+ ---
16
+
17
+ ## Summary
18
+
19
+ `_Cloud` centralizes AWS SDK S3 usage for the 2.0 stack so the `S3Client` never leaks into
20
+ workers or app code. Alongside the existing copy / get / delete methods it now has a
21
+ **LIST** method, `_Cloud::getS3Objects()`.
22
+
23
+ ## Key files / entry points
24
+
25
+ - `_underscore/Cloud.php` — `_Cloud::getS3Objects($s3BucketName, $s3BucketPath = '')`.
26
+
27
+ ## How it works
28
+
29
+ `getS3Objects()` uses the AWS SDK `S3Client->listObjectsV2` and **transparently paginates**
30
+ (`listObjectsV2` caps at 1000 keys per page), returning entries shaped:
31
+
32
+ - `Key` — object key
33
+ - `LastModified` — ISO-8601 string
34
+ - `Size`
35
+
36
+ The method is **purely additive** — no existing `_Cloud` method signature changed. Keeping
37
+ S3 listing here (rather than instantiating `S3Client` in a worker) is the reason to prefer
38
+ this helper over ad-hoc SDK calls.
39
+
40
+ First consumer: the worker2 Office Depot EDI backlog monitor — see
41
+ [OneUptime push-metric monitors](../../worker2/features/oneuptime-worker2-monitoring.md).
42
+
43
+ ## Client variations
44
+
45
+ None — shared core helper.
46
+
47
+ ## Gotchas / known issues
48
+
49
+ - Callers get every matching key regardless of count; pagination is handled internally, so
50
+ do not add your own `ContinuationToken` loop on top.
51
+
52
+ ## Change history
53
+ - 2026-07-13 — Added `_Cloud::getS3Objects()` S3 LIST helper (paginated `listObjectsV2`,
54
+ returns `{Key, LastModified, Size}`); additive, no existing signatures changed. (jcardinal)
@@ -20,6 +20,7 @@
20
20
  | [NetSuite Supporting-Record Webhook Importer (the reusable recipe)](features/netsuite-supporting-record-webhook-importer.md) | A single **repeatable recipe** for porting a legacy daily-pull NetSuite *supporting-record* importer (the lookup/dimension tables behind Forecast2 — Employees, | worker2/Worker/Netsuite/Employee.php, worker2/Worker/Netsuite/Account.php, worker2/Worker/Netsuite/Classification.php, worker2/Worker/Netsuite/Customer.php, worker2/Worker/Netsuite/Item.php, worker2/Worker/Netsuite.php, _underscore/Model/Forecast/Employee.php, _underscore/Model/Forecast/Account.php, _underscore/Model/Forecast/Classification.php, _underscore/Component/Forecast/Db/Db.php, test/@dave/test_employee_lifecycle.php, test/@dave/test_account_lifecycle.php, test/@dave/test_classification_lifecycle.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, worker/crons/toga2/forecast2/import_supporting_records.php |
21
21
  | [Background Email-Template Worker (_Worker_Notification_EmailTemplate)](features/notification-email-template.md) | `_Worker_Notification_EmailTemplate::Send(...)` dispatches a **stored, client-defined `EmailTemplates` row off-thread** as a background WorkerJob. | worker2/Worker/Notification/EmailTemplate.php, worker2/Worker/Client/True.php, _underscore/Model/Client/EmailTemplate.php |
22
22
  | [DB-Driven Notification (Internal) Email](features/notification-email.md) | Internal/notification emails (merge-conflict alerts, ops notices — anything system-generated, not client-facing transactional mail) are sent through one worker | worker2/Worker/Notification/Email.php, _underscore/Model/Client/EmailTemplate.php, dbchanges2/Client/2026-06-23a - EmailTemplateWrapper.sql, dbchanges2/Client_True/2026-06-23a - EmailTemplateWrapper.sql |
23
+ | [OneUptime push-metric monitors for 2.0 workers](features/oneuptime-worker2-monitoring.md) | A second, **OneUptime-reporting** monitoring pattern for the 2.0 worker2 tier, ported from the 1.0 `App_SystemMonitor_Compass` monitors. | worker2/Worker/Monitor/Compass.php, _underscore/Cloud.php |
23
24
  | [Startech Webhook Handler (worker2)](features/startech-webhook-handler.md) | Receives inbound webhook events from Startech (Easeedesk) and creates or updates the corresponding ticket in TOGA 2.0. | worker2/Worker/Startech.php |
24
25
  | [Talos (TOGa IQ) Meeting-Notes Integration & Token Auto-Refresh (consumer)](features/talos-meeting-notes-integration.md) | How a **dev tool / agent consumes Talos (TOGa IQ)** to query the team meeting-notes corpus programmatically. | .claude/skills/plan-ticket/scripts/talos.js |
25
26
  | [Talos Pricing Automation (worker2 Cron — AWS Actuals, Calibration, Monthly Report)](features/talos-pricing-automation.md) | The worker2 half of the **Talos Pricing Platform** (see the talos `pricing-cogs-model` and tools `talos-pricing-ui` docs for the other halves). | worker2/Worker/Talos/Pricing.php, worker2/Database/TalosPricingCrons.sql |
@@ -0,0 +1,128 @@
1
+ ---
2
+ title: OneUptime push-metric monitors for 2.0 workers
3
+ framework: "2.0"
4
+ repo: worker2
5
+ project: Worker
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-07-13
10
+ owners: ["jcardinal"]
11
+ files:
12
+ - worker2/Worker/Monitor/Compass.php
13
+ - _underscore/Cloud.php
14
+ related:
15
+ - ./monitoring-framework.md
16
+ - ../../_underscore/features/cloud-s3-helpers.md
17
+ - ../../../1.0/apps/worker/features/oneuptime-worker-uptime-monitoring.md
18
+ ---
19
+
20
+ ## Summary
21
+
22
+ A second, **OneUptime-reporting** monitoring pattern for the 2.0 worker2 tier, ported from
23
+ the 1.0 `App_SystemMonitor_Compass` monitors. A worker2 cron action runs a self-contained
24
+ check, decides pass/fail itself, and POSTs a JSON metric body to a OneUptime "Incoming
25
+ Request" monitor — replacing the 1.0 tier's email alerts. First monitor:
26
+ `_Worker_Monitor_Compass::OfficeDepotEdiImportQueue()`, which watches the Office Depot
27
+ inbound EDI import backlog. Designed to be **client-agnostic** — a reusable template for
28
+ future monitors and clients.
29
+
30
+ This is distinct from the DB-driven email-orchestrator
31
+ [Monitoring Framework](./monitoring-framework.md) (`_Worker_Monitor` orchestrator +
32
+ `_Worker_Monitors_<Name>` children + `Core.Monitors` table, sends email). This pattern has
33
+ **no orchestrator and no `Core.Monitors` table** — each monitor is a plain worker2 action
34
+ that reports straight to OneUptime. Note the folder difference: these live under
35
+ `Worker/Monitor/` (singular) vs. the framework's `Worker/Monitors/` (plural).
36
+
37
+ ## Key files / entry points
38
+
39
+ - `worker2/Worker/Monitor/Compass.php` — `abstract class _Worker_Monitor_Compass`; each
40
+ monitor is a `public static` action method returning a summary string recorded in
41
+ `WorkerJobs` (standard worker2 action conventions). First method:
42
+ `OfficeDepotEdiImportQueue()`.
43
+ - `_underscore/Cloud.php` — `_Cloud::getS3Objects()` list helper this monitor relies on
44
+ (see [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md)).
45
+
46
+ ## How it works
47
+
48
+ Registered as a `Core.CronJobs` entry: schedule `*/5 6-20 * * *`, action
49
+ `Monitor/Compass/OfficeDepotEdiImportQueue`. Each tick, `OfficeDepotEdiImportQueue()`:
50
+
51
+ 1. Lists `s3://agilant-as2/OfficeDepot/` via `_Cloud::getS3Objects()`, then counts files
52
+ older than `AGED_FILE_MINUTES` (10), **excluding** the `OUTBOX/` and `SENT/` sub-prefixes
53
+ and directory-placeholder keys.
54
+ 2. Decides the alarm state itself and POSTs a JSON metric body to the OneUptime "Incoming
55
+ Request" monitor via `_ApiRequest` — with **logging disabled** and
56
+ **`throwExceptionsOnFailure` disabled**, so a failed ping never fails the worker job.
57
+ 3. On S3 failure it still POSTs `{"status":"error"}` so a blind/dead checker is
58
+ distinguishable from a real backlog.
59
+
60
+ Body tokens (see the alarm contract below): `"alarm":"HIGH"` when the aged-file count is
61
+ over threshold, else `"alarm":"OK"`; `"status":"error"` on S3 failure.
62
+
63
+ ### The token-based alarm contract (critical — OneUptime cannot compare numbers)
64
+
65
+ OneUptime **Incoming Request** monitors **cannot** do numeric threshold comparison on a
66
+ pushed body. Verified in OneUptime source (`Common/Server/.../IncomingRequestCriteria.ts`):
67
+ a `checkOn: "Request Body"` filter supports only `Contains` / `NotContains` **string**
68
+ matching — it does **not** JSON-parse the body, does **not** target a nested key, and does
69
+ **not** support Greater Than / Less Than (those filter types apply only to
70
+ outgoing/synthetic checks like Response Time / Status Code / Metric Value).
71
+
72
+ Consequence: the "dumb reporter, smart monitor" ideal (push a raw number, let OneUptime
73
+ compare `> threshold`) is **not achievable for push monitors**. Instead the **worker makes
74
+ the threshold decision** and emits a string token that OneUptime matches with `Contains`.
75
+ OneUptime criteria for this monitor:
76
+
77
+ - Request Body **Contains** `"alarm":"HIGH"` → Offline + incident.
78
+ - Request Body **Contains** `"status":"error"` → Offline + incident.
79
+ - **Online** requires BOTH "received in 1 min" AND Request Body **Contains** `"alarm":"OK"`
80
+ — so status does not flap back to Operational while the alarm is still HIGH.
81
+
82
+ ### Heartbeat / cron cadence timing
83
+
84
+ Heartbeat missed-ping thresholds must match the cron cadence. With a **5-minute** push
85
+ cadence: **Degraded at 10 min / Offline at 15 min**. An initial 3/5-min setting
86
+ false-alarmed on a single missed ping. The Office Depot backlog alarm threshold is
87
+ **10 aged files**.
88
+
89
+ ## Provisioning a monitor (runbook)
90
+
91
+ 1. Write the monitor method on `_Worker_Monitor_Compass` (or a new `_Worker_Monitor_<X>`
92
+ class). Keep it **fully self-contained**: no private helpers, no class constants — all
93
+ per-monitor config lives as `ALL_CAPS` local variables inside the method (PHP disallows
94
+ `const` at function scope), so the class stays clean as monitors accumulate.
95
+ 2. Provision the OneUptime monitor by cloning the reusable import/export template at
96
+ `monitor-import-OfficeDepot-EDI-1.0.json` (a local dev artifact, not a repo file) and
97
+ importing it into OneUptime; wire the Contains criteria above.
98
+ 3. Register the OneUptime push URL as a local/constant in the monitor method — it is a
99
+ **push credential**; never log it and never record its value in a doc.
100
+ 4. Add the `Core.CronJobs` row (`action = 'Monitor/<Class>/<Method>'`) with the schedule,
101
+ and set OneUptime's Degraded/Offline thresholds to match the cadence.
102
+
103
+ ## Client variations
104
+
105
+ None — the pattern is shared infrastructure. The first monitor targets Compass USA's
106
+ Office Depot inbound EDI (vendor ODP), but the class and template are client-agnostic;
107
+ clone the template to provision the same check for another client.
108
+
109
+ ## Gotchas / known issues
110
+
111
+ - OneUptime Incoming Request bodies are matched **as strings only** (Contains/NotContains) —
112
+ no numeric comparison, no JSON key targeting. Always emit a decided token, never a raw
113
+ number, for push monitors.
114
+ - The OneUptime push URL is a credential — keep it as a local/constant in the method; never
115
+ log it or write its value into knowledge docs.
116
+ - Keep the metric POST non-fatal (`throwExceptionsOnFailure` off) so a monitoring outage
117
+ never breaks the worker job it rides in.
118
+
119
+ ## Change history
120
+ - 2026-07-13 — Ported Compass monitoring from the 1.0 worker tier into worker2 reporting to
121
+ OneUptime; built `_Worker_Monitor_Compass::OfficeDepotEdiImportQueue()` (Office Depot EDI
122
+ backlog), the token-based alarm contract (OneUptime Incoming Request can only string-match
123
+ Contains, not compare numbers), and the 5-min cadence → 10/15-min heartbeat timing. (jcardinal)
124
+
125
+ ## Related docs
126
+ - [Monitoring Framework](./monitoring-framework.md) — the parallel DB-driven, email-alert monitoring pattern
127
+ - [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md) — `_Cloud::getS3Objects()` used to list the EDI bucket
128
+ - [OneUptime 1.0 worker uptime monitoring](../../../1.0/apps/worker/features/oneuptime-worker-uptime-monitoring.md) — the 1.0 push-heartbeat predecessor
@@ -17,8 +17,8 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
17
17
 
18
18
  ## 2.0 framework
19
19
 
20
- - **_underscore** (_Underscore) _(framework core)_ — 29 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
21
- - **worker2** (Worker) — 27 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
20
+ - **_underscore** (_Underscore) _(framework core)_ — 31 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
21
+ - **worker2** (Worker) — 28 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
22
22
  - **api2** (API) — 10 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
23
23
  - **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
24
24
  - **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
@@ -9,6 +9,7 @@ apps:
9
9
  - toga2-commerce
10
10
  - worker
11
11
  - worker1.5
12
+ - worker2
12
13
  - dbchanges2
13
14
  project: _Underscore
14
15
  client: compass-usa
@@ -0,0 +1,63 @@
1
+ ---
2
+ type: session
3
+ slug: bdr-plan-hubspot
4
+ title: BDR web-funnel implementation plan + HubSpot content decision
5
+ author: tcox
6
+ repos: [bdr, ai-bdr]
7
+ framework: "2.0"
8
+ client: shared
9
+ status: active
10
+ created: 2026-07-13
11
+ updated: 2026-07-13
12
+ ---
13
+
14
+ # Session: bdr-plan-hubspot
15
+ **Date:** 2026-07-13
16
+ **Project/Repo:** bdr (new) — knowledge filed under `2.0/apps/ai-bdr/` (2.0)
17
+ **Task:** Produce the implementation plan for BDR — a multi-campaign "AI BDR / Agent Studio" public web funnel (Next.js) that is the new UI/front door of the existing AI-BDR product — and capture the plan + decisions into the team knowledge base.
18
+
19
+ ---
20
+
21
+ ## What WORKED
22
+ - Authored the full plan at `C:\WWW\BDR\PLAN.md` (local, Draft 2), grounded in a full read of the mockup (`C:\WWW\BDR\mockup\` — `app.jsx`, `screens.jsx`, `components.jsx`, `styles.css`, `CLAUDE.md`) and the `info` repo backend (`C:\WWW\info\src`).
23
+ - Confirmed `info`'s HubSpot usage is a ~15-line server-side fetch proxied by a Next route handler (`info/src/lib/hubspot.ts` = `getContact` via `/crm/v3/objects/contacts/{id}`; `info/src/app/bdr/api/contact/route.ts` = the proxy). This validated the "HubSpot integration is trivial" claim. `info` uses "campaign" for attribution only (`hsCampaignId` → Toga `addContactToCampaign`); it never fetches marketing content from HubSpot.
24
+ - Captured knowledge under `2.0/apps/ai-bdr/`: `features/bdr-web-funnel-plan.md` (full plan mirror, paths genericized), `features/web-funnel-content-model.md` (distilled), and an additive HubSpot-over-Contentful note in `architecture.md`. Registered the `bdr` repo in `registry.json`. `validate: OK (238 docs)` each pass.
25
+ - Kept the two feature docs at `status: draft` (this is Draft 2, still needs one more review). Architecture.md left `status: active` (shared voice-system doc; edit was additive only).
26
+
27
+ ## What did NOT work — DO NOT RETRY THESE
28
+ - **CLI git push fails:** `fatal: could not read Username for 'https://github.com'` — the `toga-tech` origin is HTTPS with no credential helper and the shell is non-interactive. `gh` CLI is NOT installed (checked both bash and PowerShell). DO NOT retry CLI push or `gh` — the developer pushes via **GitHub Desktop**.
29
+ - **Filing the doc under a separate `2.0/apps/bdr/` folder:** validate failed with `ERROR: frontmatter repo "bdr" != folder "ai-bdr"`. Resolved by setting the doc's frontmatter `repo: ai-bdr` (BDR knowledge lives under the ai-bdr app per developer directive). DO NOT create a `2.0/apps/bdr/` folder.
30
+ - **Reverted approaches (do not reintroduce):** (1) React/Vite SPA — reverted to **Next.js** because the site is highly public (SSR/SEO) and Next's server hosts the secret-bearing calls. (2) "Hybrid" static-config-in-repo-now/CMS-later — killed. (3) Hardcode-copy-now/HubSpot-later phasing — reverted; content is HubSpot-owned from the start, no in-repo/hardcoded content.
31
+
32
+ ## Not tried yet (candidates for next session)
33
+ - Final review of `PLAN.md` (Draft 2); on approval, flip the two feature docs off `draft` and re-capture the final version.
34
+ - Decide §9.10: how campaign content is modeled in HubSpot (**HubDB** likely vs custom object) — gates the content-layer build; confirm with the HubSpot instance owner. Hard part = nested content (Q&A pool + rotation sets, call summaries, accent RichLines).
35
+ - Create the actual `agilantsolutions/BDR` GitHub repo (needs gh install or the GitHub web UI) and version `PLAN.md` there.
36
+ - Resolve remaining §9 open questions: agent choice (fixed preset vs manual Creator), service-picker in scope, tweaks panel in prod, `togatech` integration mechanism, campaign catalog + who authors, first campaign direction/verbiage (pending leadership).
37
+
38
+ ## Current file state
39
+ | File | Status | Notes |
40
+ |------|--------|-------|
41
+ | `C:\WWW\BDR\PLAN.md` | Modified (local only, not in git) | Draft 2, complete. Next.js + HubSpot-owned runtime content + same-product framing. |
42
+ | `knowledge/2.0/apps/ai-bdr/features/bdr-web-funnel-plan.md` | Updated, committed `c016f4e` (UNPUSHED) | Full plan mirror; `status: draft`. |
43
+ | `knowledge/2.0/apps/ai-bdr/features/web-funnel-content-model.md` | Updated, committed `c016f4e` (UNPUSHED) | Distilled facts; `status: draft`. |
44
+ | `knowledge/2.0/apps/ai-bdr/architecture.md` | Updated, committed `0b38d14` (UNPUSHED) | Additive HubSpot-over-Contentful note; `status: active`. |
45
+ | `knowledge/registry.json` | `bdr` registered (already pushed) | `{repo:"bdr", project:"BDR", framework:"2.0", role:"app", dependsOn:["api2"]}`. |
46
+
47
+ ## Decisions made
48
+ - **Framework = Next.js (App Router) + TypeScript** (over React/Vite SPA). Rationale: highly public site → SSR + SEO, and Next's server components + route handlers host the secret-bearing HubSpot/Toga calls with no separate backend. Matches `info`.
49
+ - **Content store = HubSpot** (over Contentful). Rationale: marketing already operates in HubSpot; the campaign/attribution model already exists there; the API read is trivial (proven in `info`); no new vendor. Trade-off accepted: HubSpot is not a purpose-built content CMS (weaker nested-content + editor UX), acceptable because the content shape is stable / not frequently restructured. Contentful rejected as overkill for stable content + a new SaaS dependency.
50
+ - **Content is HubSpot-owned, fetched by slug at runtime, no hardcoding, no in-repo content**, behind one `CampaignContentProvider` seam + `DEFAULT` fallback. Rejected: hybrid static bundles; hardcode-then-HubSpot phasing.
51
+ - **BDR is the UI/front door of the existing AI-BDR product** (same product, not standalone) — so knowledge is filed under `2.0/apps/ai-bdr/`, and BDR reuses `info`'s backend calls. Resolved the earlier "backend relationship" open question.
52
+ - **Config-driven components** — campaign = serializable data bundle keyed by slug; never `if(campaign===x)` in components; behavior/icons via enum→registry. Follows toga25-supply `useClientFields` + toga2-commerce Cart C1-C7 precedents.
53
+ - **New code repo named `BDR`** (greenfield). The external `info` repo (github.com/agilantsolutions/info) is reference-only, not a TOGA registry repo.
54
+
55
+ ## Blockers
56
+ - Knowledge commits `c016f4e` + `0b38d14` are committed locally but **UNPUSHED** — CLI auth fails; awaiting the developer's manual push via GitHub Desktop.
57
+ - §9.10 (HubSpot content mechanism: HubDB vs custom object) undecided — gates building the content layer.
58
+
59
+ ## Exact next step
60
+ > In GitHub Desktop, push the `toga-tech` `_main` commits (`c016f4e` + `0b38d14`) to `agilantsolutions/claude`. Then do the final review of `C:\WWW\BDR\PLAN.md` (Draft 2); on approval, ask Claude to flip the two ai-bdr feature docs off `status: draft` and re-capture.
61
+
62
+ ---
63
+ _Saved by /session-save on 2026-07-13_
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.324",
3
+ "version": "1.0.326",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",