toga-ai 1.0.903 → 1.0.905

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.
@@ -29,6 +29,8 @@ Shared React+TS component/template/API library (`@agilant/toga-blox`) reused by
29
29
 
30
30
  `toga-blox` (npm package **`@agilant/toga-blox`**) is the shared React + TypeScript component / template / utility library reused across the 2.0 frontends: `toga2-view`, `toga2-supply`, `toga25-supply`, `toga2-commerce`, `toga2-hub`. Not a PHP framework repo — no `_underscore` backend. Filed under `2.0/` because every consumer is a 2.0 app, but structurally a standalone front-end package (`dependsOn: []`).
31
31
 
32
+ **New-platform apps (toga25-desk, toga25-iq, toga2-template, future apps) do not use this repo — they use [toga25-blox](../toga25-blox/architecture.md), a source-shared (git, not npm) replacement.**
33
+
32
34
  Ships: presentational + data-aware **components** (`src/components/`), **templates** wiring components into ready screens (`src/templates/`, e.g. `PrimaryTable`, `Login`, `AddNotes`), a thin **API client** over axios + React Query (`src/api/`, `src/reactQuery/`), and **utilities** (`src/utils/`). Builds with `tsc` to `dist/` (not bundled), develops via Storybook, tests with Vitest.
33
35
 
34
36
  **Critical rules:**
@@ -13,10 +13,12 @@
13
13
  | [Client Fields — per-tenant / language / role content & config](features/client-fields.md) | Per-tenant/language/role JSON config that drives almost all commerce UI text, fields, and login fetches (Layer A global + Layer B FIELDS registry); open before |
14
14
  | [Config-Driven Expedited Shipping Gating (Cart)](features/expedited-shipping-gating.md) | Expedited shipping options are config-gated to show only when the cart holds a computer kit; open when touching cart shipping-method options or the gate config. |
15
15
  | [Filter / Search-Results Page & the Two Search Entry Points](features/filter-search-results-page.md) | Two search entry points (Home in-place vs Header→/filter) render the same card via different code paths and FIELDS files; open for any search-results or kit-car |
16
+ | [Force Logout on Deployment (DeploymentGuard) — commerce](features/force-logout-on-deployment.md) | Signs every user out when a deploy bumps a Core parameter, after showing a notice; open for surprise logouts, a repeating "new version" toaster, or `useDeployme |
16
17
  | ["Order For" — header pill, delegated token switch, and the cart hand-off](features/header-order-for-dropdown.md) | Header pill that switches the session to a delegated token so a buyer orders **as** someone else; open before touching the pill, `useOrderForZu`, the delegation |
17
18
  | [Inactive-item purchase gating (standalone lines only — kits are exempt by design)](features/inactive-item-purchase-gating.md) | `Items.isActive = 0` blocks standalone purchase but means something different on kit components, so the gate keys on purchase CONTEXT not the flag; open when to |
18
19
  | [Multi-Tenant Resolution & Theming](features/multi-tenant-theming.md) | How the hostname resolves the tenant and fans out to API base URL, theme (CSS variables), and fields; open when adding a tenant or debugging wrong-theme/wrong-A |
19
20
  | [Order-submit sync sequencing (useSubmitOrder) — why these calls must not run in parallel](features/order-submit-sync-sequencing.md) | The item sync must finish before the header sync on order submit, or concurrent line deletes race a depth-3 response into an uncaught api2 500; open when touchi |
21
+ | [Skeleton → content transitions (AnimatePresence mode, layout jump, blank gap)](features/page-loading-transitions.md) | How commerce swaps a loading skeleton for real content with framer-motion; open for page jump/flicker or a blank gap while loading. |
20
22
  | [Config-Driven Shipping Cost Waiver (Standard Ground free for computer kits)](features/shipping-cost-waiver-gating.md) | Config-driven cost waiver making Standard Ground free for a bare computer kit (Compass USA); open when touching shipping cost gates or the Compass free-freight |
21
23
  | [SSO sign-in, session gating & token refresh identity (hasSession / useClientSsoUrl)](features/sso-redirect-and-session-gating.md) | How commerce decides whether a visitor is signed in, shows the SSO panel or the credential form, and keeps a token refresh bound to one identity. |
22
24
  | [Storefront persona gating — the Home page's kit / category / item counts, and the persona switcher](features/storefront-persona-gating-and-home-counts.md) | What a storefront user sees on the Home page is decided **in the front end**, from `user._personaIds`, not by the API's ACL. |
@@ -24,4 +26,5 @@
24
26
  | [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How toga2-commerce builds/deploys on AWS Amplify (one dynamic amplify.yml, branch-per-env, per-env blox channel); open to deploy, stand up an env, resolve a blo |
25
27
  | [Cart e2e — Cypress conventions & harness (toga2-commerce)](workflows/cypress-testing.md) | Cypress e2e conventions + the Cart harness (cartV2.cy.ts) for toga2-commerce; read before adding any spec here, and note it doubles as the config-cart refactor |
26
28
  | [Diagnosing ERR_HTTP2_PROTOCOL_ERROR (one client fails, everyone else is fine)](workflows/http2-protocol-error-diagnosis.md) | Diagnose ERR_HTTP2_PROTOCOL_ERROR when one client/network fails a togacommerce tenant but everyone else works (usually the client's TLS-inspection proxy, not ou |
29
+ | [Running toga2-commerce locally (tenant hosts, not localhost)](workflows/local-development-hosts.md) | Commerce cannot run on `localhost`; use a tenant hostname in `/etc/hosts`. |
27
30
  | [Unit tests in toga2-commerce — vitest harness & the build-mode trap](workflows/unit-testing-vitest.md) | How to run and write unit tests in commerce (vitest + jsdom), and how to verify a change actually compiles without tripping the build-mode trap. |
@@ -0,0 +1,46 @@
1
+ ---
2
+ title: Force Logout on Deployment (DeploymentGuard) — commerce
3
+ framework: "2.0"
4
+ repo: toga2-commerce
5
+ project: TOGa Commerce
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-09-25
10
+ owners: [apeterson]
11
+ files:
12
+ - toga2-commerce/src/hooks/useDeploymentGuard.tsx
13
+ - toga2-commerce/src/lib/deploymentVersion.ts
14
+ - toga2-commerce/src/components/AuthLayout/AuthLayout.tsx
15
+ - toga2-commerce/src/contexts/AuthContext.tsx
16
+ - toga2-commerce/src/lib/authStorage.ts
17
+ related:
18
+ - ./sso-redirect-and-session-gating.md
19
+ - ./catalog-cache-freshness.md
20
+ - ../../toga25-supply/features/force-logout-on-deployment.md
21
+ ---
22
+
23
+ Signs every user out when a deploy bumps a Core parameter, after showing a notice; open for surprise logouts, a repeating "new version" toaster, or `useDeploymentGuard`.
24
+
25
+ ## Summary
26
+
27
+ Core parameter `META_LAST_REFRESH_DATETIME` is polled while signed in. When it changes mid-session the app raises a toaster ("A new version is available"), waits **2.5s**, then signs out and reloads — so tokens, `localStorage`, and the persisted React Query cache (`localStorage["commerce"]`) all reset to the new build.
28
+
29
+ Ported from toga25-supply (`force-logout-on-deployment`). Two commerce-only differences:
30
+ - **Mounted as `<DeploymentGuard />` inside `AuthLayout`**, not in `AuthContext`. `ToasterProvider` sits **below** `AuthProvider` in `App.tsx`, so the context that owns sign-out cannot raise a toaster; the guard component is inside both providers.
31
+ - **Baseline lives only in `localStorage["deployment-version"]`**, listed in `AUTH_STORAGE_KEYS` so logout clears it. No baseline → the check is skipped, which is what stops a sign-out loop.
32
+
33
+ ## How it works
34
+
35
+ 1. Query `META_LAST_REFRESH_DATETIME`, `enabled` only while authenticated, `staleTime: 0`, `refetchOnWindowFocus: true`, `refetchInterval` **5 minutes**. The old version refetched only on navigation, so an idle tab never noticed a deploy.
36
+ 2. First observation writes the baseline and never signs out.
37
+ 3. A later differing value → toaster → 2.5s timer → `logout()` + reload.
38
+ 4. `logout()` clears `AUTH_STORAGE_KEYS`, including the baseline, so the next login just re-baselines.
39
+
40
+ ## Gotchas
41
+
42
+ - **⚠ Do not depend an effect on `addToaster`.** It is rebuilt on every `ToasterProvider` render, and raising the toaster re-renders that provider — the effect re-ran and its cleanup cleared the `setTimeout` it had just set, so the notice showed and the sign-out never fired. Keep unstable callbacks in refs and depend only on the stable value.
43
+ - **⚠ The "already fired" flag must be module scope, not `useRef`.** The guard is mounted inside `AuthLayout`; a route change remounts it and a per-mount ref resets to `false`, so the notice reappeared on every navigation. Also do not clear the timer on unmount.
44
+
45
+ ## Change history
46
+ - 2026-09-25 — Built for commerce: moved out of `AuthContext` into `<DeploymentGuard />` so a toaster can be shown; added 5-min poll + focus refetch; baseline key added to `AUTH_STORAGE_KEYS`. TRUE-81991. (apeterson)
@@ -0,0 +1,39 @@
1
+ ---
2
+ title: Skeleton → content transitions (AnimatePresence mode, layout jump, blank gap)
3
+ framework: "2.0"
4
+ repo: toga2-commerce
5
+ project: TOGa Commerce
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-09-25
10
+ owners: [apeterson]
11
+ files:
12
+ - toga2-commerce/src/pages/Account/AccountPage.tsx
13
+ - toga2-commerce/src/pages/Home/HomePage.tsx
14
+ - toga2-commerce/src/pages/Home/viewModel/useHomeViewModel.ts
15
+ related:
16
+ - ../architecture.md
17
+ - ./storefront-persona-gating-and-home-counts.md
18
+ ---
19
+
20
+ How commerce swaps a loading skeleton for real content with framer-motion; open for page jump/flicker or a blank gap while loading.
21
+
22
+ ## Summary
23
+
24
+ Page-level loading states are framer-motion `AnimatePresence` swaps between a shimmer skeleton and the loaded content, using `fadeInOutVariants` (exit duration **0.3s**).
25
+
26
+ ## How it works
27
+
28
+ `AnimatePresence` defaults to `mode="sync"`: the exiting child stays in layout while the entering child mounts, so both heights sum, then drop. Page-level swaps must use **`mode="wait"`**.
29
+
30
+ Measured on a cold `/account` load with `sync`: page height 1300 → **1962** (content mounts while the 520px shimmer is still in flow) → 1442 when the shimmer is finally removed. The 305ms between the two jumps matches the 0.3s exit exactly. With `mode="wait"`: 1300 → 1442, one step.
31
+
32
+ ## Gotchas
33
+
34
+ - **⚠ `mode="sync"` (the default) double-counts height** on any skeleton→content swap. Two visible jumps.
35
+ - **⚠ Open — HomePage shows a blank white gap, not a jump.** `src/pages/Home/HomePage.tsx` (~line 488) renders `{!isLoading && (<AnimatePresence>{filteredItems}</AnimatePresence>)}`. `isLoading` comes from `homeDataState.status !== "ready"` in `useHomeViewModel` and flips false **before** `filteredItems` is populated, so the skeleton unmounts with nothing to replace it. Frame-by-frame: 4.10s skeleton cards → 4.30s blank card area → 4.40s real cards. Fix belongs in the view model's readiness logic — and must not treat "filteredItems is empty" as not-ready, or a legitimate no-results state renders a skeleton forever.
36
+ - **⚠ The `mode="wait"` fix is not shipped.** It sits in `git stash@{0}` in toga2-commerce as of 2026-09-25, unreviewed.
37
+
38
+ ## Change history
39
+ - 2026-09-25 — Documented `AnimatePresence` sync-vs-wait layout jump (measured) and the still-open HomePage blank gap. (apeterson)
@@ -87,5 +87,6 @@ api2 **rotates** the refresh token. Concurrent 401s each spent a token the other
87
87
 
88
88
  ## Change history
89
89
  - 2026-09-25 — Recorded that `GET /domains` is cached (`Core.Records` id 1, `ttlCache = 15`), the likely reason freshly inserted SSO rows still fall through to `/login` (rgirish)
90
+ - 2026-09-25 — Deep links now survive sign-in: `PrivateRoute` saves before redirecting, one consumer per path, `onSuccess` navigates removed; header/footer logos point at `/home`. Known gap: the saml gateway always returns to `/`. TRUE-81991. (apeterson)
90
91
  - 2026-09-22 — Public token moved out of `localStorage`; `hasSession()` + identity epoch + `AUTH_STORAGE_KEYS` added; SSO sign-in screen replaces the auto IdP jump; open-redirect and SSO-URL-scheme holes closed; single-flight refresh. TRUE-81991 (`e546db3d`). (apeterson)
91
92
  - 2026-09-21 — Blank page at `/` fixed: gate on stored `user.uuid`, `AuthRedirect` renders the spinner instead of `null`. Branch `TRUE-82172` (`1eebdd96`, `3dc53d9a`), **not merged or deployed**. (tcox)
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: Running toga2-commerce locally (tenant hosts, not localhost)
3
+ framework: "2.0"
4
+ repo: toga2-commerce
5
+ project: TOGa Commerce
6
+ client: shared
7
+ type: workflow
8
+ status: active
9
+ updated: 2026-09-25
10
+ owners: [apeterson]
11
+ files:
12
+ - toga2-commerce/vite.config.ts
13
+ - toga2-commerce/package.json
14
+ related:
15
+ - ../architecture.md
16
+ - ./amplify-build-and-deploy.md
17
+ - ../features/sso-redirect-and-session-gating.md
18
+ ---
19
+
20
+ Commerce cannot run on `localhost`; use a tenant hostname in `/etc/hosts`. Open when local dev bounces straight to `/login`.
21
+
22
+ ## Steps
23
+
24
+ 1. Add to `/etc/hosts`, pointing at `127.0.0.1`: `compass.togacommerce`, `compasscanada.togacommerce`, `quad.togacommerce` (these are the exact hosts allowed in `vite.config.ts`). For `npm run sandbox-client` also add `client.sandbox.togacommerce`.
25
+ 2. Start Vite and open the tenant host, never `http://localhost:<port>`.
26
+
27
+ ## Why localhost fails
28
+
29
+ The tenant is resolved from `window.location.hostname`, and api2 checks the request `Origin` against `Core.Domains`. An unregistered origin returns **401** → the axios interceptor logs out → the app lands on `/login` with storage cleared. Copying tokens from another origin into `localStorage` does not help.
30
+
31
+ ## Gotchas
32
+
33
+ - **⚠ `npm run sandbox-client` dies with `getaddrinfo ENOTFOUND`** until `client.sandbox.togacommerce` has its own hosts entry.
34
+ - **⚠ A missing hosts entry looks like an auth bug.** The symptom is an instant redirect to `/login`, not a network error.
35
+
36
+ ## Change history
37
+ - 2026-09-25 — Documented after tracing "cannot sign in locally" to the Origin/`Core.Domains` check. (apeterson)
@@ -0,0 +1,5 @@
1
+ # toga25-blox (TOGa Blox 2.5) — 2.0 knowledge
2
+
3
+ | Doc | Summary |
4
+ |-----|---------|
5
+ | [TOGa Blox 2.5 — Architecture](architecture.md) | Shared React+TS SOURCE library for the new platform apps (toga25-desk, toga25-iq, toga2-template, future apps); read the ADR for full detail, not this stub. |
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: TOGa Blox 2.5 — Architecture
3
+ framework: "2.0"
4
+ repo: toga25-blox
5
+ project: TOGa Blox 2.5
6
+ client: shared
7
+ type: architecture
8
+ status: active
9
+ updated: 2026-09-26
10
+ owners: [jcardinal]
11
+ files:
12
+ - toga25-blox/src
13
+ related: []
14
+ ---
15
+ Shared React+TS SOURCE library for the new platform apps (toga25-desk, toga25-iq, toga2-template, future apps); read the ADR for full detail, not this stub.
16
+
17
+ ## Summary
18
+
19
+ `toga25-blox` is a fresh, **source-shared** (git, not npm) React+TS library that replaces
20
+ `toga-blox` for **new-platform apps only** (toga25-desk, toga25-iq, toga2-template, future
21
+ apps). Apps import blox **source** from a sibling clone — `@toga/blox/<entry>` resolves via
22
+ Vite alias to `../toga25-blox/src/<entry>/index.ts`. No build, no publish, no npm version.
23
+ Deploy clones `toga25-blox` on the app's own branch at build time. Old npm `toga-blox` stays
24
+ on `toga25-supply` / `toga2-commerce` / `toga2-view` / `toga2-hub` — do not port those apps to
25
+ this repo. Full decision + rejected alternatives + consequences: `docs/decisions/0001-platform-frontend-architecture.md` in the toga25-blox repo — read that for anything beyond these rules.
26
+
27
+ **Critical rules:**
28
+ - **Reuse order:** an existing `toga25-blox` piece → a piece already built in another
29
+ new-platform app (promote it to blox) → only then build new. Never duplicate.
30
+ - **Import boundary:** apps may import only `@toga/blox/<entry>` (the public entry barrel) —
31
+ lint blocks deeper paths. Blox never imports app code.
32
+ - **Layer order (nothing imports upward):** `lib` < `core` < `tenancy` < `theme` < `surface`
33
+ < `ui` < `overlay` < `forms` < `data-table`.
34
+ - **Blox changes are additive-only on a shared branch:** add the new API, migrate apps, then
35
+ remove the old one. A breaking edit can down every app building on that branch — see the
36
+ ADR's merge-gate + `BLOX_REF` pin guards before touching shared entries.
37
+ - **At kickoff, read `INVENTORY.md`** in the local `toga25-blox` checkout (path from Claude
38
+ memory `repo-path-toga25-blox`, default `C:\WWW\toga25-blox`). It is generated from code and
39
+ is the **only** list of blox pieces (entry, name, one-line docblock, consuming apps) —
40
+ **never copy it into this KB**, it goes stale immediately.
41
+ - **After changing blox, run `npm run inventory`** in `toga25-blox` (also enforced by the
42
+ blox PR check) to regenerate `INVENTORY.md`.
43
+
44
+ ## Related
45
+
46
+ - ADR: `docs/decisions/0001-platform-frontend-architecture.md` (toga25-blox repo).
47
+ - Old npm lib for legacy 2.0 apps: [toga-blox architecture](../toga-blox/architecture.md).
@@ -6,8 +6,8 @@ project: TOGa 2.5 Supply
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-16
10
- owners: [bala]
9
+ updated: 2026-09-28
10
+ owners: [bala, apeterson]
11
11
  files:
12
12
  - toga25-supply/src/routes.tsx
13
13
  - toga25-supply/src/layout/AppLayout/viewModel/useAppLayoutViewModel.ts
@@ -15,10 +15,12 @@ files:
15
15
  - _underscore/Model/Core/Surface.php
16
16
  related:
17
17
  - ./surface-frontend.md
18
+ - ./transfer-orders-page.md
18
19
  - ../../_underscore/features/surface-resolver.md
19
20
  - ../../_underscore/features/acl-permission-chain.md
20
21
  - ../../../1.0/apps/tools/features/persona-gated-navigation.md
21
22
  - ../../../../clients/compass-usa/workflows/granting-navigation-access.md
23
+ - ../../../../clients/quad/workflows/granting-internal-admin-navigation.md
22
24
  ---
23
25
 
24
26
  2.5 nav/routes/landing are 100% backend-driven by the `navigation` surface; a user with zero visible nav elements gets a BLANK page reported as "can't log in" — open when a login "fails" but auth actually succeeded.
@@ -32,6 +34,21 @@ Durable lesson: a user whose roles grant **zero** visible nav elements gets a **
32
34
  ## How it works
33
35
 
34
36
  1. `useAppLayoutViewModel` fetches `GET /v2/surfaces/meta?slug=navigation` and builds `sideNavItems` from the bundle's elements: `elements.filter((el) => el.isVisible && elementPath(el))`, sorted by `sortOrder`. `isVisible` comes from the server-side `SurfaceOverrides` cascade — see [Surface Resolver](../../_underscore/features/surface-resolver.md).
37
+
38
+ **Every nav element ships Core-base `isVisible = 0`**, so with no override the entry is hidden for
39
+ everyone. The canonical `Core.SurfaceElements` map for `Core.Surfaces.id = 39` (slug `navigation`) —
40
+ the ids you need to write an override row:
41
+
42
+ | Element id | `config.path` | Nav item | `sortOrder` |
43
+ |---|---|---|---|
44
+ | 109 | `sales-orders` | Sales Orders | 10 |
45
+ | 110 | `inventory` | Inventory | 20 |
46
+ | 111 | `items` | Items | 30 |
47
+ | 112 | `vendor-items` | Vendor Items | 40 |
48
+ | 113 | `bundles` | Bundles | 50 |
49
+ | 114 | `service-requests` | Service Requests | 60 |
50
+ | 190 | `transfer-orders` | Transfer Orders | — |
51
+
35
52
  2. It exposes `sideNavPaths` (same list, leading slash stripped) plus `isNavReady = !isLoading && surfaceMeta !== null`.
36
53
  3. `routes.tsx` → `AllowedRoutes` maps `sideNavPaths` through a **`ROUTE_REGISTRY`** keyed by path segment (`sales-orders`, `items`, `inventory`, `vendor-items`, `bundles`, `service-requests`), drops unknown keys, and takes the **first survivor** as the landing route:
37
54
 
@@ -79,3 +96,6 @@ That trailing silence is the tell: the app never picked a page, so it never aske
79
96
  - **`isNavReady` is not an ACL signal, it is a "bundle arrived" signal.** `routes.tsx` aliases it as `isAclReady`, which reads like a permission check but is only `!isLoading && surfaceMeta !== null`. An empty-but-successful bundle is "ready".
80
97
  - **Do not chase `AclActionPermissions` for a missing 2.5 menu item.** The `navigation-*` `Core.AclActions` rows are inert here — no `SurfaceElement` on `appId = 1` declares an `aclActionId`. The only lever is a role-scoped `SurfaceOverrides` `IS_VISIBLE = '1'` row. Full reasoning in [acl-permission-chain](../../_underscore/features/acl-permission-chain.md).
81
98
  - **Verifying a nav fix requires a LOGOUT, not a reload.** The surface bundle is cached with `staleTime`/`gcTime` `Infinity` and persisted to `localStorage` under `supply-chain-query-cache`. See [Surface Frontend](./surface-frontend.md).
99
+
100
+ ## Change history
101
+ - 2026-09-28 — Added the canonical `Core.SurfaceElements` id → path map for surface 39 (incl. 190 `transfer-orders`); it was previously only in the Compass workflow doc (apeterson)
@@ -115,7 +115,7 @@ repo: worker2 # on-disk repo (matches folder + registry); omit for p
115
115
  project: Worker # logical project name (from registry)
116
116
  client: shared # "shared" for app docs; a real client slug for client docs
117
117
  type: feature # feature | client-feature | workflow | architecture | standard
118
- status: active # active | draft | deprecated
118
+ status: active # active | draft | deprecated | deferred
119
119
  updated: 2026-06-08
120
120
  owners: [jcardinal]
121
121
  files: # the doc's PRIMARY source files only (doc → code bridge) — a handful
@@ -165,6 +165,30 @@ standalone doc. Only a recurring/systemic failure class warrants its own record,
165
165
  _Elevated_ docs (architecture, standard) are senior-owned; `capture` flags any change
166
166
  to them with ⚠ so the developer knows what they're approving.
167
167
 
168
+ ## Where knowledge lives (one place per fact)
169
+
170
+ Every fact has exactly one home — never duplicate it into a second place.
171
+
172
+ - **Generated-from-code files live with the code**, not in the KB — e.g. `toga25-blox`'s
173
+ `INVENTORY.md`, a repo's docs index. They regenerate from source and go stale the moment
174
+ they're copied elsewhere. Kickoff/capture may be told to *read* one; never to *paste* it in.
175
+ - **Everything a person must know lives in the team KB** — decisions, rules, gotchas,
176
+ architecture. Not a generated file, not a stale wiki page.
177
+ - **App docs lifecycle (pre-MVP → launch → post-launch):**
178
+ 1. **Before MVP**, an in-development app's knowledge lives in the app repo's own `docs/`.
179
+ The KB carries only a `registry.json` entry + a small `architecture.md` **stub**
180
+ pointing at the repo's docs (see `toga25-desk`/`toga25-iq` stubs) — never the real
181
+ content.
182
+ 2. **At MVP launch**, all of that repo `docs/` knowledge **moves into the KB** (proper
183
+ `architecture.md` + `features/*.md`), and the stub is replaced.
184
+ 3. **After launch**, the repo keeps only `README`, `CLAUDE.md`, and generated files
185
+ (inventories, indexes) — the durable knowledge lives in the KB, not the repo.
186
+ - **Deferred features** (designed, not yet built) discovered after an app's launch go in
187
+ that app's KB folder as `type: feature`, `status: deferred` — not a separate backlog doc.
188
+ - **The app template's starter docs** (in `toga2-template`) are the **template itself** —
189
+ boilerplate every new app copies and edits — not app knowledge about any one app. They stay
190
+ in that repo, not the KB.
191
+
168
192
  ## Required sections
169
193
 
170
194
  Every **feature**, **client-feature**, and **workflow** doc must include a
@@ -22,7 +22,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
22
22
  - **_underscore** (_Underscore) _(framework core)_ — 90 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
23
23
  - **worker2** (Worker) — 80 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
24
24
  - **api2** (API) — 26 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
25
- - **dbchanges2** (Database Changes) _(framework core)_ — 20 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
25
+ - **dbchanges2** (Database Changes) _(framework core)_ — 21 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
26
26
  - **toga2-supply** (TOGa Supply) — 9 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
27
27
  - **saml** (SAML SSO Gateway) — 9 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
28
28
  - **apiproxy** (API Proxy) — 1 doc(s) → [2.0/apps/apiproxy/INDEX.md](2.0/apps/apiproxy/INDEX.md)
@@ -32,10 +32,11 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
32
32
  - **talos-backend** (TOGa Talos Backend) — 1 doc(s) → [2.0/apps/talos-backend/INDEX.md](2.0/apps/talos-backend/INDEX.md)
33
33
  - **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
34
34
  - **ai-bdr** (AI-BDR) — 13 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
35
- - **toga2-commerce** (TOGa Commerce) — 26 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
35
+ - **toga2-commerce** (TOGa Commerce) — 29 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
36
36
  - **toga25-supply** (TOGa 2.5 Supply) — 22 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
37
37
  - **toga25-iq** (TOGa IQ) — 1 doc(s) → [2.0/apps/toga25-iq/INDEX.md](2.0/apps/toga25-iq/INDEX.md)
38
38
  - **toga25-desk** (TOGa Desk 2.5) — 1 doc(s) → [2.0/apps/toga25-desk/INDEX.md](2.0/apps/toga25-desk/INDEX.md)
39
+ - **toga25-blox** (TOGa Blox 2.5) — 1 doc(s) → [2.0/apps/toga25-blox/INDEX.md](2.0/apps/toga25-blox/INDEX.md)
39
40
  - **toga-blox** (TOGa Blox) — 15 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
40
41
  - **bdr** (BDR) — 0 doc(s) → [2.0/apps/bdr/INDEX.md](2.0/apps/bdr/INDEX.md)
41
42
 
@@ -27,6 +27,7 @@
27
27
  | [Persona Model & Levy-Sector Gating (worker2 PEOPLE cron)](features/persona-model-and-levy-gating.md) | 2.0 | Compass USA catalogue visibility is driven by **personas** in `Client_Compass`. |
28
28
  | [Compass Sales-Order Line-Number Renumbering (and why it hangs off the SO hooks)](features/sales-order-line-renumbering.md) | 2.0 | How Compass keeps SO lines numbered 1..N with no gaps, why the renumber rides the parent SO hooks, and the two-phase SQL that avoids a duplicate-key fatal; open |
29
29
  | [Compass SAML SSO (Entra) — identity mapping, the @togatech.com bypass, and per-environment enablement](features/saml-sso.md) | 2.0 | How Compass users are matched from an Entra SAML assertion, the hardcoded `@togatech.com` test bypass that is live in production, and what it takes to turn SSO |
30
+ | [Compass SSO signs every @togatech.com user in as one hardcoded user (GINA PRINCE, 4273)](features/sso-devteam-user-shortcut.md) | 2.0 | Why an internal dev signing into a Compass app via SSO lands as someone else; open for "I'm signed in as the wrong user" on Compass. |
30
31
  | [Stranded Approval Reassignment (repointing approvals off dead duplicate Compass Users rows)](features/stranded-approval-reassignment.md) | 2.0 | The engine that repoints approvals stranded on dead duplicate Compass Users rows onto the live account; open when approvals are stuck on a re-hired employee's o |
31
32
  | [Compass: where a `TrackingNumbers` row comes from (invoice sync, NOT the ASN) and how it reaches a customer](features/tracking-number-origin-and-asn-link.md) | 1.0 | Why a Compass TrackingNumbers row is born from the invoice sync (not the ASN) and why `dtCreated` is the wrong clock for any shipping notification/SLA; open bef |
32
33
  | [Compass USA](profile.md) | 2.0 | Compass USA client profile: platforms, vendors/integrations, and the key features/traps across its 1.0+2.0 supply-chain commerce stack; open first when starting |
@@ -0,0 +1,32 @@
1
+ ---
2
+ title: Compass SSO signs every @togatech.com user in as one hardcoded user (GINA PRINCE, 4273)
3
+ framework: "2.0"
4
+ project: _Underscore
5
+ client: compass-usa
6
+ type: client-feature
7
+ status: active
8
+ updated: 2026-09-25
9
+ owners: [apeterson]
10
+ files:
11
+ - _underscore/Model/Compass/ClientAuthentication.php
12
+ related:
13
+ - ../profile.md
14
+ - ../../../2.0/apps/toga2-commerce/features/sso-redirect-and-session-gating.md
15
+ ---
16
+
17
+ Why an internal dev signing into a Compass app via SSO lands as someone else; open for "I'm signed in as the wrong user" on Compass.
18
+
19
+ ## Summary
20
+
21
+ `_Model_Compass_ClientAuthentication::getAuthenticatedSsoUser()` checks the SAML NameID. If it contains `@togatech.com` it returns `new _Model_Compass_User(4273)` (**GINA PRINCE**, `_isSuperAdmin = 1`) instead of matching on `c_hrEmpUsername`. The source comment says "return gina's user if testing on devteam".
22
+
23
+ So **every** TOGA employee signing into **any** Compass app through SSO is that one shared super-admin user. Deliberate test shortcut, not a bug — but it costs real time to trace, because the behaviour starts in the front end and only shows up down in the framework model.
24
+
25
+ ## Gotchas
26
+
27
+ - **⚠ Shared identity.** Two devs signed in at once are the same user — carts, orders and audit rows attribute to 4273.
28
+ - **⚠ Elevated by accident.** That user is a super admin, so devteam testing never sees normal-permission behaviour.
29
+ - **⚠ Not reachable by credentials.** Bypassing it needs a non-SSO sign-in path; see the proposed `/login?local=1` flag in the commerce SSO doc.
30
+
31
+ ## Change history
32
+ - 2026-09-25 — Recorded after tracing a wrong-user SSO sign-in from commerce through the saml gateway into `_underscore`. (apeterson)
@@ -39,6 +39,7 @@ related:
39
39
  - features/oneuptime-ma-refresh-order-monitor.md
40
40
  - workflows/catalog-fold-reversal.md
41
41
  - features/asn-to-item-fulfillment.md
42
+ - features/sso-devteam-user-shortcut.md
42
43
  - features/odp-strategic-systems-asn-csv-import.md
43
44
  - features/multi-tier-tracking-and-serial-mirroring.md
44
45
  - features/order-fulfillment-status-per-line.md
@@ -6,7 +6,7 @@ project: Database Changes
6
6
  client: compass-usa
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-09-16
9
+ updated: 2026-09-28
10
10
  owners: [apeterson, bala]
11
11
  files:
12
12
  - dbchanges2/Client_Compass/2026-08-12a - NavigationRoleGrants.sql
@@ -16,6 +16,7 @@ files:
16
16
  related:
17
17
  - ./granting-persona-bundle-access.md
18
18
  - ../profile.md
19
+ - ../../quad/workflows/granting-internal-admin-navigation.md
19
20
  - ../../../2.0/apps/_underscore/features/surface-resolver.md
20
21
  - ../../../2.0/apps/dbchanges2/features/surface-layer-schema.md
21
22
  - ../../../2.0/apps/toga25-supply/features/surface-frontend.md
@@ -34,15 +35,7 @@ How to give a Compass user another user's toga25-supply menu items via role-scop
34
35
 
35
36
  So visibility = exactly one thing: a `Client_Compass.SurfaceOverrides` row with `attribute = 'IS_VISIBLE'`, `value = 1`, scoped to a `roleId` the user holds.
36
37
 
37
- `Core.SurfaceElements` ids for surface slug `navigation`:
38
- | Element id | `config.path` | Nav item | `sortOrder` |
39
- |---|---|---|---|
40
- | 109 | `sales-orders` | Sales Orders | 10 |
41
- | 110 | `inventory` | Inventory | 20 |
42
- | 111 | `items` | Items | 30 |
43
- | 112 | `vendor-items` | Vendor Items | 40 |
44
- | 113 | `bundles` | Bundles | 50 |
45
- | 114 | `service-requests` | Service Requests | 60 |
38
+ Element ids for surface 39 (`navigation`) live in [side navigation & default route](../../../2.0/apps/toga25-supply/features/side-navigation-and-default-route.md) — do not duplicate them here.
46
39
 
47
40
  The Compass override map (all `IS_VISIBLE`, `value = 1`, `personaId` NULL, `languageId` NULL):
48
41
  | Element | Roles granting visibility |
@@ -122,6 +115,7 @@ Role 16's Vendor Items gap is **left open on purpose** — widening nav for ever
122
115
  - **Role permissions resolve as a union**, so an additive `Users_Roles` row never takes access away — see [ACL Permission Chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md). The flip side is that it also grants everything else the role carries.
123
116
  - **⚠ These ids sit OUTSIDE the team-reserved blocks.** The reserved-id table in [Surface Resolver](../../../2.0/apps/_underscore/features/surface-resolver.md) and [surface-layer-schema](../../../2.0/apps/dbchanges2/features/surface-layer-schema.md) lists `Core.Surfaces` **41–43** / `Core.SurfaceElements` **125–146**. The `navigation` surface predates those blocks and lives at elements **109–114**. Read the reserved blocks as "the decision-surface reseed", not as an inventory of all surface ids.
124
117
  - **Verifying the change requires a LOGOUT, not a reload.** Surface meta is cached `staleTime`/`gcTime` `Infinity` **and persisted to `localStorage`** under `supply-chain-query-cache`, so a hard reload restores the stale bundle and proves nothing. There is no server-side surface cache. Detail in [Surface Frontend](../../../2.0/apps/toga25-supply/features/surface-frontend.md).
118
+ - **The `Agilant - Administrators` role may carry ZERO `AclFieldPermissions`.** `Client_Compass` role 9 has none — it is a nav/record-scoping role only; field reads come from the other roles the same user holds. `Client_Compasscanada` role **10** is the same role under a different id. Same pattern applied to Quad 2026-09-28: [Quad internal-admin navigation](../../quad/workflows/granting-internal-admin-navigation.md).
125
119
  - **`aclActionId` is NULL on all six nav elements** — resist the instinct to reach for `AclActionPermissions` when a menu item is missing. It is always a `SurfaceOverrides` row.
126
120
 
127
121
  ## Related
@@ -133,3 +127,6 @@ Role 16's Vendor Items gap is **left open on purpose** — widening nav for ever
133
127
  - [Re-runnable additive INSERTs](../../../2.0/apps/dbchanges2/features/rerunnable-additive-inserts.md)
134
128
  - [ACL Permission Chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md)
135
129
  - [Side navigation & default route](../../../2.0/apps/toga25-supply/features/side-navigation-and-default-route.md)
130
+
131
+ ## Change history
132
+ - 2026-09-28 — Element-id map moved to the shared side-nav doc; recorded that role 9 holds no `AclFieldPermissions` and that Compass Canada's equivalent is role 10 (apeterson)
@@ -7,3 +7,4 @@
7
7
  | [Quad: PO + ASN email importer (import_po_and_asn.php)](features/po-asn-email-import.md) | 1.0 | Quad's inbox-driven PO/ASN CSV importer and its 2026-08 failure classes; open when a Quad PO/ASN email jams the mailbox, fails EV-12, or you touch `import_po_an |
8
8
  | [Quad Graphics](profile.md) | 2.0 | Quad Graphics is a TOGA 2.0 client on the `_underscore` platform, prod schema `Client_Quad`. |
9
9
  | [Quad Graphics — reversing the 30-item catalog fold (storefront-assortment recovery)](workflows/catalog-fold-reversal.md) | 2.0 | How the 2026-08-31 catalog-blind item merge damaged Quad's storefront and how the reversal was built; open when assessing/repairing catalog-fold damage on a two |
10
+ | [Giving Quad's internal TOGA staff extra toga25-supply nav (the `Agilant - Administrators` role pattern)](workflows/granting-internal-admin-navigation.md) | 2.0 | How to surface extra 2.5 Supply pages to internal TOGA staff on a tenant without showing them to the client's own users — Quad's first internal-admin role; open |
@@ -16,12 +16,13 @@ project: _Underscore
16
16
  client: quad
17
17
  type: profile
18
18
  status: active
19
- updated: 2026-09-17
19
+ updated: 2026-09-28
20
20
  owners: ["jcardinal", "bala", "apeterson", "ajean"]
21
21
  files: []
22
22
  related:
23
23
  - ../../2.0/apps/toga2-commerce/features/storefront-persona-gating-and-home-counts.md
24
24
  - ./workflows/catalog-fold-reversal.md
25
+ - ./workflows/granting-internal-admin-navigation.md
25
26
  - ./features/po-asn-email-import.md
26
27
  - ./features/enter-po-details-vendor-selection-ev12.md
27
28
  - ../../2.0/apps/_underscore/features/sales-order-po-number-sourcing.md
@@ -162,4 +163,9 @@ Client-specific DB change-sets live in `dbchanges2/Client_Quad/`.
162
163
  but not catalog 2. Mechanism:
163
164
  [TableView apiWhereClause row-filtering](../../2.0/apps/api2/features/tableview-apiwhereclause-row-filtering.md);
164
165
  catalog background: [Compass item catalogs](../compass-usa/features/item-catalogs-and-duplicate-items.md).
166
+ - **Internal-staff nav is its own role (2026-09-28).** Quad now follows the Compass
167
+ `Agilant - Administrators` pattern: a dedicated role carries the 2.5 Supply `SurfaceOverrides`
168
+ nav grants for Items/Vendor Items so Quad's own `@quad.com` users (five of the seven holders of
169
+ role 7 `Global Admin`) do not get them. Migration written, **not yet run**. See
170
+ [Quad internal-admin navigation](./workflows/granting-internal-admin-navigation.md).
165
171
  - This profile is a starting point; expand as more Quad-specific behavior is captured.
@@ -0,0 +1,81 @@
1
+ ---
2
+ title: Giving Quad's internal TOGA staff extra toga25-supply nav (the `Agilant - Administrators` role pattern)
3
+ framework: "2.0"
4
+ repo: dbchanges2
5
+ project: Database Changes
6
+ client: quad
7
+ type: workflow
8
+ status: active
9
+ updated: 2026-09-28
10
+ owners: ["apeterson"]
11
+ files:
12
+ - dbchanges2/Client_Quad/2026-09-28a - AgilantAdministratorsRoleAndItemsNav.sql
13
+ related:
14
+ - ../profile.md
15
+ - ../../compass-usa/workflows/granting-navigation-access.md
16
+ - ../../../2.0/apps/toga25-supply/features/side-navigation-and-default-route.md
17
+ - ../../../2.0/apps/_underscore/features/acl-permission-chain.md
18
+ - ../../../2.0/apps/dbchanges2/features/rerunnable-additive-inserts.md
19
+ ---
20
+
21
+ How to surface extra 2.5 Supply pages to internal TOGA staff on a tenant without showing them to the client's own users — Quad's first internal-admin role; open for "internal user can't see page X" on any tenant.
22
+
23
+ ## Summary
24
+
25
+ Internal TOGA staff need pages (Items, Vendor Items, Bundles) that the tenant's own staff must
26
+ **not** see. The house pattern is a **dedicated `Agilant - Administrators` role** that carries the
27
+ nav overrides and nothing else — `Client_Compass` role **9**, `Client_Compasscanada` role **10**,
28
+ and as of 2026-09-28 a new role on `Client_Quad`. Nav gating itself is the shared
29
+ [`SurfaceOverrides IS_VISIBLE` mechanism](../../../2.0/apps/toga25-supply/features/side-navigation-and-default-route.md)
30
+ (element-id map lives there); this doc is only about **which role** gets the override.
31
+
32
+ Never hang an internal nav grant on a shared admin role. `Client_Quad` role **7** (`Global Admin`)
33
+ is held by **7** users, **five of them `@quad.com`** (MNICHOLS, JOSHUAMARTI, DJPUTNEY, mnaber,
34
+ JACEDWARDS) — adding elements 111/112 there would have shown Items and Vendor Items to Quad's own
35
+ staff. That is a tenant-wide behavior change dressed up as a per-user fix.
36
+
37
+ ## Steps
38
+
39
+ 1. **Check whether the data ACL is already in place** — usually it is, making this nav-only.
40
+ `Client_Quad.AclRecordPermissions` already grants record **19** (`vendor-items`) to role **1**
41
+ (Base), full CRUD, `appId` NULL, and record **21** (`items`) to role **1** at `appId = 1`. So
42
+ every Quad user could already *read* the data; only the menu entry was missing.
43
+ 2. **Count the users on the candidate role before granting on it** (`Users_Roles` joined to
44
+ `Users`). Any `@<client-domain>` holder disqualifies the role for an internal-only grant.
45
+ 3. **Create a dedicated `Agilant - Administrators` role** in the client DB if the tenant has none,
46
+ and assign only the internal users the developer names. Quad got **`devteam@togatech.com` only**;
47
+ the other three `@togatech.com` Quad users (JWise, NSchenkel, rgreen) were deliberately left off.
48
+ 4. **Add role-scoped `SurfaceOverrides` rows** (`surfaceId` 39, the element ids, `IS_VISIBLE` = `'1'`,
49
+ `personaId`/`languageId` NULL) for that new role. Nothing else — no `AclRecordPermissions`, no
50
+ `AclFieldPermissions`, so the migration cannot widen data access.
51
+ 5. **Hand the file to the developer to run.** Verify in the app by **logging out and back in** —
52
+ the surface bundle is cached in `localStorage`, so a reload proves nothing.
53
+
54
+ ### The 2026-09-28 Quad migration
55
+ `dbchanges2/Client_Quad/2026-09-28a - AgilantAdministratorsRoleAndItemsNav.sql` — committed
56
+ `a890c26` on branch `TRUE-82246`, **not yet run in any environment.** Three guarded statements,
57
+ all inside `Client_Quad`: create the role, assign `devteam@togatech.com`, insert two
58
+ `SurfaceOverrides` rows for elements **111** (`items`) and **112** (`vendor-items`). Core ids
59
+ (39, 111, 112) are **hardcoded literals** — `Core` and `Client_<tenant>` are separate prod
60
+ clusters, so the statement cannot join `Core.SurfaceElements` to resolve them.
61
+
62
+ Quad's starting state for contrast: **one** nav override total (element 109 `sales-orders`, `roleId`
63
+ **NULL** — i.e. tenant-wide) and no internal-admin role at all.
64
+
65
+ ## Gotchas
66
+
67
+ - **⚠ `Core.Apps` id 1 = TOGa Supply, id 5 = TOGa Commerce.** Quad's role-7 grant on record 21
68
+ (`items`) is `appId = 5` — **Commerce, not Supply**. It reads like "Global Admin can already see
69
+ Items in Supply" and it cannot. Always read the `appId` on an `AclRecordPermissions` row before
70
+ concluding a role has Supply access.
71
+ - **The internal-admin role may carry zero `AclFieldPermissions`.** `Client_Compass` role 9 has
72
+ **none** — it is a nav/record-scoping role only, and field reads come from the other roles the
73
+ same user holds. An empty field-permission set on this role is correct, not a broken grant.
74
+ - **⚠ These facts were read from a LOCAL (beta/sandbox-derived) copy.** The prod client reader
75
+ `reader1.client.database.togahub.com` was unreachable on 2026-09-28 (`ERROR 2002 … (36)`).
76
+ Re-verify role ids, user lists and existing overrides against production before running the
77
+ migration there.
78
+
79
+ ## Change history
80
+ - 2026-09-28 — Created; Quad's first `Agilant - Administrators` role + Items/Vendor Items nav
81
+ overrides written (not yet run), scoped away from `Global Admin` role 7 (apeterson)
@@ -223,13 +223,24 @@
223
223
  "project": "TOGa IQ",
224
224
  "framework": "2.0",
225
225
  "role": "app",
226
- "dependsOn": []
226
+ "dependsOn": [
227
+ "toga25-blox"
228
+ ]
227
229
  },
228
230
  {
229
231
  "repo": "toga25-desk",
230
232
  "project": "TOGa Desk 2.5",
231
233
  "framework": "2.0",
232
234
  "role": "app",
235
+ "dependsOn": [
236
+ "toga25-blox"
237
+ ]
238
+ },
239
+ {
240
+ "repo": "toga25-blox",
241
+ "project": "TOGa Blox 2.5",
242
+ "framework": "2.0",
243
+ "role": "app",
233
244
  "dependsOn": []
234
245
  },
235
246
  {
package/knowledge.js CHANGED
@@ -38,6 +38,9 @@ const REGISTRY = path.join(ROOT, 'registry.json');
38
38
  const FRAMEWORKS = ['1.0', '2.0', 'standalone'];
39
39
  const DOC_TYPES = ['feature', 'client-feature', 'workflow', 'architecture', 'standard', 'profile'];
40
40
  const ELEVATED_TYPES = ['architecture', 'standard'];
41
+ // deferred = designed but not yet built (post-launch backlog living on the feature doc
42
+ // itself, not a separate backlog file) — loads and searches exactly like `active`.
43
+ const DOC_STATUSES = ['active', 'draft', 'deprecated', 'deferred'];
41
44
 
42
45
  /* Prime-cost guardrails — WARN-ONLY (never fail a commit/capture). These are
43
46
  * creep detectors, not hard limits, so a genuinely long-but-needed doc is allowed.
@@ -311,7 +314,7 @@ function cmdPreflight(args) {
311
314
  if (isChosen) {
312
315
  features = docs
313
316
  .filter(d => d.file.startsWith(repoDir + path.sep) && d.data.type !== 'architecture' && matchQ(d))
314
- .map(d => ({ path: d.rel, title: d.data.title || '', files: (d.data.files || []).join(', ') }));
317
+ .map(d => ({ path: d.rel, title: d.data.title || '', files: (d.data.files || []).join(', '), status: d.data.status || '' }));
315
318
  }
316
319
  const summary = (!isChosen && archDoc) ? (extractSection(archDoc.body, 'Summary') || firstSentence(archDoc.body)) : '';
317
320
  return {
@@ -356,7 +359,7 @@ function cmdPreflight(args) {
356
359
  .filter(d => path.basename(d.file) !== 'profile.md')
357
360
  .filter(d => !frameworks.length || !d.data.framework || frameworks.includes(d.data.framework))
358
361
  .filter(matchQ)
359
- .map(d => ({ path: d.rel, title: d.data.title || '', framework: d.data.framework || '', type: d.data.type || '' }));
362
+ .map(d => ({ path: d.rel, title: d.data.title || '', framework: d.data.framework || '', type: d.data.type || '', status: d.data.status || '' }));
360
363
  client = {
361
364
  slug,
362
365
  title: (profileDoc && profileDoc.data.title) || slug,
@@ -385,7 +388,7 @@ function cmdPreflight(args) {
385
388
  const pushRepoRead = (r) => {
386
389
  if (r.chosen) {
387
390
  reads.push({ label: 'repo-architecture', repo: r.repo, path: r.architecture.path, exists: r.architecture.exists, content: r.architecture.exists ? contentFor(r.architecture.path) : undefined });
388
- for (const f of r.features) reads.push({ label: 'feature', repo: r.repo, path: f.path, exists: true, title: f.title, content: contentFor(f.path) });
391
+ for (const f of r.features) reads.push({ label: 'feature', repo: r.repo, path: f.path, exists: true, title: f.title, status: f.status, content: contentFor(f.path) });
389
392
  } else {
390
393
  reads.push({ label: 'architecture-summary', repo: r.repo, role: r.role, path: r.architecture.path, exists: r.architecture.exists, lazy: true, summary: r.summary });
391
394
  }
@@ -570,7 +573,7 @@ function cmdSearch(args) {
570
573
  return true;
571
574
  });
572
575
  for (const m of matches) {
573
- console.log(m.rel + '\t' + (m.data.title || '') + '\t' + (m.data.type || ''));
576
+ console.log(m.rel + '\t' + (m.data.title || '') + '\t' + (m.data.type || '') + (m.data.status === 'deferred' ? '\t[deferred]' : ''));
574
577
  }
575
578
  }
576
579
 
@@ -739,6 +742,7 @@ function cmdValidate() {
739
742
  if (!d.data[f]) { fail(`${d.rel}: missing frontmatter "${f}"`); ok = false; }
740
743
  }
741
744
  if (d.data.type && !DOC_TYPES.includes(d.data.type)) { fail(`${d.rel}: invalid type "${d.data.type}"`); ok = false; }
745
+ if (d.data.status && !DOC_STATUSES.includes(d.data.status)) { fail(`${d.rel}: invalid status "${d.data.status}"`); ok = false; }
742
746
  const isClientDoc = d.rel.startsWith('clients/');
743
747
  if (isClientDoc) {
744
748
  if (!d.data.client || d.data.client === 'shared') { fail(`${d.rel}: client doc must set a real "client"`); ok = false; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.903",
3
+ "version": "1.0.905",
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",
@@ -116,6 +116,11 @@ Assemble these fields, matching the `session-capture` agent's input contract **e
116
116
 
117
117
  That is the entire main-thread analysis job: summarize, do not investigate the KB.
118
118
 
119
+ **If this session changed `toga25-blox` source**, remind the developer to run
120
+ `npm run inventory` there (regenerates `INVENTORY.md`; also enforced by the blox PR check).
121
+ Never write `toga25-blox`'s inventory contents into the KB — it is generated-from-code and
122
+ lives only in the repo (see CONVENTIONS → "Where knowledge lives").
123
+
119
124
  ## Step 3 — Delegate to the `session-capture` subagent
120
125
 
121
126
  Spawn the subagent via the **Agent tool** with `subagent_type: session-capture`, passing the
@@ -308,6 +308,10 @@ Do this now:
308
308
  2. As you read, hold the **load-bearing facts** you'll code against: the critical rules
309
309
  (naming prefixes, DB-access layer, dispatch pattern, response envelope, migration filename
310
310
  rules), the task-relevant feature entry points and **gotchas**, and any client variations.
311
+ 2b. **If `toga25-blox` is in `loadSet`**, after absorbing its KB docs and before the Step 5
312
+ summary, also read `INVENTORY.md` in its local checkout (`repo-path-toga25-blox` memory,
313
+ default `C:\WWW\toga25-blox`) — it is the only generated, always-current list of blox
314
+ pieces (entry, name, docblock, consuming apps). Never copy its contents into the KB.
311
315
  3. When you have read **every** resolved doc, run the completion sentinel to release the gate:
312
316
  ```bash
313
317
  node "<TEAM_REPO>/knowledge.js" kickoff-primed