You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The fourth tile of ats_employer_hiring that #8 could not build. Deliberately left out rather than approximated (PR #54); this card is what would make it real. Blocked-by: #53.Unblocked — #53 merged as PR #64 (62e496d).
DESIGN.md §04 asks for median days from applied_at to the offer's created_at for hired applications. #8 established, by measurement, four independent reasons it could not be expressed. Three still hold; the fourth is gone:
No median aggregate. The available set is count / sum / avg / min / max / count_distinct.
A dataset cannot compute a duration across two objects. The only derived form combines other measures of the same dataset by name.
A measure-scoped filter answers 501 NOT_IMPLEMENTED on the memory driver (objectstack#16642) — so even a single-object formulation would error on the boot AGENTS.md documents.
A stored scalar on ats_application — offer_created_at (or days_to_offer), written by an afterInsert hook on ats_offer. A duration the analytics layer can average has to be a column; there is no cross-object arithmetic in a dataset.
DESIGN.md §04 — the wording moves from median to average, because median is not available and writing a spec the platform cannot express is how this tile came to be missing in the first place.
⛔ Do not touch src/security/, src/data/, the other two dashboards, or anything #39 / #45 covers.
What the seed gives you to check against
After a boot on the memory driver, the nine hired applications and their accepted offers are readable per employer. As a starting cross-check: admin@quillstone.example reads 3 offers (1 accepted), admin@harborline.example reads 4 (2 accepted). So the tile's denominator per employer is small and hand-checkable — which is the point: the acceptance below asks for the number, not the render.
Acceptance
pnpm validate && pnpm lint && pnpm typecheck exit 0. CI now gates this repo (.github/workflows/ci.yml) and pnpm lint runs --i18n-strict, so a new field or label needs both locales.
Boot, wait for [Seeder] Seed loading complete, and show the tile reading a plausible non-zero number for both Quillstone and Harborline, cross-checked against an independent computation over the same rows. ⛔ A tile that renders is not the acceptance; the number matching an independently-derived one is.
⚠️Measurement trap, found while verifying feat(data): an accepted offer on every hired application; seed-count docs read 818 #64: POST /api/v1/analytics/dataset/query accepts a filter at body.filter, selection.filter, filters or where with HTTP 200 and silently ignores it. The key the analytics service actually reads is selection.runtimeFilter (which is what the Console sends). A probe using any other key reports unfiltered tenant-wide numbers as if they were the widget's.
The fourth tile of
ats_employer_hiringthat #8 could not build. Deliberately left out rather than approximated (PR #54); this card is what would make it real.Blocked-by: #53.Unblocked — #53 merged as PR #64 (62e496d).DESIGN.md §04 asks for median days from
applied_atto the offer'screated_atfor hired applications. #8 established, by measurement, four independent reasons it could not be expressed. Three still hold; the fourth is gone:count / sum / avg / min / max / count_distinct.501 NOT_IMPLEMENTEDon the memory driver (objectstack#16642) — so even a single-object formulation would error on the boot AGENTS.md documents.⭐ The demo seed has no offer on any hired application — all 14DONE in feat(data): an accepted offer on every hired application; seed-count docs read 818 #64. The seed now carries oneats_offerrows sit onoffer-stage applications (Demo seed: noats_offerrow belongs to a hired application — all 14 offers sit onoffer-stage applications, so "days to offer for hired applications" is unmeasurable on the seed #53).acceptedats_offerper hired application in both locale packs (23 offers: 14 onoffer-stage applications + 9accepted, one per hired). Verified in the artifact and through RLS at runtime: 9 of 9 hired applications have exactly one accepted offer, and no accepted offer sits anywhere else. The tile will have data; do not re-seed it.Scope
ats_application—offer_created_at(ordays_to_offer), written by anafterInserthook onats_offer. A duration the analytics layer can average has to be a column; there is no cross-object arithmetic in a dataset.ctx.api.object(...).findOne({ where: { id } })inside the hook sandbox returns an unrelated row (every seededats_jobreadsemployer_org = org_ats_orbit) #43 contract: re-derive on update only when the payload names a source field or the derived field itself. A hook that recomputes unconditionally is corrupted byclaimSeedOwnership's predicate update on the next boot and looks correct until someone counts. That defect cost this project a full card; do not re-introduce it on a new column.Demo seed: noAlready done in feat(data): an accepted offer on every hired application; seed-count docs read 818 #64 — out of scope now. If you find yourself editingats_offerrow belongs to a hired application — all 14 offers sit onoffer-stage applications, so "days to offer for hired applications" is unmeasurable on the seed #53's seed fix — offers on the 9 hired applications, so the tile has data. Both locale packs, throughscopeToDemo().src/data/, stop: the seed is correct, and moving applications between stages would move the 88 / 46 / 28 / 14 / 9 funnel the release screenshots show.avg, not median, onats_employer_hiring.src/dashboards/employer-hiring.dashboard.ts— it currently explains why the tile is absent (as of feat(data): an accepted offer on every hired application; seed-count docs read 818 #64, on reasons 1 and 2 only). Replace it with what the tile now is.⛔ Do not touch
src/security/,src/data/, the other two dashboards, or anything #39 / #45 covers.What the seed gives you to check against
After a boot on the memory driver, the nine hired applications and their accepted offers are readable per employer. As a starting cross-check:
admin@quillstone.examplereads 3 offers (1 accepted),admin@harborline.examplereads 4 (2 accepted). So the tile's denominator per employer is small and hand-checkable — which is the point: the acceptance below asks for the number, not the render.Acceptance
pnpm validate && pnpm lint && pnpm typecheckexit 0. CI now gates this repo (.github/workflows/ci.yml) andpnpm lintruns--i18n-strict, so a new field or label needs both locales.[Seeder] Seed loading complete, and show the tile reading a plausible non-zero number for both Quillstone and Harborline, cross-checked against an independent computation over the same rows. ⛔ A tile that renders is not the acceptance; the number matching an independently-derived one is.POST /api/v1/analytics/dataset/queryaccepts a filter atbody.filter,selection.filter,filtersorwherewith HTTP 200 and silently ignores it. The key the analytics service actually reads isselection.runtimeFilter(which is what the Console sends). A probe using any other key reports unfiltered tenant-wide numbers as if they were the widget's.PATCHon an application leaves it unchanged (the Stamp hooks write one arbitrary employer's org onto every row:ctx.api.object(...).findOne({ where: { id } })inside the hook sandbox returns an unrelated row (every seededats_jobreadsemployer_org = org_ats_orbit) #43 regression test).Out of scope
The conversion-% tiles: those need a ratio over two differently-filtered counts and are blocked on objectstack#16642. Separate card when that lands.