@rubytech/create-sitedesk-code 0.1.524 → 0.1.526
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/dist/__tests__/samba-provision.test.js +46 -1
- package/dist/__tests__/websockify-bind-wiring.test.js +47 -0
- package/dist/index.js +39 -1
- package/dist/samba-provision.js +37 -0
- package/package.json +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +8 -7
- package/payload/platform/plugins/admin/skills/superpowers-sprint/SKILL.md +26 -0
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
- package/payload/platform/plugins/memory/PLUGIN.md +10 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +5 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js +141 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +6 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts +45 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js +122 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js +26 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js +201 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +89 -33
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +6 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js +148 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +83 -6
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +90 -5
- package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js +173 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +73 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +194 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +25 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +81 -8
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +39 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +9 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
- package/payload/platform/scripts/__tests__/check-canonical-tool-names.test.sh +162 -0
- package/payload/platform/scripts/__tests__/check-no-task-id-leaks.test.sh +53 -0
- package/payload/platform/scripts/__tests__/task-id-citation.test.sh +72 -0
- package/payload/platform/scripts/check-canonical-tool-names.mjs +36 -4
- package/payload/platform/scripts/check-no-task-id-leaks.mjs +80 -62
- package/payload/platform/scripts/lib/canonical-tool-names.mjs +130 -26
- package/payload/platform/scripts/lib/task-id-citation.mjs +67 -0
- package/payload/platform/scripts/vnc.sh +45 -1
- package/payload/server/{chunk-VUXKXOJU.js → chunk-7MGR5M6G.js} +4 -2
- package/payload/server/{manager-4TJK3YOY.js → manager-6XOWHREK.js} +1 -1
- package/payload/server/server.js +10 -4
- package/payload/platform/.docs/search-surface-contract.md +0 -58
- package/payload/platform/docs/superpowers/plans/2026-06-02-task-610-follower-202-retry.md +0 -372
- package/payload/platform/docs/superpowers/plans/2026-06-04-public-agent-knowledge-delivery.md +0 -230
- package/payload/platform/docs/superpowers/plans/2026-06-23-account-filesystem-schema.md +0 -544
- package/payload/platform/docs/superpowers/plans/2026-07-11-task-1557-operator-on-behalf-preference-attribution.md +0 -542
- package/payload/platform/docs/superpowers/plans/2026-07-13-account-schema-ontology-projection.md +0 -547
- package/payload/platform/docs/superpowers/plans/2026-07-14-graph-top-level-labels-ontology-single-source.md +0 -458
- package/payload/platform/docs/superpowers/plans/2026-07-17-task-1736-retire-mcp-stderr-tee.md +0 -397
- package/payload/platform/docs/superpowers/plans/2026-07-18-graph-native-ledger.md +0 -807
- package/payload/platform/docs/superpowers/plans/2026-07-18-ledger-write-path-fix.md +0 -109
- package/payload/platform/docs/superpowers/plans/2026-07-20-storage-pages-create.md +0 -82
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1704-data-portal-standing-audit.md +0 -673
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +0 -1244
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +0 -462
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +0 -321
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1829-platform-ui-typecheck.md +0 -830
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +0 -2177
- package/payload/platform/docs/superpowers/plans/2026-07-21-graph-caption-resolver.md +0 -124
- package/payload/platform/docs/superpowers/plans/2026-07-21-task-1877-bash-schema-enforcement.md +0 -107
- package/payload/platform/docs/superpowers/plans/2026-07-21-task-1887-reconcile-allowed-top-level.md +0 -205
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1899-reconcile-report-only.md +0 -748
- package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +0 -1716
- package/payload/platform/docs/superpowers/plans/2026-07-23-task-1930-adherence-enforcement.md +0 -422
- package/payload/platform/docs/superpowers/plans/2026-07-24-task-1942-subaccount-switcher-brand-head.md +0 -327
- package/payload/platform/docs/superpowers/plans/2026-07-25-task-1974-uploads-intake-inbox.md +0 -235
- package/payload/platform/docs/superpowers/plans/2026-07-25-task-1976-intra-folder-hygiene.md +0 -215
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-1902-account-owned-entry-declarations.md +0 -555
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-1926-portal-push-concurrency-guard.md +0 -482
- package/payload/platform/docs/superpowers/plans/2026-07-26-task-2023-sidebar-sessions-async-reads.md +0 -394
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2016-disabled-agent-routing.md +0 -624
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2028-declared-file-write-deny.md +0 -303
- package/payload/platform/docs/superpowers/plans/2026-07-27-task-2052-account-machinery-write-fence.md +0 -250
- package/payload/platform/docs/superpowers/plans/2026-07-28-task-2097-email-signature-set.md +0 -65
- package/payload/platform/docs/superpowers/specs/2026-06-02-task-610-follower-202-retry-design.md +0 -116
- package/payload/platform/docs/superpowers/specs/2026-06-23-account-filesystem-schema-design.md +0 -170
- package/payload/platform/docs/superpowers/specs/2026-07-11-task-1557-operator-on-behalf-preference-attribution-design.md +0 -90
- package/payload/platform/docs/superpowers/specs/2026-07-13-account-schema-ontology-projection-design.md +0 -178
- package/payload/platform/docs/superpowers/specs/2026-07-14-graph-top-level-labels-ontology-single-source-design.md +0 -119
- package/payload/platform/docs/superpowers/specs/2026-07-17-task-1736-retire-mcp-stderr-tee-design.md +0 -128
- package/payload/platform/docs/superpowers/specs/2026-07-18-graph-native-ledger-design.md +0 -241
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1704-data-portal-standing-audit-design.md +0 -177
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +0 -201
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +0 -217
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +0 -80
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1820-cpu-triage-admin-tools-design.md +0 -97
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md +0 -163
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +0 -163
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1899-reconcile-report-only-design.md +0 -121
- package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +0 -103
- package/payload/platform/docs/superpowers/specs/2026-07-23-task-1930-adherence-enforcement-design.md +0 -81
- package/payload/platform/docs/superpowers/specs/2026-07-24-task-1942-subaccount-switcher-brand-head-design.md +0 -35
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-1902-account-owned-entry-declarations-design.md +0 -232
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-1926-portal-push-concurrency-guard-design.md +0 -200
- package/payload/platform/docs/superpowers/specs/2026-07-26-task-2014-ui-suite-nondeterminism-design.md +0 -169
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2016-disabled-agent-routing-design.md +0 -139
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2028-declared-file-write-deny-design.md +0 -127
- package/payload/platform/docs/superpowers/specs/2026-07-27-task-2052-account-machinery-write-fence-design.md +0 -150
- package/payload/platform/docs/superpowers/specs/2026-07-28-task-2097-email-signature-set-design.md +0 -155
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
# Graph top-level label allowlist ontology single-source — Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** Make the `/graph` filter-popover chip allowlist (and the two other server routes that read the same set) derive from each vertical's `## Top-level node types` schema table instead of the hardcoded `FILTER_TOP_LEVEL_LABELS`, so a top-level label added to a vertical schema surfaces as a chip with no TypeScript edit, while preserving today's exact chip visibility.
|
|
6
|
-
|
|
7
|
-
**Architecture:** A new server-only module `platform/ui/server/lib/top-level-labels.ts` reads every `platform/plugins/memory/references/schema-*.md`, extracts the `Neo4j Label` column of each file's `## Top-level node types` table, and unions those with an explicit `STATIC_TOP_LEVEL_LABELS` const (base-schema infra plus verticals lacking a top-level section). The three server routes (`graph-labels-in-graph`, `graph-subgraph`, `graph-default-view`) call `getTopLevelLabelAllowlist()` instead of importing `FILTER_TOP_LEVEL_LABELS`. Count-gating in the cypher is unchanged.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** TypeScript (ES modules), Node `fs` (`readdirSync`/`readFileSync`), Vitest, Hono routes.
|
|
10
|
-
|
|
11
|
-
## Global Constraints
|
|
12
|
-
|
|
13
|
-
- Derived union MUST deep-equal today's `FILTER_TOP_LEVEL_LABELS` (48 members) against the real references dir — the byte-identical acceptance baseline.
|
|
14
|
-
- No `schema:` namespace filter: every row of a `## Top-level node types` table contributes its `Neo4j Label`, so `WhatsAppGroup` (`cdm:Channel`) stays a chip.
|
|
15
|
-
- The `Neo4j Label` column is resolved by header name, never by index (construction table has 5 columns, estate-agent 4).
|
|
16
|
-
- The new module is server-only (reads files). `FILTER_TOP_LEVEL_LABELS` stays in `app/lib/graph-labels.ts` for the client `searchHelpers.ts`.
|
|
17
|
-
- `getTopLevelLabelAllowlist()` returns a `ReadonlySet<string>` so `.has()` and spread work at every call site.
|
|
18
|
-
- Observability line on computation: `op=top-level-labels source=ontology count=<n> files=<...> derived=<d> static=<s>`, written to stderr via `console.error`.
|
|
19
|
-
- No em-dashes in any shipped log string. No task numbers in operator-visible strings or log lines.
|
|
20
|
-
- Path resolution mirrors `server/index.ts`: `process.env.MAXY_PLATFORM_ROOT ?? join(__dirname, '..')`, then `plugins/memory/references`.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
### Task 1: The derivation module and its regression test
|
|
25
|
-
|
|
26
|
-
**Files:**
|
|
27
|
-
- Create: `platform/ui/server/lib/top-level-labels.ts`
|
|
28
|
-
- Test: `platform/ui/server/lib/top-level-labels.test.ts`
|
|
29
|
-
|
|
30
|
-
**Interfaces:**
|
|
31
|
-
- Produces: `getTopLevelLabelAllowlist(opts?: { referencesDir?: string }): ReadonlySet<string>` and `STATIC_TOP_LEVEL_LABELS: ReadonlySet<string>`.
|
|
32
|
-
- Consumes: nothing from earlier tasks. Reads `FILTER_TOP_LEVEL_LABELS` from `../../app/lib/graph-labels` in the test only.
|
|
33
|
-
|
|
34
|
-
- [ ] **Step 1: Write the failing test**
|
|
35
|
-
|
|
36
|
-
```ts
|
|
37
|
-
// platform/ui/server/lib/top-level-labels.test.ts
|
|
38
|
-
import { describe, it, expect } from 'vitest'
|
|
39
|
-
import { resolve } from 'node:path'
|
|
40
|
-
import { mkdtempSync, writeFileSync, mkdirSync } from 'node:fs'
|
|
41
|
-
import { tmpdir } from 'node:os'
|
|
42
|
-
import { getTopLevelLabelAllowlist } from './top-level-labels'
|
|
43
|
-
import { FILTER_TOP_LEVEL_LABELS } from '../../app/lib/graph-labels'
|
|
44
|
-
|
|
45
|
-
// The real shipped references directory (four hops up from this test file:
|
|
46
|
-
// server/lib -> server -> ui -> platform -> plugins/memory/references).
|
|
47
|
-
const REAL_REFERENCES = resolve(
|
|
48
|
-
import.meta.dirname,
|
|
49
|
-
'../../../plugins/memory/references',
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
describe('getTopLevelLabelAllowlist', () => {
|
|
53
|
-
it('deep-equals FILTER_TOP_LEVEL_LABELS against the real references dir', () => {
|
|
54
|
-
const derived = getTopLevelLabelAllowlist({ referencesDir: REAL_REFERENCES })
|
|
55
|
-
expect([...derived].sort()).toEqual([...FILTER_TOP_LEVEL_LABELS].sort())
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it('surfaces a new top-level row with no TypeScript edit', () => {
|
|
59
|
-
const dir = mkdtempSync(resolve(tmpdir(), 'tll-'))
|
|
60
|
-
writeFileSync(
|
|
61
|
-
resolve(dir, 'schema-widgets.md'),
|
|
62
|
-
[
|
|
63
|
-
'# Widgets',
|
|
64
|
-
'',
|
|
65
|
-
'## Top-level node types (operator-entry, natural key)',
|
|
66
|
-
'',
|
|
67
|
-
'| Entity | Neo4j Label | Schema.org Type | Required Properties |',
|
|
68
|
-
'|--------|-------------|-----------------|---------------------|',
|
|
69
|
-
'| Widget | `Widget` | `schema:Product` | `accountId`, `sku` |',
|
|
70
|
-
'',
|
|
71
|
-
].join('\n'),
|
|
72
|
-
)
|
|
73
|
-
const derived = getTopLevelLabelAllowlist({ referencesDir: dir })
|
|
74
|
-
expect(derived.has('Widget')).toBe(true)
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
it('includes a cdm:-namespaced top-level row (no namespace filter)', () => {
|
|
78
|
-
const dir = mkdtempSync(resolve(tmpdir(), 'tll-'))
|
|
79
|
-
writeFileSync(
|
|
80
|
-
resolve(dir, 'schema-transport.md'),
|
|
81
|
-
[
|
|
82
|
-
'## Top-level node types',
|
|
83
|
-
'',
|
|
84
|
-
'| Entity | Neo4j Label | Schema.org Type | Required Properties |',
|
|
85
|
-
'|--------|-------------|-----------------|---------------------|',
|
|
86
|
-
'| Channel | `SomeChannel` | `cdm:Channel` | `accountId`, `id` |',
|
|
87
|
-
'',
|
|
88
|
-
].join('\n'),
|
|
89
|
-
)
|
|
90
|
-
const derived = getTopLevelLabelAllowlist({ referencesDir: dir })
|
|
91
|
-
expect(derived.has('SomeChannel')).toBe(true)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
it('ignores tables that are not under a Top-level node types heading', () => {
|
|
95
|
-
const dir = mkdtempSync(resolve(tmpdir(), 'tll-'))
|
|
96
|
-
writeFileSync(
|
|
97
|
-
resolve(dir, 'schema-child.md'),
|
|
98
|
-
[
|
|
99
|
-
'## Child node types (reached via parent neighbourhood)',
|
|
100
|
-
'',
|
|
101
|
-
'| Entity | Neo4j Label | Schema.org Type | Required Properties |',
|
|
102
|
-
'|--------|-------------|-----------------|---------------------|',
|
|
103
|
-
'| Line | `QuoteLine` | `schema:Thing` | `accountId`, `id` |',
|
|
104
|
-
'',
|
|
105
|
-
].join('\n'),
|
|
106
|
-
)
|
|
107
|
-
const derived = getTopLevelLabelAllowlist({ referencesDir: dir })
|
|
108
|
-
// Only the STATIC base labels survive; QuoteLine is a child, not top-level.
|
|
109
|
-
expect(derived.has('QuoteLine')).toBe(false)
|
|
110
|
-
expect(derived.has('Person')).toBe(true)
|
|
111
|
-
})
|
|
112
|
-
})
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
- [ ] **Step 2: Run test to verify it fails**
|
|
116
|
-
|
|
117
|
-
Run: `cd platform/ui && npx vitest run server/lib/top-level-labels.test.ts`
|
|
118
|
-
Expected: FAIL — `getTopLevelLabelAllowlist` is not exported / module not found.
|
|
119
|
-
|
|
120
|
-
- [ ] **Step 3: Write minimal implementation**
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
// platform/ui/server/lib/top-level-labels.ts
|
|
124
|
-
/**
|
|
125
|
-
* Server-side derivation of the /graph top-level chip allowlist from the
|
|
126
|
-
* per-vertical ontology.
|
|
127
|
-
*
|
|
128
|
-
* The allowlist is the union of two sources:
|
|
129
|
-
* 1. The `Neo4j Label` column of every `## Top-level node types` table in
|
|
130
|
-
* `plugins/memory/references/schema-*.md`. Today only schema-construction
|
|
131
|
-
* and schema-estate-agent carry that section; more arrive as verticals
|
|
132
|
-
* gain one.
|
|
133
|
-
* 2. `STATIC_TOP_LEVEL_LABELS` — base-schema infra (schema-base.md has no
|
|
134
|
-
* top-level section; its Node Types table mixes in child types) plus any
|
|
135
|
-
* vertical whose schema has no top-level section yet.
|
|
136
|
-
*
|
|
137
|
-
* No namespace filter: every row contributes its label, so transport labels
|
|
138
|
-
* like WhatsAppGroup (cdm:Channel) stay chips. This differs from the account
|
|
139
|
-
* file-schema generator, which excludes non-schema: rows because a channel
|
|
140
|
-
* has no file bucket. A channel is still a legitimate operator-entry graph node.
|
|
141
|
-
*/
|
|
142
|
-
import { readdirSync, readFileSync } from 'node:fs'
|
|
143
|
-
import { join, resolve } from 'node:path'
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Top-level labels not (yet) sourced from a vertical `## Top-level node types`
|
|
147
|
-
* section. This set shrinks as verticals gain a top-level section; each such
|
|
148
|
-
* change moves its labels from here into the derived union.
|
|
149
|
-
*/
|
|
150
|
-
export const STATIC_TOP_LEVEL_LABELS: ReadonlySet<string> = Object.freeze(
|
|
151
|
-
new Set([
|
|
152
|
-
// Base-schema infra (schema-base.md has no top-level section).
|
|
153
|
-
'LocalBusiness',
|
|
154
|
-
'Service',
|
|
155
|
-
'PriceSpecification',
|
|
156
|
-
'OpeningHoursSpecification',
|
|
157
|
-
'Organization',
|
|
158
|
-
'Person',
|
|
159
|
-
'UserProfile',
|
|
160
|
-
'Preference',
|
|
161
|
-
'AdminUser',
|
|
162
|
-
'AccessGrant',
|
|
163
|
-
'KnowledgeDocument',
|
|
164
|
-
'DigitalDocument',
|
|
165
|
-
'CreativeWork',
|
|
166
|
-
'Question',
|
|
167
|
-
'FAQPage',
|
|
168
|
-
'DefinedTerm',
|
|
169
|
-
'Review',
|
|
170
|
-
'ImageObject',
|
|
171
|
-
'Invoice',
|
|
172
|
-
'Task',
|
|
173
|
-
'Project',
|
|
174
|
-
'Event',
|
|
175
|
-
'Workflow',
|
|
176
|
-
'Email',
|
|
177
|
-
'EmailAccount',
|
|
178
|
-
'Agent',
|
|
179
|
-
// Knowledge-work vertical: schema-knowledge-work.md has no top-level
|
|
180
|
-
// section yet.
|
|
181
|
-
'Objective',
|
|
182
|
-
'KeyResult',
|
|
183
|
-
'Decision',
|
|
184
|
-
'Risk',
|
|
185
|
-
'Source',
|
|
186
|
-
'Finding',
|
|
187
|
-
'Hypothesis',
|
|
188
|
-
]),
|
|
189
|
-
) as ReadonlySet<string>
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Split a pipe-delimited markdown row into trimmed cells, honouring backtick
|
|
193
|
-
* code-spans so a `|` inside `` `A|B` `` does not split the row. Strips the
|
|
194
|
-
* optional leading/trailing pipes.
|
|
195
|
-
*/
|
|
196
|
-
function splitPipeRow(row: string): string[] {
|
|
197
|
-
let trimmed = row.trim()
|
|
198
|
-
if (trimmed.startsWith('|')) trimmed = trimmed.slice(1)
|
|
199
|
-
if (trimmed.endsWith('|')) trimmed = trimmed.slice(0, -1)
|
|
200
|
-
const cells: string[] = []
|
|
201
|
-
let inBacktick = false
|
|
202
|
-
let start = 0
|
|
203
|
-
for (let i = 0; i < trimmed.length; i++) {
|
|
204
|
-
const ch = trimmed[i]
|
|
205
|
-
if (ch === '`') inBacktick = !inBacktick
|
|
206
|
-
else if (ch === '|' && !inBacktick) {
|
|
207
|
-
cells.push(trimmed.slice(start, i).trim())
|
|
208
|
-
start = i + 1
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
cells.push(trimmed.slice(start).trim())
|
|
212
|
-
return cells
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const stripBackticks = (s: string): string => s.replace(/`/g, '').trim()
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Extract the `Neo4j Label` column of the first pipe-table beneath a
|
|
219
|
-
* `## Top-level node types` heading. Returns [] when the file has no such
|
|
220
|
-
* section. The heading is matched by case-insensitive prefix so the
|
|
221
|
-
* `(operator-entry, natural key)` suffix is tolerated; parsing stops at the
|
|
222
|
-
* next `## ` heading.
|
|
223
|
-
*/
|
|
224
|
-
function parseTopLevelLabels(content: string): string[] {
|
|
225
|
-
const lines = content.split(/\r?\n/)
|
|
226
|
-
let i = 0
|
|
227
|
-
// Find the heading.
|
|
228
|
-
while (i < lines.length) {
|
|
229
|
-
const l = lines[i]
|
|
230
|
-
if (l.startsWith('## ') && l.toLowerCase().includes('top-level node types')) break
|
|
231
|
-
i++
|
|
232
|
-
}
|
|
233
|
-
if (i >= lines.length) return []
|
|
234
|
-
i++
|
|
235
|
-
// Find the header row of the first table before the next `## `.
|
|
236
|
-
for (; i < lines.length - 1; i++) {
|
|
237
|
-
const line = lines[i]
|
|
238
|
-
if (line.startsWith('## ')) return []
|
|
239
|
-
if (!line.includes('|')) continue
|
|
240
|
-
const sep = lines[i + 1]
|
|
241
|
-
if (!/^\s*\|?[\s:|-]+\|?\s*$/.test(sep) || !sep.includes('-')) continue
|
|
242
|
-
const headers = splitPipeRow(line)
|
|
243
|
-
const labelCol = headers.findIndex((h) => h.toLowerCase() === 'neo4j label')
|
|
244
|
-
if (labelCol === -1) return []
|
|
245
|
-
const out: string[] = []
|
|
246
|
-
let j = i + 2
|
|
247
|
-
for (; j < lines.length; j++) {
|
|
248
|
-
const row = lines[j]
|
|
249
|
-
if (!row.trim() || !row.includes('|')) break
|
|
250
|
-
const cells = splitPipeRow(row)
|
|
251
|
-
const label = stripBackticks(cells[labelCol] ?? '')
|
|
252
|
-
if (label) out.push(label)
|
|
253
|
-
}
|
|
254
|
-
return out
|
|
255
|
-
}
|
|
256
|
-
return []
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function resolveReferencesDir(): string {
|
|
260
|
-
// Production: MAXY_PLATFORM_ROOT is `<install>/platform`, and the schema
|
|
261
|
-
// references live at `<platform>/plugins/memory/references` (same root
|
|
262
|
-
// schema-loader.ts resolves brand.json against).
|
|
263
|
-
if (process.env.MAXY_PLATFORM_ROOT) {
|
|
264
|
-
return join(process.env.MAXY_PLATFORM_ROOT, 'plugins', 'memory', 'references')
|
|
265
|
-
}
|
|
266
|
-
// Dev fallback (env unset, unbundled): three hops up from this source file,
|
|
267
|
-
// server/lib -> server -> ui -> platform, then plugins/memory/references.
|
|
268
|
-
return resolve(import.meta.dirname, '../../../plugins/memory/references')
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
let cached: ReadonlySet<string> | null = null
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* The /graph top-level chip allowlist, derived from the ontology. Cached at
|
|
275
|
-
* module scope because schema files are static per install. An injected
|
|
276
|
-
* `referencesDir` (tests) bypasses the cache and does not populate it.
|
|
277
|
-
*/
|
|
278
|
-
export function getTopLevelLabelAllowlist(
|
|
279
|
-
opts: { referencesDir?: string } = {},
|
|
280
|
-
): ReadonlySet<string> {
|
|
281
|
-
if (!opts.referencesDir && cached) return cached
|
|
282
|
-
|
|
283
|
-
const referencesDir = opts.referencesDir ?? resolveReferencesDir()
|
|
284
|
-
const fileNames = readdirSync(referencesDir)
|
|
285
|
-
.filter((f) => f.startsWith('schema-') && f.endsWith('.md'))
|
|
286
|
-
.sort()
|
|
287
|
-
|
|
288
|
-
const derived = new Set<string>()
|
|
289
|
-
const contributingFiles: string[] = []
|
|
290
|
-
for (const fileName of fileNames) {
|
|
291
|
-
const content = readFileSync(join(referencesDir, fileName), 'utf-8')
|
|
292
|
-
const labels = parseTopLevelLabels(content)
|
|
293
|
-
if (labels.length > 0) contributingFiles.push(fileName)
|
|
294
|
-
for (const label of labels) derived.add(label)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const derivedCount = derived.size
|
|
298
|
-
for (const label of STATIC_TOP_LEVEL_LABELS) derived.add(label)
|
|
299
|
-
|
|
300
|
-
const result = Object.freeze(derived) as ReadonlySet<string>
|
|
301
|
-
process.stderr.write(
|
|
302
|
-
`[graph-page] op=top-level-labels source=ontology count=${derived.size} ` +
|
|
303
|
-
`files=${contributingFiles.join('|') || 'none'} derived=${derivedCount} ` +
|
|
304
|
-
`static=${STATIC_TOP_LEVEL_LABELS.size}\n`,
|
|
305
|
-
)
|
|
306
|
-
|
|
307
|
-
if (!opts.referencesDir) cached = result
|
|
308
|
-
return result
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
- [ ] **Step 4: Run test to verify it passes**
|
|
313
|
-
|
|
314
|
-
Run: `cd platform/ui && npx vitest run server/lib/top-level-labels.test.ts`
|
|
315
|
-
Expected: PASS — all four tests green. If the deep-equal test fails, the diff names the drifted label(s); reconcile `STATIC_TOP_LEVEL_LABELS` against `FILTER_TOP_LEVEL_LABELS` (do not weaken the assertion).
|
|
316
|
-
|
|
317
|
-
- [ ] **Step 5: Commit**
|
|
318
|
-
|
|
319
|
-
```bash
|
|
320
|
-
git add platform/ui/server/lib/top-level-labels.ts platform/ui/server/lib/top-level-labels.test.ts
|
|
321
|
-
git commit -m "feat: derive /graph top-level chip allowlist from vertical ontology (1624)"
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
---
|
|
325
|
-
|
|
326
|
-
### Task 2: Point the three server routes at the derived allowlist
|
|
327
|
-
|
|
328
|
-
**Files:**
|
|
329
|
-
- Modify: `platform/ui/server/routes/admin/graph-labels-in-graph.ts`
|
|
330
|
-
- Modify: `platform/ui/server/routes/admin/graph-subgraph.ts`
|
|
331
|
-
- Modify: `platform/ui/server/routes/admin/graph-default-view.ts`
|
|
332
|
-
|
|
333
|
-
**Interfaces:**
|
|
334
|
-
- Consumes: `getTopLevelLabelAllowlist(): ReadonlySet<string>` from Task 1.
|
|
335
|
-
- Produces: nothing new; behaviour of the three routes is unchanged because the derived set deep-equals `FILTER_TOP_LEVEL_LABELS`.
|
|
336
|
-
|
|
337
|
-
- [ ] **Step 1: Confirm the existing route tests pass before the swap (baseline)**
|
|
338
|
-
|
|
339
|
-
Run: `cd platform/ui && npx vitest run app/graph/__tests__/labels-in-graph.test.ts server/routes/__tests__/graph-default-topLevel.test.ts`
|
|
340
|
-
Expected: PASS. This is the regression baseline the swap must preserve.
|
|
341
|
-
|
|
342
|
-
- [ ] **Step 2: Swap the popover route**
|
|
343
|
-
|
|
344
|
-
In `graph-labels-in-graph.ts`, change the import block
|
|
345
|
-
|
|
346
|
-
```ts
|
|
347
|
-
import {
|
|
348
|
-
FILTER_TOP_LEVEL_LABELS,
|
|
349
|
-
AGENT_ACTION_LABELS,
|
|
350
|
-
} from '../../../app/lib/graph-labels'
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
to
|
|
354
|
-
|
|
355
|
-
```ts
|
|
356
|
-
import { AGENT_ACTION_LABELS } from '../../../app/lib/graph-labels'
|
|
357
|
-
import { getTopLevelLabelAllowlist } from '../../lib/top-level-labels'
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
and change the allowlist construction (currently `new Set<string>(FILTER_TOP_LEVEL_LABELS)`)
|
|
361
|
-
|
|
362
|
-
```ts
|
|
363
|
-
const allowedSet = new Set<string>(getTopLevelLabelAllowlist())
|
|
364
|
-
if (includeAgentActions) {
|
|
365
|
-
for (const label of AGENT_ACTION_LABELS) allowedSet.add(label)
|
|
366
|
-
}
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
- [ ] **Step 3: Swap the default-view fetch route**
|
|
370
|
-
|
|
371
|
-
In `graph-subgraph.ts`, remove `FILTER_TOP_LEVEL_LABELS` from the `../../../app/lib/graph-labels` import (keep `HIDDEN_BY_DEFAULT_LABELS` and any others it imports), add `import { getTopLevelLabelAllowlist } from '../../lib/top-level-labels'`, and change line ~280
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
const labels = [...getTopLevelLabelAllowlist()].filter((l) => !HIDDEN_BY_DEFAULT_LABELS.has(l))
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
- [ ] **Step 4: Swap the eligibility route**
|
|
378
|
-
|
|
379
|
-
In `graph-default-view.ts`, replace
|
|
380
|
-
|
|
381
|
-
```ts
|
|
382
|
-
import { FILTER_TOP_LEVEL_LABELS } from '../../../app/lib/graph-labels'
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
with
|
|
386
|
-
|
|
387
|
-
```ts
|
|
388
|
-
import { getTopLevelLabelAllowlist } from '../../lib/top-level-labels'
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
and change the eligibility check (line ~158)
|
|
392
|
-
|
|
393
|
-
```ts
|
|
394
|
-
if (!getTopLevelLabelAllowlist().has(lbl)) {
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
Update the adjacent `reason` string to drop the parenthetical `(FILTER_TOP_LEVEL_LABELS)` naming so no internal set name leaks; keep the operator-facing text: `label "${lbl}" is not eligible for default view — outside the chip allowlist`.
|
|
398
|
-
|
|
399
|
-
- [ ] **Step 5: Run the route tests to verify no behaviour change**
|
|
400
|
-
|
|
401
|
-
Run: `cd platform/ui && npx vitest run app/graph/__tests__/labels-in-graph.test.ts server/routes/__tests__/graph-default-topLevel.test.ts`
|
|
402
|
-
Expected: PASS — the derived set equals `FILTER_TOP_LEVEL_LABELS`, so every assertion still holds.
|
|
403
|
-
|
|
404
|
-
- [ ] **Step 6: Typecheck the UI package**
|
|
405
|
-
|
|
406
|
-
Run: `cd platform/ui && npx tsc --noEmit`
|
|
407
|
-
Expected: no errors (no unused-import errors for the removed `FILTER_TOP_LEVEL_LABELS`).
|
|
408
|
-
|
|
409
|
-
- [ ] **Step 7: Commit**
|
|
410
|
-
|
|
411
|
-
```bash
|
|
412
|
-
git add platform/ui/server/routes/admin/graph-labels-in-graph.ts platform/ui/server/routes/admin/graph-subgraph.ts platform/ui/server/routes/admin/graph-default-view.ts
|
|
413
|
-
git commit -m "refactor: three /graph server routes read the ontology-derived allowlist (1624)"
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
---
|
|
417
|
-
|
|
418
|
-
### Task 3: Update the graph-labels doc comment and file the client follow-up
|
|
419
|
-
|
|
420
|
-
**Files:**
|
|
421
|
-
- Modify: `platform/ui/app/lib/graph-labels.ts` (comment only — the set stays for the client)
|
|
422
|
-
- Create: `.tasks/NNNN-graph-searchhelpers-derive-top-level-from-ontology.md` (number assigned at land time from LANES)
|
|
423
|
-
|
|
424
|
-
**Interfaces:**
|
|
425
|
-
- Consumes: nothing. Documentation and task-tracking only.
|
|
426
|
-
|
|
427
|
-
- [ ] **Step 1: Annotate `FILTER_TOP_LEVEL_LABELS` as the client-only mirror**
|
|
428
|
-
|
|
429
|
-
Add a sentence to the `FILTER_TOP_LEVEL_LABELS` doc block in `graph-labels.ts` recording that the server routes now derive this set from the vertical ontology (`server/lib/top-level-labels.ts`), and that this constant remains only for the client `searchHelpers.ts`, which cannot read files. Do not change the set members.
|
|
430
|
-
|
|
431
|
-
- [ ] **Step 2: Write the follow-up task file**
|
|
432
|
-
|
|
433
|
-
Create the task file describing: plumb the ontology-derived allowlist to the client `app/data/searchHelpers.ts` so its `/data` search chip pre-filter stops reading the static `FILTER_TOP_LEVEL_LABELS`, closing the last second source. In scope: deliver the derived set to the client (via the `graph-labels-in-graph` response the page already fetches, or an equivalent). Out of scope: the server routes (done here). Include Testing and Observability sections per the `.tasks` format.
|
|
434
|
-
|
|
435
|
-
- [ ] **Step 3: Commit**
|
|
436
|
-
|
|
437
|
-
```bash
|
|
438
|
-
git add platform/ui/app/lib/graph-labels.ts .tasks/NNNN-graph-searchhelpers-derive-top-level-from-ontology.md
|
|
439
|
-
git commit -m "docs: mark FILTER_TOP_LEVEL_LABELS client-only; file searchHelpers follow-up (1624)"
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
---
|
|
443
|
-
|
|
444
|
-
## Self-Review
|
|
445
|
-
|
|
446
|
-
**Spec coverage:**
|
|
447
|
-
- New module + derivation → Task 1. ✅
|
|
448
|
-
- Three server routes switch → Task 2. ✅
|
|
449
|
-
- Byte-identical baseline → Task 1 Step 1 deep-equal test + Task 2 route tests. ✅
|
|
450
|
-
- No namespace filter (WhatsAppGroup) → Task 1 Step 1 cdm test + the real-dir deep-equal (which contains WhatsAppGroup). ✅
|
|
451
|
-
- Base list explicit → `STATIC_TOP_LEVEL_LABELS` in Task 1. ✅
|
|
452
|
-
- Observability line → Task 1 Step 3 `process.stderr.write`. ✅
|
|
453
|
-
- Client follow-up filed → Task 3. ✅
|
|
454
|
-
- Count-gating unchanged → cypher untouched in Task 2; asserted by the pre-existing route tests. ✅
|
|
455
|
-
|
|
456
|
-
**Placeholder scan:** The only deferred value is the follow-up task number (`NNNN`), assigned from LANES at archive time per sprint protocol — not a code placeholder.
|
|
457
|
-
|
|
458
|
-
**Type consistency:** `getTopLevelLabelAllowlist(opts?: { referencesDir?: string }): ReadonlySet<string>` is used identically in Task 1 (definition), Task 1 test, and all three Task 2 call sites. `STATIC_TOP_LEVEL_LABELS` is a `ReadonlySet<string>`. Call sites use `.has()` and spread, both valid on `ReadonlySet`.
|