toga-ai 1.0.426 → 1.0.428
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.
- package/knowledge/2.0/apps/_underscore/features/acl-permission-chain.md +13 -1
- package/knowledge/2.0/apps/_underscore/features/per-client-database-connections.md +24 -1
- package/knowledge/2.0/apps/api2/INDEX.md +1 -0
- package/knowledge/2.0/apps/api2/features/record-scripts.md +160 -0
- package/knowledge/2.0/apps/toga2-view/features/query-string-builder.md +0 -2
- package/knowledge/2.0/apps/toga2-view/features/zip-validation-contact-phone.md +0 -1
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -1
- package/knowledge/2.0/apps/worker2/features/team-sprint-management.md +29 -2
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/rate/features/aig-contract-creation.md +14 -2
- package/knowledge/clients/rate/features/whole-home-warranty-purchase-guard.md +31 -0
- package/knowledge/clients/rate/profile.md +3 -2
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-23
|
|
10
10
|
owners: ["jcardinal", "mhammontree", "tcox"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -56,6 +56,13 @@ writable). Without it the field is rejected on write **and** is unreadable:
|
|
|
56
56
|
- **`AclFieldPermissions` is `UNIQUE (recordFieldId, roleId)`** (Client blank DDL) — guard inserts
|
|
57
57
|
with `NOT EXISTS`. For a **read-only, server-written** field (one populated by a `postPost`
|
|
58
58
|
interceptor rather than the API caller), grant `isWritable = 0`.
|
|
59
|
+
- **A read-only field only needs a grant on the role(s) that actually READ it — find that role by
|
|
60
|
+
mirroring a sibling field of the same kind the readers already consume**, rather than blanket-granting
|
|
61
|
+
every role. E.g. the standard FK `serviceAddressId` mirrored its sibling FK `saleItemId`, whose sole
|
|
62
|
+
reader in `Client_Rate` is **role 1** (the toga2-view service cards) — so a single role-1 read grant
|
|
63
|
+
sufficed, NOT a 1/2/4 fan-out (a cross-role probe had shown roles 1/2/4 all lacked the grant, but only
|
|
64
|
+
role 1 actually reads the field). Grant `isWritable = 0` for a server-written field; resolve the role
|
|
65
|
+
by name/subselect (ids differ per client DB). Beta-proven on Rate `Client_Rate` 2026-07-23.
|
|
59
66
|
|
|
60
67
|
### Standard field vs. custom (`c_`) field — TWO different permission tables
|
|
61
68
|
|
|
@@ -199,6 +206,11 @@ the field.
|
|
|
199
206
|
|
|
200
207
|
## Change history
|
|
201
208
|
|
|
209
|
+
- **2026-07-23** — TRUE-79533: noted that a **read-only field only needs a grant on the role(s) that
|
|
210
|
+
actually read it** — mirror a sibling field of the same kind the readers already consume rather than
|
|
211
|
+
granting every role. The standard FK `serviceAddressId` mirrored `saleItemId`'s reader, **role 1** in
|
|
212
|
+
`Client_Rate` (the toga2-view service cards), so a single role-1 read grant sufficed (not a 1/2/4
|
|
213
|
+
fan-out). (tcox)
|
|
202
214
|
- **2026-07-23** — TRUE-79533: documented the **READ side** of `AclFieldPermissions` — a registered
|
|
203
215
|
field with no grant returns **`EZ-2`** on read (the counterpart of the write code `EV-9`), and
|
|
204
216
|
the **standard vs. custom** split: standard fields use `AclFieldPermissions` (keyed by
|
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-07-23
|
|
10
|
-
owners: ["dfranks", "jcardinal", "mhammontree", "apeterson"]
|
|
10
|
+
owners: ["dfranks", "jcardinal", "mhammontree", "apeterson", "kyalamarthi"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Database.php
|
|
13
13
|
- _underscore/ApiRequest.php
|
|
@@ -75,6 +75,22 @@ per-client), region-aware (1=us-east-1, 2=us-west-2, 3=eu-west-1) with per-regio
|
|
|
75
75
|
registration is region-aware in `api2/Controller/Index.php`; the alias const is in `api2/_.php`.
|
|
76
76
|
`Records.ttlCache` (TINYINT UNSIGNED, default 15) governs cache TTL.
|
|
77
77
|
|
|
78
|
+
### The `Team` schema rides the Core cluster (`DB_TEAM`)
|
|
79
|
+
|
|
80
|
+
The **`Team` schema** (tables `Tasks`, `Sprints`; models `_Model_Team_Task` / `_Model_Team_Sprint`
|
|
81
|
+
with `const DATABASE = _underscore::DB_TEAM`) is **not a first-class api2 database** — it is not a
|
|
82
|
+
value of `Core.Records.aclDatabase` (that enum is only `'CORE'` / `'CLIENT'`). It physically
|
|
83
|
+
resolves to the **core cluster**: `DB_TEAM` **reads** route to `reader1.core.database.togahub.com`
|
|
84
|
+
and **writes** to `writer.core.database.togahub.com`. So a `DB_TEAM` SELECT auto-routes to the core
|
|
85
|
+
reader, and a Record backed by the `Team` schema must be registered with **`aclDatabase = 'CORE'`**
|
|
86
|
+
(see [Record Scripts](../../api2/features/record-scripts.md)).
|
|
87
|
+
|
|
88
|
+
**Where DB hosts come from — the split:** Core and other **non-client** cluster hosts (Core, the
|
|
89
|
+
`Team`-schema-bearing core cluster, etc.) come from the api2 `Config/*.ini` **`[database]`
|
|
90
|
+
section**, not from `DatabaseHosts`. **Per-client** hosts come from the `Core.DatabaseHosts` table,
|
|
91
|
+
resolved by `_Database::registerClientDatabases()`. (Credential/host *values* are not reproduced
|
|
92
|
+
here — they live in `Config/*.ini`.)
|
|
93
|
+
|
|
78
94
|
## Gotchas / known issues
|
|
79
95
|
|
|
80
96
|
- **The "logs DB write trap" (local dev).** A laptop usually imports only `Client_<Id>`, not
|
|
@@ -108,6 +124,13 @@ registration is region-aware in `api2/Controller/Index.php`; the alias const is
|
|
|
108
124
|
|
|
109
125
|
## Change history
|
|
110
126
|
|
|
127
|
+
- 2026-07-23 — Documented that the **`Team` schema** (`Tasks`/`Sprints`, `_Model_Team_*`,
|
|
128
|
+
`DB_TEAM`) is not a first-class api2 DB (not in `Records.aclDatabase`, which is CORE/CLIENT
|
|
129
|
+
only) and physically resolves to the **core cluster** (reads → `reader1.core…`, writes →
|
|
130
|
+
`writer.core…`), so `DB_TEAM` SELECTs auto-route to the core reader and a `Team`-backed Record
|
|
131
|
+
registers as `aclDatabase = 'CORE'`. Also recorded the host-resolution split: Core/non-client
|
|
132
|
+
hosts come from api2 `Config/*.ini` `[database]`; per-client hosts come from `DatabaseHosts`
|
|
133
|
+
via `registerClientDatabases()`. (kyalamarthi)
|
|
111
134
|
- 2026-07-23 — Documented the shared **Core Logs** (`DB_LOGS`) analogue of the logs trap: its schema
|
|
112
135
|
name is resolved from a `Core.Database` row (`id = CORE_LOGS_DATABASE_ID`) in
|
|
113
136
|
`api2/Controller/Index.php::api()`, so a name-mismatched local Logs DB still reads as missing; and
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
| [Encrypted-User-UUID Auth Handoff (/auth/encrypted-user-uuid)](features/encrypted-user-uuid-auth-handoff.md) | `POST /auth/encrypted-user-uuid` is the intended **cross-client / SSO-handoff identity mechanism**: given an encrypted `{client, user}` UUID pair, it mints a fr | api2/Component/Api/CrossClient/CrossClient.php |
|
|
8
8
|
| [Language Translation Layer (audience.language + sidecar tables)](features/language-translation-layer.md) | Serves the same TOGa data (Item title/description/longDescription, plus item **feature** text — `Features.name`, `ItemCategoryFeatureGroups.name`, `ItemFeatures | api2/Component/Api/V2/V2.php, api2/Component/Api/V2/Response/Response.php, _underscore/Model/Core/Setting.php, _underscore/Model/Core/RecordField.php, _underscore/Model/Core/DefaultGlobalSetting.php, _underscore/Model/Client/ItemTranslation.php, _underscore/Model/Client/FeatureTranslation.php, _underscore/Model/Client/ItemCategoryFeatureGroupTranslation.php, _underscore/Model/Client/ItemFeatureTranslation.php, dbchanges2/Client/2026-06-23a - ItemTranslations.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql, dbchanges2/Client/2026-07-13a - FeatureTranslations.sql, dbchanges2/Client/2026-07-13b - FeatureTranslationsAcl.sql, dbchanges2/Core/2026-06-23a - RecordFieldsTranslationColumn.sql, dbchanges2/Core/2026-06-23b - ItemTranslationsRecord.sql, dbchanges2/Core/2026-07-13 - FeatureTranslationsRecord.sql |
|
|
9
9
|
| [Nested-relationship writes & child matching (link vs. create)](features/nested-relationship-writes.md) | When a 2.0 API write payload (`POST`/`PUT`) contains a **nested related object** (e.g. | api2/Component/Api/V2/V2.php |
|
|
10
|
+
| [Record Scripts (computed/aggregate /v2 endpoints — the authoring contract)](features/record-scripts.md) | In api2 you almost never write a controller. | api2/Component/Api/V2/V2.php, _underscore/Model/Team/Sprint.php |
|
|
10
11
|
| [POST + JSON-body args for scripted APIs](features/scripted-api-post-body-args.md) | The V2 engine can run a Record Script (scripted API) for a **POST** request, and a scripted API can receive its arguments from the **JSON request body** instead | api2/Component/Api/V2/V2.php |
|
|
11
12
|
| [Surface action-state via the surface=<slug> request option (M2M-safe)](features/surface-meta-option.md) | An opt-in V2 engine request option, `surface=<slug>`, that attaches per-record UI action state (`isVisible`/`isEnabled`) to a GET response **under `meta.surface | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Surface.php |
|
|
12
13
|
| [TableView row-filtering via apiWhereClause (options.where grammar, end to end)](features/tableview-apiwhereclause-row-filtering.md) | `TableViews.apiWhereClause` (TEXT, nullable) is the sanctioned, code-free way to restrict or exclude rows from a 2.0 table view. | api2/Component/Api/V2/V2.php, _underscore/Model/Client/TableView.php, toga2-supply/src/api/toga.ts |
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Record Scripts (computed/aggregate /v2 endpoints — the authoring contract)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: api2
|
|
5
|
+
project: API
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-23
|
|
10
|
+
owners: ["kyalamarthi"]
|
|
11
|
+
files:
|
|
12
|
+
- api2/Component/Api/V2/V2.php
|
|
13
|
+
- _underscore/Model/Team/Sprint.php
|
|
14
|
+
related:
|
|
15
|
+
- ./scripted-api-post-body-args.md
|
|
16
|
+
- ./tableview-apiwhereclause-row-filtering.md
|
|
17
|
+
- ../architecture.md
|
|
18
|
+
- ../../_underscore/features/per-client-database-connections.md
|
|
19
|
+
- ../../worker2/features/team-sprint-management.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
|
|
24
|
+
In api2 you almost never write a controller. The generic **/v2 engine** serves models from
|
|
25
|
+
metadata (Records / RecordFields / ACL rows), so ordinary CRUD is a *data* change, not code.
|
|
26
|
+
For a payload the metadata CRUD path cannot express — a **computed value, an aggregate, or a
|
|
27
|
+
purpose-built dashboard tile** — the mechanism is a **Record Script**: a `public static`
|
|
28
|
+
method on the record's `_Model` whose return value becomes the `/v2` envelope `data`. This is
|
|
29
|
+
the company-standard way to add a read-only, non-CRUD endpoint.
|
|
30
|
+
|
|
31
|
+
This doc is the **authoring contract** for a Record Script (the general pattern and how to
|
|
32
|
+
register one). For the POST + JSON-request-body variant (large argument values), see
|
|
33
|
+
[POST + JSON-body args for scripted APIs](./scripted-api-post-body-args.md).
|
|
34
|
+
|
|
35
|
+
Motivating example: `_Model_Team_Sprint::committedTile()` — a read-only dashboard tile over
|
|
36
|
+
the internal TOGa IQ sprint (`Team` schema) data. **Decision recorded:** internal
|
|
37
|
+
sprint/dashboard read endpoints are served from **api2 via a Record Script**, not by building
|
|
38
|
+
a bespoke controller or standing the data up inside the talos / TOGa IQ app.
|
|
39
|
+
|
|
40
|
+
## The method contract
|
|
41
|
+
|
|
42
|
+
A Record Script is a static method on the record's model — for the example above, the
|
|
43
|
+
signature is `public static function committedTile(&$api, int|string $sprint): object` on
|
|
44
|
+
`_underscore/Model/Team/Sprint.php`. Its return value becomes the `/v2` envelope `data`.
|
|
45
|
+
|
|
46
|
+
Rules the engine enforces (see `getRecordScriptPhpMethod()` + the dispatch in
|
|
47
|
+
`processRoutePairs()` inside `api2/Component/Api/V2/V2.php`):
|
|
48
|
+
|
|
49
|
+
- **First parameter is `&$api`** — the engine always sets `$args['api'] = $this` last, so the
|
|
50
|
+
method receives the live V2 engine instance (for auth context, `internalApiRequest()`, etc.).
|
|
51
|
+
- **Remaining parameters are named** and are filled from the **query string**. Each query-string
|
|
52
|
+
key maps to the same-named PHP parameter; unmatched named args land in a `...$args` variadic
|
|
53
|
+
if the method declares one. (`transactionId` and `api` keys are reserved and never mapped.)
|
|
54
|
+
- **The return value is the envelope `data`.** Return an object/array; do not echo, and do not
|
|
55
|
+
return the bare success/error envelope — the engine wraps it.
|
|
56
|
+
- Invocation is effectively `$model::$phpMethod(...$args)`.
|
|
57
|
+
|
|
58
|
+
## Running SQL inside a Record Script
|
|
59
|
+
|
|
60
|
+
Record Scripts commonly run their own query (via `new \_Query($sql, \_underscore::DB_TEAM)`
|
|
61
|
+
then `->fetchRow()`) rather than going through model CRUD. Two framework realities to respect:
|
|
62
|
+
|
|
63
|
+
- **`_Query` has no bound-parameter support.** There is no `?`/`:name` binding. Every value
|
|
64
|
+
you place into the query text must be sanitized by hand **before** it is assembled into the
|
|
65
|
+
SQL: pass strings through `\_Database::escape()` and cast numerics with `(int)`. Record-script
|
|
66
|
+
arguments arrive straight from the query string (user input), so this sanitization is
|
|
67
|
+
mandatory — an unsanitized value in the query text is a SQL-injection hole. Treat
|
|
68
|
+
`\_Database::escape()` + `(int)` casting as the required substitute for the prepared
|
|
69
|
+
statements `_Query` does not offer.
|
|
70
|
+
- **Query cache for "live" tiles.** A dashboard tile that must reflect current data should call
|
|
71
|
+
`\_Database::useQueryCache(false)` before the query and **restore the previous flag**
|
|
72
|
+
afterward (capture the value it returns and set it back), so it doesn't serve stale cached
|
|
73
|
+
rows and doesn't leave caching globally disabled for the rest of the request.
|
|
74
|
+
- `DB_TEAM` SELECTs auto-route to the **core cluster reader** — see
|
|
75
|
+
[per-client database connections](../../_underscore/features/per-client-database-connections.md)
|
|
76
|
+
for why the `Team` schema resolves to core.
|
|
77
|
+
|
|
78
|
+
## Registering / enabling an endpoint (metadata across two databases)
|
|
79
|
+
|
|
80
|
+
Registering a Record Script is a **metadata change split across two databases plus a
|
|
81
|
+
per-client ACL grant** — it is not a code deploy. The route is
|
|
82
|
+
**`GET /v2/<recordRoute>/<scriptRoute>`**.
|
|
83
|
+
|
|
84
|
+
**Data model:**
|
|
85
|
+
|
|
86
|
+
- **`Core.Records`** (the route ↔ model row). Its **`aclDatabase` enum is only `'CORE'` or
|
|
87
|
+
`'CLIENT'`** — there is no `TEAM` value, so a `Team`-schema-backed record registers as
|
|
88
|
+
**`aclDatabase = 'CORE'`** (the `Team` schema physically lives on the core cluster).
|
|
89
|
+
- **`Core.RecordScripts`** — `uuid`, `recordId` (FK → `Core.Records`), `method` (e.g. `GET`),
|
|
90
|
+
`route` (the script route), `phpMethod` (the static method name). **`UNIQUE(recordId, method,
|
|
91
|
+
route)`.**
|
|
92
|
+
- **`Client.AclRecordScripts`** — model `_Model_Client_AclRecordScript`, on `DB_CLIENT`:
|
|
93
|
+
`recordScriptId` (FK → `Core.RecordScripts`) + `roleId` (FK → `Client.Roles`). This is the
|
|
94
|
+
ACL grant, and it lives **per client** — so **record-script authorization is per-client**:
|
|
95
|
+
granting a role in one client does not grant it in another.
|
|
96
|
+
|
|
97
|
+
At dispatch the engine calls `getRecordScriptPhpMethod(record, httpMethod, route, roleIds)`,
|
|
98
|
+
which joins `Core.RecordScripts` to the caller's `Client.AclRecordScripts` rows for their
|
|
99
|
+
roles; no matching grant → the script does not run for that caller.
|
|
100
|
+
|
|
101
|
+
**Where the rows go:** seed them in **dbchanges2** as dated migration files — the
|
|
102
|
+
`Core.Records`/`Core.RecordScripts` rows under the `Core` folder, and the `AclRecordScripts`
|
|
103
|
+
grant under the per-client `Client_<Name>` folder. See the
|
|
104
|
+
[api2 architecture — metadata CRUD engine](../architecture.md) for the surrounding Records
|
|
105
|
+
metadata model.
|
|
106
|
+
|
|
107
|
+
## Data source note (core DB hosts/credentials)
|
|
108
|
+
|
|
109
|
+
The core cluster hosts and credentials this endpoint reads through are **not** documented here;
|
|
110
|
+
they live in **api2 `Config/*.ini`** (`[database]` section). Do not paste those values into
|
|
111
|
+
knowledge or code.
|
|
112
|
+
|
|
113
|
+
## Client variations
|
|
114
|
+
|
|
115
|
+
None — this is engine behavior. Per-client access is entirely controlled by which roles have
|
|
116
|
+
an `AclRecordScripts` grant in each client DB.
|
|
117
|
+
|
|
118
|
+
## Gotchas / known issues
|
|
119
|
+
|
|
120
|
+
- **No bound params in `_Query`.** Sanitize with `\_Database::escape()` (strings) and `(int)`
|
|
121
|
+
casts (numerics) for every value placed into the query text — query-string args are user
|
|
122
|
+
input.
|
|
123
|
+
- **Restore the query-cache flag.** `useQueryCache(false)` is a global toggle; capture the
|
|
124
|
+
prior value and restore it, or you disable caching for the rest of the request.
|
|
125
|
+
- **`aclDatabase` has no `TEAM`.** A `Team`-schema record registers as `'CORE'`; picking
|
|
126
|
+
`'CLIENT'` sends the engine looking in the wrong cluster.
|
|
127
|
+
- **Registration is per-environment and split.** A missing `Core.RecordScripts` row → the route
|
|
128
|
+
isn't a script (falls through to normal CRUD / 404-shaped behavior); a missing
|
|
129
|
+
`AclRecordScripts` grant → the caller's roles can't run it. Both rows must exist in every
|
|
130
|
+
environment the API serves.
|
|
131
|
+
- **The committed-tile definition is intentionally not the sprint-scoring definition.**
|
|
132
|
+
`committedTile()` uses the **Power BI / current-state** definition of done/committed
|
|
133
|
+
(`dtDone IS NOT NULL` + `workTypeNow`), which is *deliberately different* from TOGa IQ's
|
|
134
|
+
canonical sprint scoring (`statusNow IN (STATUS_IN__DONE)` + `workTypeAtLock`). See
|
|
135
|
+
[Team Sprint Management](../../worker2/features/team-sprint-management.md) before assuming a
|
|
136
|
+
tile agrees with sprint scores.
|
|
137
|
+
|
|
138
|
+
## Change history
|
|
139
|
+
|
|
140
|
+
- 2026-07-23 — Initial documentation of the Record Script authoring contract (static
|
|
141
|
+
`fn(&$api, ...namedParams)` on the record model; query-string → named args; return → `/v2`
|
|
142
|
+
envelope `data`), the SQL specifics (`_Query` has no bound params → `_Database::escape()` +
|
|
143
|
+
int-cast; `useQueryCache(false)` + restore for live tiles), and the registration metadata
|
|
144
|
+
(`Core.Records` `aclDatabase` CORE/CLIENT only, `Core.RecordScripts` UNIQUE(recordId,method,
|
|
145
|
+
route), per-client `Client.AclRecordScripts` grant). Motivated by
|
|
146
|
+
`_Model_Team_Sprint::committedTile()`, a read-only internal sprint dashboard tile — recording
|
|
147
|
+
the decision that internal sprint/dashboard reads are served from api2 via a Record Script
|
|
148
|
+
rather than the talos / TOGa IQ app. (kyalamarthi)
|
|
149
|
+
|
|
150
|
+
## Related docs
|
|
151
|
+
|
|
152
|
+
- [POST + JSON-body args for scripted APIs](./scripted-api-post-body-args.md) — the POST /
|
|
153
|
+
JSON-request-body variant for large argument values.
|
|
154
|
+
- [api2 architecture](../architecture.md) — the metadata-driven /v2 CRUD engine that Record
|
|
155
|
+
Scripts extend.
|
|
156
|
+
- [Per-Client Database Connections](../../_underscore/features/per-client-database-connections.md)
|
|
157
|
+
— why `DB_TEAM` resolves to the core cluster.
|
|
158
|
+
- [Team Sprint Management & Reporting](../../worker2/features/team-sprint-management.md) — the
|
|
159
|
+
canonical sprint scoring definition the committed tile deliberately diverges from.
|
|
160
|
+
</content>
|
|
@@ -60,5 +60,3 @@ easy to misread as an API problem.
|
|
|
60
60
|
values to `String` before `urlEncode` — a numeric value (first hit via `fetchAddressesByIds`)
|
|
61
61
|
threw `input.replace is not a function` and silently aborted the request client-side. Added
|
|
62
62
|
regression tests. (tcox)
|
|
63
|
-
</content>
|
|
64
|
-
</invoke>
|
|
@@ -46,4 +46,3 @@ the store when it changed.
|
|
|
46
46
|
field (a different covered property can have a different contact number). Previously the field
|
|
47
47
|
was disabled once the store held a phone, and the post-purchase save made the first purchase lock
|
|
48
48
|
it for every later visit. `save-if-different` retained. (tcox)
|
|
49
|
-
</content>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
| [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 |
|
|
29
29
|
| [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 |
|
|
30
30
|
| [Talos Transcript Ingestion Pipeline (worker2 → AWS Bedrock KBs)](features/talos-transcript-ingestion.md) | `_Worker_Team_Transcripts` runs a fully automated, cron-driven pipeline that ingests raw Teams transcripts into the **Talos / TOGa IQ** AWS Bedrock knowledge ba | worker2/Worker/Team/Transcripts.php, worker2/bin/sync-knowledge-bases.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Team/2026-06-30a, dbchanges2/Team/2026-06-30b, dbchanges2/Team/2026-06-30c, dbchanges2/Team/2026-06-30d, dbchanges2/Team/2026-06-30e, dbchanges2/Core/2026-06-30a, dbchanges2/Core/2026-07-02a, dbchanges2/Team/2026-07-02a, dbchanges2/Team/2026-07-08a, dbchanges2/Team/2026-07-09a, dbchanges2/Team/2026-07-10a |
|
|
31
|
-
| [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` (file `Worker/Team/Sprint.php`) is the engine behind TOGA's internal **development-sprint process and reporting**. | worker2/Worker/Team/Sprint.php, dbchanges2/Core/CronJobs (SprintLockScheduled seed) |
|
|
31
|
+
| [Team Sprint Management & Reporting](features/team-sprint-management.md) | `_Worker_Team_Sprint` (file `Worker/Team/Sprint.php`) is the engine behind TOGA's internal **development-sprint process and reporting**. | worker2/Worker/Team/Sprint.php, _underscore/Model/Team/Sprint.php, dbchanges2/Core/CronJobs (SprintLockScheduled seed) |
|
|
32
32
|
| [Teams Meeting Transcript Export](features/teams-transcript-export.md) | > **SUPERSEDED (2026-07-09) — the S3-staging model below is history.** `Export` is now a thin > **GRAPH-DIRECT** cron poller: it no longer archives raw VTT to ` | worker2/Worker/Team/Transcripts.php, worker2/Config/production.ini, worker2/Database/TeamsTranscriptExports.sql, dbchanges2/Core/2026-06-18a - Teams Transcript Export schedule.sql |
|
|
33
33
|
| [VAPI Webhook Handler (worker2 — AI-BDR end-of-call processing)](features/vapi-webhook-handler.md) | `_Worker_Vapi` ([worker2/Worker/Vapi.php](worker2/Worker/Vapi.php)) is the **PHP side of the AI-BDR call loop** — the webhook that receives VAPI's end-of-call r | worker2/Worker/Vapi.php, worker2/Worker/Ai/Bdr/Vapi.php |
|
|
34
34
|
| [WJE Freshservice Sync (worker2)](features/wje-freshservice-sync.md) | WJE ("WJE IT", helpdesk `wje.freshservice.com`) is a **Freshservice**-based help-desk client whose tickets, contacts, assets, groups, categories, and canned res | worker2/Worker/Wje.php, _underscore/Component/Api/Wje/Wje.php, _underscore/Model/Wje/Ticket.php, _underscore/Model/Wje/TicketNote.php, _underscore/Model/Wje/Contact.php, _underscore/Model/Wje/Unit.php, _underscore/Model/Wje/TicketTeam.php, _underscore/Model/Wje/TicketCategory.php, _underscore/Model/Wje/AssetType.php, _underscore/Model/Wje/PredefinedReply.php, library/app/api/wje.php, worker/crons/toga2/wje/import_supporting_records.php, worker/crons/toga2/wje/sync_togasupply_wje.php, worker/crons/notifications/reports/wje/wje_common.php, library/app/systemmonitor/wje.php, dbchanges2/Client_Wje/2024-10-04 - WjeOnboarding.sql |
|
|
@@ -6,10 +6,11 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
10
|
-
owners: ["jcardinal"]
|
|
9
|
+
updated: 2026-07-23
|
|
10
|
+
owners: ["jcardinal", "kyalamarthi"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Team/Sprint.php
|
|
13
|
+
- _underscore/Model/Team/Sprint.php
|
|
13
14
|
- dbchanges2/Core/CronJobs (SprintLockScheduled seed)
|
|
14
15
|
related:
|
|
15
16
|
- ../architecture.md
|
|
@@ -228,8 +229,34 @@ backfills `workTypeAtLock` the first time a task is seen).
|
|
|
228
229
|
Do **not** overload one map for both jobs. This pattern is replicated identically in
|
|
229
230
|
`SprintLock()`, `CaptureSprintEnd()`, and `CaptureSprintDaily()` — fix all three together.
|
|
230
231
|
|
|
232
|
+
## Two coexisting "committed / done" definitions (reconcile before comparing)
|
|
233
|
+
|
|
234
|
+
There are **two intentionally-different definitions** of committed/done for a sprint task, and
|
|
235
|
+
they do not agree:
|
|
236
|
+
|
|
237
|
+
1. **Canonical sprint-scoring definition** — used by the scoring model above (and by the
|
|
238
|
+
reusable scoring helpers `_pointsByWorkType` / `_tasksByWorkType` on **`_Model_Team_Sprint`**
|
|
239
|
+
in `_underscore`, `Model/Team/Sprint.php`): a task is *done* when
|
|
240
|
+
`statusNow IN (STATUS_IN__DONE)` and its category is taken from **`workTypeAtLock`** (the
|
|
241
|
+
frozen at-lock baseline). This is what reliability/integrity scoring measures against.
|
|
242
|
+
2. **Power BI / current-state definition** — used by dashboard tiles that must match the Power BI
|
|
243
|
+
numbers: *done* is **`dtDone IS NOT NULL`** and category is **`workTypeNow`** (current state,
|
|
244
|
+
not the at-lock snapshot). The api2 Record Script
|
|
245
|
+
[`_Model_Team_Sprint::committedTile()`](../../api2/features/record-scripts.md) deliberately
|
|
246
|
+
uses this definition.
|
|
247
|
+
|
|
248
|
+
Neither is "wrong" — they answer different questions (scored performance vs. live current state).
|
|
249
|
+
If a tile or report must **agree with TOGa IQ's canonical scoring**, reconcile it to definition
|
|
250
|
+
(1); otherwise expect current-state tiles to diverge from sprint scores.
|
|
251
|
+
|
|
231
252
|
## Change history
|
|
232
253
|
|
|
254
|
+
- 2026-07-23 — Recorded that two intentionally-different "committed/done" definitions coexist:
|
|
255
|
+
the canonical sprint-scoring definition (`statusNow IN (STATUS_IN__DONE)` + `workTypeAtLock`,
|
|
256
|
+
incl. the reusable `_pointsByWorkType`/`_tasksByWorkType` helpers on `_Model_Team_Sprint` in
|
|
257
|
+
`_underscore`) vs. the Power BI / current-state definition (`dtDone IS NOT NULL` +
|
|
258
|
+
`workTypeNow`) used by dashboard tiles such as the api2 `committedTile()` Record Script.
|
|
259
|
+
Added `_underscore/Model/Team/Sprint.php` to this doc's files. (kyalamarthi)
|
|
233
260
|
- 2026-07-21 — Added `SprintLockScheduled()` action + a `Core.CronJobs` seed
|
|
234
261
|
(`0 10 * * 3`, every Wednesday 10 AM Central, action `Team/Sprint/SprintLockScheduled`,
|
|
235
262
|
maxExecutionTime 600) to run **Sprint Lock automatically at the start of each biweekly
|
package/knowledge/INDEX.md
CHANGED
|
@@ -19,7 +19,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
19
19
|
|
|
20
20
|
- **_underscore** (_Underscore) _(framework core)_ — 37 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
21
21
|
- **worker2** (Worker) — 31 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
22
|
-
- **api2** (API) —
|
|
22
|
+
- **api2** (API) — 14 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)
|
|
25
25
|
- **saml** (SAML SSO Gateway) — 3 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
@@ -6,8 +6,8 @@ project: _Underscore
|
|
|
6
6
|
client: rate
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [mhammontree]
|
|
9
|
+
updated: 2026-07-23
|
|
10
|
+
owners: [mhammontree, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Rate/Entitlement.php
|
|
13
13
|
- _underscore/ApiRequest.php
|
|
@@ -96,9 +96,21 @@ creation vs. cancellation. This is an accepted, documented limitation — not an
|
|
|
96
96
|
address, so an entitlement with no address never even attempts contract creation (and
|
|
97
97
|
never logs a `/contract` call).
|
|
98
98
|
- **Auth-induced failures are blind to the monitor** — see the detection limitation above.
|
|
99
|
+
- **The AIG-success branch REASSIGNS `$payload->uuid`** (`$payload->uuid = _String::generateUuid()`,
|
|
100
|
+
repurposed as the AIG contract number) — and it does so **in the same `postPost`** that pins the WH
|
|
101
|
+
service address. On the pre-redesign build the pin step ran **after** this reassignment and keyed
|
|
102
|
+
off `$payload->uuid`, so **every successful AIG contract silently broke the WH address pin** (the
|
|
103
|
+
pin lookup matched no row). Preserve the ordering fix — snapshot the entitlement uuid **before**
|
|
104
|
+
this branch. See the
|
|
105
|
+
[WH per-address purchase guard](whole-home-warranty-purchase-guard.md) "AIG-success clobbers the
|
|
106
|
+
pin" gotcha (`_underscore` `_beta` `9c2e4b1c`).
|
|
99
107
|
|
|
100
108
|
## Change history
|
|
101
109
|
|
|
110
|
+
- 2026-07-23 — Documented that the **AIG-success branch reassigns `$payload->uuid`** (as the contract
|
|
111
|
+
number) inside the shared `postPost`, which silently broke the WH service-address pin on the
|
|
112
|
+
pre-redesign build (pin keyed off the now-reassigned uuid). Fixed by snapshotting the entitlement
|
|
113
|
+
uuid before the AIG branch (`_underscore` `_beta` `9c2e4b1c`). Cross-linked the WH guard doc. (tcox)
|
|
102
114
|
- 2026-07-07 — `postPost` now also persists the validated service address
|
|
103
115
|
(`Entitlements.c_serviceAddressId`, `Addresses.isValidated=1`), wrapped so it never blocks the
|
|
104
116
|
AIG-contract/email flow; a new WH `prePost` guard carrier-normalizes the address onto the
|
|
@@ -106,6 +106,10 @@ Ticket: TRUE-79533. Business rule owner: PM Paulina.
|
|
|
106
106
|
`Addresses.isValidated = 1` and writes `Entitlements.serviceAddressId`. This block is
|
|
107
107
|
wrapped so a failure never interrupts the AIG-contract creation or the confirmation email
|
|
108
108
|
(see the [AIG contract creation doc](aig-contract-creation.md) — the same `postPost`).
|
|
109
|
+
**The entitlement UUID is snapshotted up front** (`$entitlementUuid = $payload->uuid`) at the
|
|
110
|
+
top of `postPost`, **before** the AIG branch runs — because the AIG-success branch reassigns
|
|
111
|
+
`$payload->uuid` (see the "AIG-success clobbers the pin" gotcha). The pin lookup must use the
|
|
112
|
+
snapshot, not `$payload->uuid`.
|
|
109
113
|
6. All SQL uses `_Database::escape()` / int-casts — `_Query` has **no bind API** in this path.
|
|
110
114
|
|
|
111
115
|
## Field & model: `serviceAddressId` is now a STANDARD field (reviewer Jeff)
|
|
@@ -280,6 +284,22 @@ live carrier waterfall is opt-in via `RUN_LIVE=1` (defaults off). Verified **18/
|
|
|
280
284
|
- **`ApiPayloadInterceptors` (Client_Rate) has no `phpMethod` column.** The interceptor method
|
|
281
285
|
is resolved **by convention** from `(prePostProcessing, httpMethod)`: `PRE+POST → prePost`,
|
|
282
286
|
`POST+POST → postPost` (matches the framework `[pre|post][HttpMethod]` convention).
|
|
287
|
+
- **ROOT CAUSE of NULL pins on the pre-redesign build: the pin silently fails whenever the AIG
|
|
288
|
+
contract is created SUCCESSFULLY.** The old `postPost` pinned the entitlement by `$payload->uuid`,
|
|
289
|
+
but the **AIG-success branch reassigns `$payload->uuid = _String::generateUuid()`** (reusing it as
|
|
290
|
+
the AIG contract number) **before** the pin step. So on an AIG success the pin lookup by
|
|
291
|
+
`$payload->uuid` matches **no row**, logs `"TRUE-79533: could not resolve service address"`, and
|
|
292
|
+
gives up — leaving `Entitlements.serviceAddressId` (and, pre-redesign, `c_serviceAddressId`) NULL
|
|
293
|
+
and the purchase's `Addresses.isValidated` NULL (that pair is the fingerprint of a lost pin).
|
|
294
|
+
Pinning is therefore **intermittent — it works only when AIG auth FAILS** (the branch that
|
|
295
|
+
clobbers the uuid never runs). Proven on beta `Client_Rate` 2026-07-23: entitlement 68 pinned,
|
|
296
|
+
69/70/71 unpinned despite an intact Contact→`primaryContactAddressId`→ContactAddresses chain.
|
|
297
|
+
**Fixed in `_underscore` `_beta` commit `9c2e4b1c`**: `postPost` snapshots
|
|
298
|
+
`$entitlementUuid = $payload->uuid` up front, before the AIG branch, and pins by the snapshot.
|
|
299
|
+
**DEPLOY DEPENDENCY:** any environment still running a **pre-redesign** `api2`/`_underscore` build
|
|
300
|
+
loses the address pin every time AIG succeeds; the fix only takes effect on **EB redeploy**
|
|
301
|
+
(api2 pulls `_underscore` at deploy). This is the mechanism behind the COALESCE fallback and the
|
|
302
|
+
dangling-pin gotcha below — the fallback is what keeps the dedup correct while pins are missing.
|
|
283
303
|
- **Dedup is defeated by a DANGLING pin (not just a NULL one).** The COALESCE fallback covers a
|
|
284
304
|
**NULL** `serviceAddressId`, but if `serviceAddressId` points at a **deleted `Addresses` row**,
|
|
285
305
|
the join drops that entitlement and it becomes **invisible to the guard** — a duplicate WH slips
|
|
@@ -324,6 +344,17 @@ live carrier waterfall is opt-in via `RUN_LIVE=1` (defaults off). Verified **18/
|
|
|
324
344
|
(verified in `api2/Component/Api/V2/V2.php` `getAclFieldPermissions()` ~line 5731), which is why
|
|
325
345
|
a custom→standard promotion must re-create the read grant as an `AclFieldPermissions` row.
|
|
326
346
|
(mhammontree)
|
|
347
|
+
- 2026-07-23 — **Root cause of intermittent NULL pins + guard hardening confirmed** (tcox). (1) The
|
|
348
|
+
`postPost` service-address pin **silently fails whenever the AIG contract creation SUCCEEDS**,
|
|
349
|
+
because the AIG-success branch reassigns `$payload->uuid` before the pin step, so pinning by
|
|
350
|
+
`$payload->uuid` matches no row (fingerprint: `serviceAddressId` + `Addresses.isValidated` both
|
|
351
|
+
NULL). Proven on beta (entitlement 68 pinned, 69/70/71 not). Fixed in `_underscore` `_beta`
|
|
352
|
+
`9c2e4b1c` (snapshot `$entitlementUuid` up front). Deploy-dependency: pins are lost on AIG success
|
|
353
|
+
on any pre-redesign build until EB redeploy. (2) The dedup blind spot (unpinned entitlements
|
|
354
|
+
invisible to `hasActiveWarrantyAtAddress` — demonstrated live: two active WH at "STE 250" slipped
|
|
355
|
+
through on the old build) is **CLOSED in `_beta` `a9f408bc`** — the dedup joins Addresses via
|
|
356
|
+
`COALESCE(serviceAddressId, ContactAddresses.addressId)`, disables the query cache for the read,
|
|
357
|
+
and documents an accepted TOCTOU. (tcox)
|
|
327
358
|
- 2026-07-23 — **LAUNCH BLOCKER found: the standard `serviceAddressId` field has NO read ACL grant**
|
|
328
359
|
(tcox). Probe-verified on beta: `GET /v2/entitlements?fields=serviceAddressId` → 403 `EZ-2` for
|
|
329
360
|
roles 1/2/4 (`aclDatabase=CLIENT`). Standard fields are authorized via **`AclFieldPermissions`**
|
|
@@ -3,6 +3,7 @@ title: "Rate"
|
|
|
3
3
|
framework: "2.0"
|
|
4
4
|
apps:
|
|
5
5
|
- _underscore
|
|
6
|
+
- api2
|
|
6
7
|
- saml
|
|
7
8
|
- toga2-view
|
|
8
9
|
- worker
|
|
@@ -12,8 +13,8 @@ project: SAML SSO Gateway
|
|
|
12
13
|
client: rate
|
|
13
14
|
type: profile
|
|
14
15
|
status: active
|
|
15
|
-
updated: 2026-
|
|
16
|
-
owners: ["rgirish", "bala", "mhammontree"]
|
|
16
|
+
updated: 2026-07-23
|
|
17
|
+
owners: ["rgirish", "bala", "mhammontree", "tcox"]
|
|
17
18
|
files: []
|
|
18
19
|
related:
|
|
19
20
|
- clients/rate/features/saml-sso.md
|
package/package.json
CHANGED