toga-ai 1.0.122 → 1.0.124

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.
@@ -0,0 +1,5 @@
1
+ # toga2-commerce (TOGa Commerce) — 2.0 knowledge
2
+
3
+ | Doc | Summary | Files |
4
+ |-----|---------|-------|
5
+ | [Cart Notification Emails — duplicate prevention](features/cart-notification-emails.md) | On the cart "Notifications" section a user can add CC email addresses to an order. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/stores/useEmailOptionsStore.ts, src/stores/useCartSalesQuoteZu.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts |
@@ -0,0 +1,79 @@
1
+ ---
2
+ title: Cart Notification Emails — duplicate prevention
3
+ framework: "2.0"
4
+ repo: toga2-commerce
5
+ project: TOGa Commerce
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-18
10
+ owners: ["bala"]
11
+ files:
12
+ - src/pages/Cart/CartPage.tsx
13
+ - src/pages/Cart/view/cartForm/CartForm.tsx
14
+ - src/stores/useEmailOptionsStore.ts
15
+ - src/stores/useCartSalesQuoteZu.ts
16
+ - src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts
17
+ related: []
18
+ ---
19
+
20
+ ## Summary
21
+ On the cart "Notifications" section a user can add CC email addresses to an order. Adding the
22
+ same address twice (often with different casing) used to slip through to the API and blow up on
23
+ submit with a MySQL 1062 duplicate-key error on the `SalesOrderEmailAddresses.salesOrderId_emailAddress`
24
+ unique index (whose collation `utf8mb4_0900_ai_ci` is case-insensitive). This feature blocks
25
+ duplicates case-insensitively in the frontend and shows a sapphire info message instead of letting
26
+ the error reach the user.
27
+
28
+ ## Key files / entry points
29
+ - `CartPage.tsx` — `handleAddEmail`: validates the email regex, then on success calls
30
+ `addEmailOption` (UI options list) and `addEmail` (the cart sales-quote store that builds the
31
+ `salesOrderEmailAddresses` payload). Normalizes the input once with `.trim()`.
32
+ - `CartForm.tsx` — owns the duplicate UX. `handleAddEmailWithDuplicateCheck` wraps the passed-in
33
+ `handleAddEmail`: it case-insensitively checks the existing `emails` list and, on a match, shows
34
+ the message instead of adding. The **Add Email** button calls this wrapper.
35
+ - `useEmailOptionsStore.ts` — `addEmailOption` de-dupes the checkbox list.
36
+ - `useCartSalesQuoteZu.ts` — `addEmail` de-dupes the actual `salesOrderEmailAddresses` payload.
37
+
38
+ ## How it works
39
+ 1. **Before (manual add):** clicking Add Email runs `handleAddEmailWithDuplicateCheck` in
40
+ `CartForm`. It compares `(e.email ?? "").trim().toLowerCase()` against the normalized input. On a
41
+ match it sets `duplicateEmailMessage` and returns (does not add).
42
+ 2. **After (auto-add on user select):** when the order user is chosen, their email (and manager's)
43
+ is auto-added via `addEmailOption`. Both stores de-dupe case-insensitively, so a case-variant of
44
+ an already-present address is silently dropped rather than duplicated.
45
+ 3. **Display:** the message renders via the shared `InfoBanner` component (the same one used for the
46
+ "view as" / "edit order" banners), styled `text-sapphire-700 text-xs`, no icon. It is NOT a
47
+ toaster and NOT a red `setError` validation — it is a sapphire info message.
48
+ 4. The message text comes from the `notifications.duplicateEmailAddressError.label` field config,
49
+ present in every `CARTPAGE.ts` (all client/role/language variants, English + French).
50
+ 5. A `useEffect` clears the message when the input, selected user (`orderForUser?.uuid`), or email
51
+ list (`emails.length`) changes.
52
+
53
+ ## Data model
54
+ Frontend only. The payload list maps to the `SalesOrderEmailAddresses` table (api2/backend), which
55
+ has a case-insensitive unique index on `(salesOrderId, emailAddress)`.
56
+
57
+ ## Client variations
58
+ The de-dupe mechanism is uniform across clients. Only the message label text differs per client
59
+ config: English `"This email has already been added"`; French (Compass Canada)
60
+ `"Cette adresse e-mail a déjà été ajoutée"`. The label must exist in every `CARTPAGE.ts` variant or
61
+ the message resolves to `undefined` and renders blank.
62
+
63
+ ## Gotchas / known issues
64
+ - The real source of the 1062 was `useCartSalesQuoteZu.addEmail` comparing with `===`
65
+ (case-sensitive). Both stores must compare case-insensitively; fixing only the UI list is not
66
+ enough because `addEmail` builds the payload.
67
+ - Normalize with `(x ?? "").trim().toLowerCase()`, not `x?.trim().toLowerCase()` — both are
68
+ nullish-safe (optional chaining short-circuits the whole chain, it does not throw), but `(x ?? "")`
69
+ guarantees a string and avoids an `undefined === undefined` match edge.
70
+ - Adding the label to only some `CARTPAGE.ts` files leaves other client/role/language users with a
71
+ blank message.
72
+ - Do not use a toaster or `setError` (red) for this — product wants the sapphire info style.
73
+
74
+ ## Change history
75
+ - 2026-06-18 — Initial: case-insensitive de-dupe in both cart stores, sapphire `InfoBanner` message in
76
+ CartForm, `duplicateEmailAddressError` label added to all CARTPAGE.ts variants (bala)
77
+
78
+ ## Related docs
79
+ None yet.
@@ -25,10 +25,12 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
25
25
  - **talos** (TOGa IQ) — 6 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
26
26
  - **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
27
27
  - **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
28
+ - **toga2-commerce** (TOGa Commerce) — 1 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
28
29
 
29
30
  ## standalone framework
30
31
 
31
32
  - **togatech** (TOGA Technology Website) — 1 doc(s) → [standalone/apps/togatech/INDEX.md](standalone/apps/togatech/INDEX.md)
33
+ - **forward** (Forwarder) — 2 doc(s) → [standalone/apps/forward/INDEX.md](standalone/apps/forward/INDEX.md)
32
34
 
33
35
  ## Clients
34
36
 
@@ -5,14 +5,15 @@ apps:
5
5
  - _underscore
6
6
  - api2
7
7
  - toga2-supply
8
+ - toga2-commerce
8
9
  - worker
9
10
  - dbchanges2
10
11
  project: _Underscore
11
12
  client: compass-canada
12
13
  type: profile
13
14
  status: active
14
- updated: 2026-06-08
15
- owners: [jcardinal]
15
+ updated: 2026-06-18
16
+ owners: [jcardinal, bala]
16
17
  files: []
17
18
  related:
18
19
  - ../compass-usa/profile.md
@@ -5,14 +5,15 @@ apps:
5
5
  - _underscore
6
6
  - api2
7
7
  - toga2-supply
8
+ - toga2-commerce
8
9
  - worker
9
10
  - dbchanges2
10
11
  project: _Underscore
11
12
  client: compass-usa
12
13
  type: profile
13
14
  status: active
14
- updated: 2026-06-08
15
- owners: [jcardinal]
15
+ updated: 2026-06-18
16
+ owners: [jcardinal, bala]
16
17
  files: []
17
18
  related:
18
19
  - features/asn-to-item-fulfillment.md
@@ -1,21 +1,163 @@
1
1
  [
2
- { "repo": "_underscore", "project": "_Underscore", "framework": "2.0", "role": "core", "dependsOn": [] },
3
- { "repo": "worker2", "project": "Worker", "framework": "2.0", "role": "app", "dependsOn": [] },
4
- { "repo": "api2", "project": "API", "framework": "2.0", "role": "app", "dependsOn": [] },
5
- { "repo": "dbchanges2", "project": "Database Changes", "framework": "2.0", "role": "core", "dependsOn": [] },
6
- { "repo": "library", "project": "Library", "framework": "1.0", "role": "core", "dependsOn": [] },
7
- { "repo": "worker", "project": "Worker", "framework": "1.0", "role": "app", "dependsOn": [] },
8
- { "repo": "toga2-supply", "project": "TOGa Supply", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
9
- { "repo": "saml", "project": "SAML SSO Gateway", "framework": "2.0", "role": "app", "dependsOn": [] },
10
- { "repo": "toga2-view", "project": "TOGa View Frontend", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
11
- { "repo": "togadesk", "project": "TOGa Desk", "framework": "1.0", "role": "app", "dependsOn": [] },
12
- { "repo": "togaview", "project": "TOGa View", "framework": "1.0", "role": "app", "dependsOn": [] },
13
- { "repo": "toga2-hub", "project": "TOGa Hub", "framework": "2.0", "role": "app", "dependsOn": ["api2"] },
14
- { "repo": "togatech", "project": "TOGA Technology Website", "framework": "standalone", "role": "app", "dependsOn": [] },
15
- { "repo": "webhook", "project": "Webhook", "framework": "1.0", "role": "app", "dependsOn": ["library"] },
16
- { "repo": "walmarttechservices", "project": "Walmart Tech Services", "framework": "1.0", "role": "app", "dependsOn": ["library"] },
17
- { "repo": "talos", "project": "TOGa IQ", "framework": "2.0", "role": "app", "dependsOn": [], "language": "python" },
18
- { "repo": "test", "project": "Test", "framework": "1.0", "role": "app", "dependsOn": ["library"] },
19
- { "repo": "voice-to-voice", "project": "TOGa Voice", "framework": "2.0", "role": "app", "dependsOn": [], "language": "python" },
20
- { "repo": "ai-bdr", "project": "AI-BDR", "framework": "2.0", "role": "app", "dependsOn": [] }
2
+ {
3
+ "repo": "_underscore",
4
+ "project": "_Underscore",
5
+ "framework": "2.0",
6
+ "role": "core",
7
+ "dependsOn": []
8
+ },
9
+ {
10
+ "repo": "worker2",
11
+ "project": "Worker",
12
+ "framework": "2.0",
13
+ "role": "app",
14
+ "dependsOn": []
15
+ },
16
+ {
17
+ "repo": "api2",
18
+ "project": "API",
19
+ "framework": "2.0",
20
+ "role": "app",
21
+ "dependsOn": []
22
+ },
23
+ {
24
+ "repo": "dbchanges2",
25
+ "project": "Database Changes",
26
+ "framework": "2.0",
27
+ "role": "core",
28
+ "dependsOn": []
29
+ },
30
+ {
31
+ "repo": "library",
32
+ "project": "Library",
33
+ "framework": "1.0",
34
+ "role": "core",
35
+ "dependsOn": []
36
+ },
37
+ {
38
+ "repo": "worker",
39
+ "project": "Worker",
40
+ "framework": "1.0",
41
+ "role": "app",
42
+ "dependsOn": []
43
+ },
44
+ {
45
+ "repo": "toga2-supply",
46
+ "project": "TOGa Supply",
47
+ "framework": "2.0",
48
+ "role": "app",
49
+ "dependsOn": [
50
+ "api2"
51
+ ]
52
+ },
53
+ {
54
+ "repo": "saml",
55
+ "project": "SAML SSO Gateway",
56
+ "framework": "2.0",
57
+ "role": "app",
58
+ "dependsOn": []
59
+ },
60
+ {
61
+ "repo": "toga2-view",
62
+ "project": "TOGa View Frontend",
63
+ "framework": "2.0",
64
+ "role": "app",
65
+ "dependsOn": [
66
+ "api2"
67
+ ]
68
+ },
69
+ {
70
+ "repo": "togadesk",
71
+ "project": "TOGa Desk",
72
+ "framework": "1.0",
73
+ "role": "app",
74
+ "dependsOn": []
75
+ },
76
+ {
77
+ "repo": "togaview",
78
+ "project": "TOGa View",
79
+ "framework": "1.0",
80
+ "role": "app",
81
+ "dependsOn": []
82
+ },
83
+ {
84
+ "repo": "toga2-hub",
85
+ "project": "TOGa Hub",
86
+ "framework": "2.0",
87
+ "role": "app",
88
+ "dependsOn": [
89
+ "api2"
90
+ ]
91
+ },
92
+ {
93
+ "repo": "togatech",
94
+ "project": "TOGA Technology Website",
95
+ "framework": "standalone",
96
+ "role": "app",
97
+ "dependsOn": []
98
+ },
99
+ {
100
+ "repo": "webhook",
101
+ "project": "Webhook",
102
+ "framework": "1.0",
103
+ "role": "app",
104
+ "dependsOn": [
105
+ "library"
106
+ ]
107
+ },
108
+ {
109
+ "repo": "walmarttechservices",
110
+ "project": "Walmart Tech Services",
111
+ "framework": "1.0",
112
+ "role": "app",
113
+ "dependsOn": [
114
+ "library"
115
+ ]
116
+ },
117
+ {
118
+ "repo": "talos",
119
+ "project": "TOGa IQ",
120
+ "framework": "2.0",
121
+ "role": "app",
122
+ "dependsOn": [],
123
+ "language": "python"
124
+ },
125
+ {
126
+ "repo": "test",
127
+ "project": "Test",
128
+ "framework": "1.0",
129
+ "role": "app",
130
+ "dependsOn": [
131
+ "library"
132
+ ]
133
+ },
134
+ {
135
+ "repo": "voice-to-voice",
136
+ "project": "TOGa Voice",
137
+ "framework": "2.0",
138
+ "role": "app",
139
+ "dependsOn": [],
140
+ "language": "python"
141
+ },
142
+ {
143
+ "repo": "ai-bdr",
144
+ "project": "AI-BDR",
145
+ "framework": "2.0",
146
+ "role": "app",
147
+ "dependsOn": []
148
+ },
149
+ {
150
+ "repo": "forward",
151
+ "project": "Forwarder",
152
+ "framework": "standalone",
153
+ "role": "app",
154
+ "dependsOn": []
155
+ },
156
+ {
157
+ "repo": "toga2-commerce",
158
+ "project": "TOGa Commerce",
159
+ "framework": "2.0",
160
+ "role": "app",
161
+ "dependsOn": ["api2"]
162
+ }
21
163
  ]
@@ -0,0 +1,6 @@
1
+ # forward (Forwarder) — standalone knowledge
2
+
3
+ | Doc | Summary | Files |
4
+ |-----|---------|-------|
5
+ | [Forwarder Architecture](architecture.md) | Forwarder is a tiny **standalone PHP application** that powers TOGA's short, branded redirect domains. | forward/forward.ini, forward/index.php, forward/.htaccess, forward/.platform/httpd/conf.d/rewritemap.conf, forward/.ebextensions/rewritemap.config, forward/composer.json |
6
+ | [Encrypted-Link Handler](features/encrypted-link-handler.md) | A `index.php` feature in [Forwarder](../architecture.md) for redirect domains whose URL path carries an **encrypted token** that must be decoded before redirect | forward/index.php |
@@ -0,0 +1,121 @@
1
+ ---
2
+ title: Forwarder Architecture
3
+ framework: "standalone"
4
+ repo: forward
5
+ project: Forwarder
6
+ client: shared
7
+ type: architecture
8
+ status: active
9
+ updated: 2026-06-18
10
+ owners: [jcardinal]
11
+ files:
12
+ - forward/forward.ini
13
+ - forward/index.php
14
+ - forward/.htaccess
15
+ - forward/.platform/httpd/conf.d/rewritemap.conf
16
+ - forward/.ebextensions/rewritemap.config
17
+ - forward/composer.json
18
+ related: []
19
+ ---
20
+
21
+ # Forwarder Architecture
22
+
23
+ Forwarder is a tiny **standalone PHP application** that powers TOGA's short, branded
24
+ redirect domains. The pattern: point a short **CNAME** (e.g. `powerbi.togatech.com`,
25
+ `feedback.agilantsolutions.com`) at the Forwarder app running on **AWS Elastic Beanstalk**
26
+ (Amazon Linux 2023 / Apache), and Forwarder issues an HTTP redirect to the real
27
+ destination. It does **not** use either PHP framework (1.0 `App_` or 2.0 `_underscore`),
28
+ which is why it lives under the `standalone/` knowledge partition.
29
+
30
+ Local path on dev machines is tracked in Claude memory (`repo-path-forward`), not here.
31
+
32
+ ## Two layers: zero-compute map vs. light PHP compute
33
+
34
+ Forwarder has two distinct redirect mechanisms. The vast majority of traffic never
35
+ touches PHP at all.
36
+
37
+ ### 1. Zero-compute redirects (the common case) — Apache `RewriteMap` + `forward.ini`
38
+
39
+ The `.htaccess` at the repo root drives a plain-text lookup table:
40
+
41
+ - `forward.ini` is an Apache **`txt` RewriteMap** of `source destination` lines:
42
+ - `source` = full host or `host/path` (no scheme, no trailing slash)
43
+ - `destination` = full target URL (query strings allowed)
44
+ - Blank lines and `#` comments are ignored.
45
+ - The `RewriteMap forwardmap txt:/var/www/html/forward.ini` directive **cannot** live in
46
+ `.htaccess` (must be server/VirtualHost scope), so it is deployed separately (see
47
+ Deployment below).
48
+ - **Match precedence in `.htaccess`:**
49
+ 1. Exact `HTTP_HOST + REQUEST_URI` match → `302` (flags `NE,QSA`).
50
+ 2. Else `HTTP_HOST`-only match → `302`, appending the original path + query to the
51
+ destination.
52
+ 3. Else, if the request is not a real file/dir, fall through to `index.php`.
53
+ - **Operational win:** editing `forward.ini` takes effect **immediately** — Apache
54
+ re-reads the txt map on the next request after a graceful reload (`sudo systemctl
55
+ reload httpd`); no redeploy or restart needed. Most "add a redirect" requests are a
56
+ one-line edit to `forward.ini`.
57
+ - **Authoring rule:** put the most **specific** `host/path` lines **before** the general
58
+ `host`-only line, or the general line matches first and the path line is never reached
59
+ (documented in the `forward.ini` header).
60
+ - **Health check:** `.htaccess` short-circuits to `200 OK` for `ELB-HealthChecker`
61
+ user-agents and localhost / private-IP hosts (`127.*`, `10.*`, `172.*`, `192.168.*`),
62
+ so AWS ELB health checks pass without a map entry.
63
+
64
+ ### 2. Light compute (the exception) — `index.php`
65
+
66
+ `index.php` is reached only when no map entry matched. It handles three cases:
67
+
68
+ - **Encrypted-link handler** — for a hardcoded `$encryptedDomains` allowlist (PCMatic /
69
+ Newegg / `forward.*` agent-download domains), it extracts the encrypted path segment
70
+ (by a per-domain `offset`: `0` = first segment, `1` = second), runs `decrypt()`, then
71
+ `302`s to the domain's configured base URL with the decrypted value `urlencode`d
72
+ appended. Invalid/empty/tampered input returns a styled `400`. See the
73
+ [encrypted-link-handler](features/encrypted-link-handler.md) feature doc.
74
+ - **Local directory handler** — if the first host label matches a directory in the repo
75
+ (e.g. `sos.*` → `/sos/`), it redirects to `/<subdomain>/` and serves that dir's
76
+ `index.html`. Used for the bundled `sos/` Splashtop SOS download page.
77
+ - **404** — otherwise returns a styled `404` echoing the unmatched `host + uri`.
78
+
79
+ ## Deployment (Elastic Beanstalk / Apache)
80
+
81
+ - Runs on **Elastic Beanstalk**, Amazon Linux 2023, Apache `httpd`.
82
+ - The `RewriteMap` definition is deployed to `/etc/httpd/conf.d/rewritemap.conf` two ways
83
+ for cross-platform safety:
84
+ - `.platform/httpd/conf.d/rewritemap.conf` — the **Amazon Linux 2+/2023** mechanism
85
+ (current).
86
+ - `.ebextensions/rewritemap.config` — legacy **Amazon Linux 1** `files:` fallback.
87
+ Both can coexist; AL2+ ignores the `.ebextensions` file path managed by `.platform`,
88
+ and AL1 ignores `.platform` entirely.
89
+ - `forward.ini` deploys to `/var/www/html/forward.ini` (the path the map points at).
90
+ - `composer.json` declares a `Togatech\Forward\` PSR-4 autoload over `src/` but has **no
91
+ dependencies** and no `src/` is shipped today — the app is effectively a single
92
+ `index.php` plus the Apache config.
93
+
94
+ ## The `decrypt()` scheme
95
+
96
+ `decrypt()` in `index.php` is a **home-rolled** symmetric scheme, used only to obfuscate
97
+ agent-download tokens in URLs — **not** real encryption and not for protecting secrets:
98
+
99
+ 1. URL-safe base64 normalize (`-_` → `+/`); the **last char** is a stored integrity hash.
100
+ 2. base64-decode the remainder; the **first byte** is the key-start seed.
101
+ 3. Regenerate a keystream from the seed (`i += i` per step, `chr(i % 255)`) and XOR it
102
+ against the payload to recover the plaintext.
103
+ 4. Verify by recomputing the first char of `base64(hmac_sha256(payload, key))` and
104
+ comparing to the stored hash; mismatch → empty string (treated as failure → `400`).
105
+
106
+ Treat this as obfuscation only. Anything genuinely sensitive must not rely on it.
107
+
108
+ ## Notes / gotchas
109
+
110
+ - **No restart needed for new redirects** — edit `forward.ini`, reload httpd. This is the
111
+ single most useful operational fact about this app.
112
+ - Specific-path map lines must precede host-only lines (see `forward.ini` header).
113
+ - New **encrypted** domains require a code change (`$encryptedDomains` in `index.php`),
114
+ not just a `forward.ini` edit.
115
+ - `index.php` only runs on a map miss, so a bad/over-broad `forward.ini` host-only line
116
+ can shadow paths you expected PHP to handle.
117
+
118
+ ## Change history
119
+
120
+ - 2026-06-18 (jcardinal) — Initial architecture documentation; registered `forward`
121
+ (Forwarder, standalone) in the knowledge base.
@@ -0,0 +1,70 @@
1
+ ---
2
+ title: Encrypted-Link Handler
3
+ framework: "standalone"
4
+ repo: forward
5
+ project: Forwarder
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-18
10
+ owners: [jcardinal]
11
+ files:
12
+ - forward/index.php
13
+ related:
14
+ - standalone/apps/forward/architecture.md
15
+ ---
16
+
17
+ # Encrypted-Link Handler
18
+
19
+ A `index.php` feature in [Forwarder](../architecture.md) for redirect domains whose URL
20
+ path carries an **encrypted token** that must be decoded before redirecting — used for
21
+ remote-support / agent-download links (PCMatic, Newegg, generic `forward.*` agent
22
+ downloads) where the trailing value (e.g. a user identifier) is obfuscated in the path.
23
+
24
+ ## When it runs
25
+
26
+ Only when the Apache `RewriteMap` (`forward.ini`) produced **no match** and the request
27
+ falls through to `index.php`, and the lowercased `HTTP_HOST` is a key in the hardcoded
28
+ `$encryptedDomains` allowlist near the top of `index.php`.
29
+
30
+ ## Configuration
31
+
32
+ `$encryptedDomains` maps a host to `['url' => <base>, 'offset' => <int>]`:
33
+
34
+ - `url` — the destination base URL; the decrypted value is `urlencode`d and appended.
35
+ - `offset` — which **0-based path segment** holds the encrypted token:
36
+ - `0` → `host.com/ENCRYPTED`
37
+ - `1` → `host.com/prefix/ENCRYPTED`
38
+
39
+ Adding a new encrypted domain is a **code change** (edit `$encryptedDomains`), not a
40
+ `forward.ini` edit.
41
+
42
+ ## Flow
43
+
44
+ 1. Split the request path on `/`; take the segment at `offset` as the token.
45
+ 2. Empty token → styled **`400` Invalid Link**.
46
+ 3. `decrypt($token)` (see below); failure (empty return) → styled **`400` Decryption
47
+ Failed**.
48
+ 4. Success → `header('Location: ' . $base . urlencode($decrypted), 302)`.
49
+
50
+ ## The `decrypt()` algorithm
51
+
52
+ Custom symmetric scheme — **obfuscation, not real encryption**:
53
+
54
+ 1. Restore URL-safe base64 (`-_` → `+/`). The **final character** is a stored 1-char
55
+ integrity hash; strip and keep it.
56
+ 2. base64-decode the rest. The **first byte** is the key-start seed; the remainder is the
57
+ payload.
58
+ 3. Regenerate a keystream from the seed — `for (i = seed, j = 0; j < len; i += i, j++)
59
+ key .= chr(i % 255)` — and XOR it byte-for-byte against the payload to recover the
60
+ plaintext.
61
+ 4. Integrity check: recompute `substr(base64_encode(hash_hmac('SHA256', payload, key,
62
+ true)), 0, 1)` and compare to the stored hash char. Mismatch → return `''` (→ `400`).
63
+
64
+ Because it is home-rolled and uses only a single hash character for integrity, treat it
65
+ strictly as link obfuscation. Do not use it to protect anything sensitive.
66
+
67
+ ## Change history
68
+
69
+ - 2026-06-18 (jcardinal) — Initial documentation of the encrypted-link handler during
70
+ Forwarder onboarding into the knowledge base.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.122",
3
+ "version": "1.0.124",
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",