@rubytech/create-sitedesk-code 0.1.524 → 0.1.525

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.
Files changed (117) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/skills/superpowers-sprint/SKILL.md +26 -0
  3. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  4. package/payload/platform/plugins/memory/PLUGIN.md +10 -0
  5. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js +5 -1
  6. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/compiled-truth-revision.test.js.map +1 -1
  7. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts +2 -0
  8. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.d.ts.map +1 -0
  9. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js +141 -0
  10. package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/temporal-type-preserve.test.js.map +1 -0
  11. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts +1 -1
  12. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.d.ts.map +1 -1
  13. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js +6 -0
  14. package/payload/platform/plugins/memory/mcp/dist/lib/log-ingest.js.map +1 -1
  15. package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts +45 -0
  16. package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.d.ts.map +1 -0
  17. package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js +122 -0
  18. package/payload/platform/plugins/memory/mcp/dist/lib/temporal-type-preserve.js.map +1 -0
  19. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js +26 -0
  20. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-by-name.test.js.map +1 -1
  21. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts +2 -0
  22. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.d.ts.map +1 -0
  23. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js +201 -0
  24. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-update-type-preserve.test.js.map +1 -0
  25. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.d.ts.map +1 -1
  26. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js +89 -33
  27. package/payload/platform/plugins/memory/mcp/dist/tools/memory-update.js.map +1 -1
  28. package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
  29. package/payload/platform/plugins/scheduling/PLUGIN.md +6 -0
  30. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts +2 -0
  31. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.d.ts.map +1 -0
  32. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js +148 -0
  33. package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/meeting-retype.test.js.map +1 -0
  34. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts +83 -6
  35. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.d.ts.map +1 -1
  36. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js +90 -5
  37. package/payload/platform/plugins/scheduling/mcp/dist/lib/booking-reconcile.js.map +1 -1
  38. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js +111 -2
  39. package/payload/platform/plugins/scheduling/mcp/dist/scripts/__tests__/routine-roster-audit.test.js.map +1 -1
  40. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts +38 -0
  41. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.d.ts.map +1 -1
  42. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js +116 -0
  43. package/payload/platform/plugins/scheduling/mcp/dist/scripts/agent-turn-dispatch.js.map +1 -1
  44. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +13 -1
  45. package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
  46. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js +81 -8
  47. package/payload/platform/plugins/scheduling/mcp/dist/scripts/reconcile-bookings.js.map +1 -1
  48. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js +39 -0
  49. package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-event-agent.test.js.map +1 -1
  50. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.d.ts.map +1 -1
  51. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js +9 -1
  52. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-event.js.map +1 -1
  53. package/payload/platform/scripts/__tests__/check-canonical-tool-names.test.sh +162 -0
  54. package/payload/platform/scripts/__tests__/check-no-task-id-leaks.test.sh +53 -0
  55. package/payload/platform/scripts/__tests__/task-id-citation.test.sh +72 -0
  56. package/payload/platform/scripts/check-canonical-tool-names.mjs +36 -4
  57. package/payload/platform/scripts/check-no-task-id-leaks.mjs +80 -62
  58. package/payload/platform/scripts/lib/canonical-tool-names.mjs +130 -26
  59. package/payload/platform/scripts/lib/task-id-citation.mjs +67 -0
  60. package/payload/platform/.docs/search-surface-contract.md +0 -58
  61. package/payload/platform/docs/superpowers/plans/2026-06-02-task-610-follower-202-retry.md +0 -372
  62. package/payload/platform/docs/superpowers/plans/2026-06-04-public-agent-knowledge-delivery.md +0 -230
  63. package/payload/platform/docs/superpowers/plans/2026-06-23-account-filesystem-schema.md +0 -544
  64. package/payload/platform/docs/superpowers/plans/2026-07-11-task-1557-operator-on-behalf-preference-attribution.md +0 -542
  65. package/payload/platform/docs/superpowers/plans/2026-07-13-account-schema-ontology-projection.md +0 -547
  66. package/payload/platform/docs/superpowers/plans/2026-07-14-graph-top-level-labels-ontology-single-source.md +0 -458
  67. package/payload/platform/docs/superpowers/plans/2026-07-17-task-1736-retire-mcp-stderr-tee.md +0 -397
  68. package/payload/platform/docs/superpowers/plans/2026-07-18-graph-native-ledger.md +0 -807
  69. package/payload/platform/docs/superpowers/plans/2026-07-18-ledger-write-path-fix.md +0 -109
  70. package/payload/platform/docs/superpowers/plans/2026-07-20-storage-pages-create.md +0 -82
  71. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1704-data-portal-standing-audit.md +0 -673
  72. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +0 -1244
  73. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +0 -462
  74. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +0 -321
  75. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1829-platform-ui-typecheck.md +0 -830
  76. package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +0 -2177
  77. package/payload/platform/docs/superpowers/plans/2026-07-21-graph-caption-resolver.md +0 -124
  78. package/payload/platform/docs/superpowers/plans/2026-07-21-task-1877-bash-schema-enforcement.md +0 -107
  79. package/payload/platform/docs/superpowers/plans/2026-07-21-task-1887-reconcile-allowed-top-level.md +0 -205
  80. package/payload/platform/docs/superpowers/plans/2026-07-22-task-1899-reconcile-report-only.md +0 -748
  81. package/payload/platform/docs/superpowers/plans/2026-07-22-task-1910-two-way-portal-exchange.md +0 -1716
  82. package/payload/platform/docs/superpowers/plans/2026-07-23-task-1930-adherence-enforcement.md +0 -422
  83. package/payload/platform/docs/superpowers/plans/2026-07-24-task-1942-subaccount-switcher-brand-head.md +0 -327
  84. package/payload/platform/docs/superpowers/plans/2026-07-25-task-1974-uploads-intake-inbox.md +0 -235
  85. package/payload/platform/docs/superpowers/plans/2026-07-25-task-1976-intra-folder-hygiene.md +0 -215
  86. package/payload/platform/docs/superpowers/plans/2026-07-26-task-1902-account-owned-entry-declarations.md +0 -555
  87. package/payload/platform/docs/superpowers/plans/2026-07-26-task-1926-portal-push-concurrency-guard.md +0 -482
  88. package/payload/platform/docs/superpowers/plans/2026-07-26-task-2023-sidebar-sessions-async-reads.md +0 -394
  89. package/payload/platform/docs/superpowers/plans/2026-07-27-task-2016-disabled-agent-routing.md +0 -624
  90. package/payload/platform/docs/superpowers/plans/2026-07-27-task-2028-declared-file-write-deny.md +0 -303
  91. package/payload/platform/docs/superpowers/plans/2026-07-27-task-2052-account-machinery-write-fence.md +0 -250
  92. package/payload/platform/docs/superpowers/plans/2026-07-28-task-2097-email-signature-set.md +0 -65
  93. package/payload/platform/docs/superpowers/specs/2026-06-02-task-610-follower-202-retry-design.md +0 -116
  94. package/payload/platform/docs/superpowers/specs/2026-06-23-account-filesystem-schema-design.md +0 -170
  95. package/payload/platform/docs/superpowers/specs/2026-07-11-task-1557-operator-on-behalf-preference-attribution-design.md +0 -90
  96. package/payload/platform/docs/superpowers/specs/2026-07-13-account-schema-ontology-projection-design.md +0 -178
  97. package/payload/platform/docs/superpowers/specs/2026-07-14-graph-top-level-labels-ontology-single-source-design.md +0 -119
  98. package/payload/platform/docs/superpowers/specs/2026-07-17-task-1736-retire-mcp-stderr-tee-design.md +0 -128
  99. package/payload/platform/docs/superpowers/specs/2026-07-18-graph-native-ledger-design.md +0 -241
  100. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1704-data-portal-standing-audit-design.md +0 -177
  101. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +0 -201
  102. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +0 -217
  103. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +0 -80
  104. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1820-cpu-triage-admin-tools-design.md +0 -97
  105. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md +0 -163
  106. package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +0 -163
  107. package/payload/platform/docs/superpowers/specs/2026-07-22-task-1899-reconcile-report-only-design.md +0 -121
  108. package/payload/platform/docs/superpowers/specs/2026-07-22-task-1910-two-way-portal-exchange-design.md +0 -103
  109. package/payload/platform/docs/superpowers/specs/2026-07-23-task-1930-adherence-enforcement-design.md +0 -81
  110. package/payload/platform/docs/superpowers/specs/2026-07-24-task-1942-subaccount-switcher-brand-head-design.md +0 -35
  111. package/payload/platform/docs/superpowers/specs/2026-07-26-task-1902-account-owned-entry-declarations-design.md +0 -232
  112. package/payload/platform/docs/superpowers/specs/2026-07-26-task-1926-portal-push-concurrency-guard-design.md +0 -200
  113. package/payload/platform/docs/superpowers/specs/2026-07-26-task-2014-ui-suite-nondeterminism-design.md +0 -169
  114. package/payload/platform/docs/superpowers/specs/2026-07-27-task-2016-disabled-agent-routing-design.md +0 -139
  115. package/payload/platform/docs/superpowers/specs/2026-07-27-task-2028-declared-file-write-deny-design.md +0 -127
  116. package/payload/platform/docs/superpowers/specs/2026-07-27-task-2052-account-machinery-write-fence-design.md +0 -150
  117. package/payload/platform/docs/superpowers/specs/2026-07-28-task-2097-email-signature-set-design.md +0 -155
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-sitedesk-code",
3
- "version": "0.1.524",
3
+ "version": "0.1.525",
4
4
  "description": "Install SiteDesk — automated back office for independent building contractors",
5
5
  "bin": {
6
6
  "create-sitedesk-code": "./dist/index.js"
@@ -11,6 +11,23 @@ A structured workflow for executing development work — features, bug fixes, re
11
11
 
12
12
  This skill is the maxy-code counterpart to `sprint`. The phase chain is identical in spirit; the difference is the skill at each phase. If both `gstack` and superpowers are installed and the project is set up for it, prefer `sprint` — its plan reviews are richer. Use this skill when `gstack` is not available.
13
13
 
14
+ ## Execution model: inline only, never subagents
15
+
16
+ Every phase of this sprint runs in the current session, in the main agent's own context. No phase dispatches a subagent, and no phase delegates work to the Agent tool, `superpowers:subagent-driven-development`, or `superpowers:dispatching-parallel-agents`.
17
+
18
+ Several of the chained superpowers skills offer or recommend a subagent path. In this sprint, take the inline path at each of them:
19
+
20
+ | Chained skill | Its subagent offer | What this sprint does |
21
+ |---|---|---|
22
+ | `superpowers:brainstorming` | dispatches a spec document reviewer subagent | review the spec yourself, against the same template |
23
+ | `superpowers:writing-plans` | dispatches a plan document reviewer subagent, and offers "Subagent-Driven (recommended)" as the implementation mode | review the plan yourself; choose the `superpowers:executing-plans` mode, never subagent-driven |
24
+ | `superpowers:executing-plans` | tells you to switch to `superpowers:subagent-driven-development` where subagents exist | ignore that instruction and stay in `executing-plans` |
25
+ | `superpowers:requesting-code-review` | dispatches a `general-purpose` code reviewer subagent | apply the reviewer template to the diff yourself, in this session |
26
+
27
+ A review done inline still uses the chained skill's reviewer template verbatim — the checklist is what makes the review real, not the isolation. The cost of inline is that the reviewer shares the sprint's context and therefore its assumptions; the operator accepts that in exchange for a single visible transcript.
28
+
29
+ Dispatching a subagent from any phase is a defect, not an optimisation.
30
+
14
31
  ## Output discipline (applies to every emission in this skill)
15
32
 
16
33
  Every message the sprint writes — plan summaries, change logs, status tables, the final summary — is written `/plainly`. That means:
@@ -54,18 +71,24 @@ Read the task file or operator brief first, then enter brainstorming with what y
54
71
 
55
72
  If the operator hands over a fully-formed plan that is already broken into specific files and behaviours, brainstorming can be a fast confirmation rather than an open exploration — but it still runs, because skipping it is the failure mode the phase exists to prevent.
56
73
 
74
+ When the skill reaches its spec document review, run that review inline against `spec-document-reviewer-prompt.md`. Do not dispatch the reviewer subagent.
75
+
57
76
  ### Phase 2: Plan — `superpowers:writing-plans`
58
77
 
59
78
  Invoke `superpowers:writing-plans` against the brainstorm output. The skill produces a single written plan: the files to touch, the behaviours to add or change, the edge cases, the test coverage, the deployment story. This is the artefact Phase 6 (Plan Conformity) checks the implementation against.
60
79
 
61
80
  This is a deliberate downgrade from `sprint`'s separate CEO and Eng reviews — multi-approach architecture review is out of scope for the superpowers chain. One reviewed plan is the baseline.
62
81
 
82
+ Two of the skill's steps offer a subagent; take neither. Review the finished plan inline against `plan-document-reviewer-prompt.md`. When the skill asks which implementation mode to use, choose `superpowers:executing-plans`, never "Subagent-Driven".
83
+
63
84
  Present the final plan to the operator and get explicit confirmation before writing any production code.
64
85
 
65
86
  ### Phase 3: Implement — `superpowers:executing-plans` + `superpowers:test-driven-development`
66
87
 
67
88
  Invoke `superpowers:executing-plans` to drive execution against the Phase 2 plan. Within each step, follow `superpowers:test-driven-development` — RED-GREEN-REFACTOR for every behaviour change.
68
89
 
90
+ `superpowers:executing-plans` opens by telling you to switch to `superpowers:subagent-driven-development` when subagents are available. Ignore that line; this sprint runs every task inline in the current session.
91
+
69
92
  Tests are ephemeral: design and run them to prove the implementation works during the sprint, then discard them. They are not committed to the repository unless the project's own test suite already lives in the tree and the new behaviour belongs in it. Their purpose is to enforce thinking through behaviour before writing production code, not to build a permanent regression suite.
70
93
 
71
94
  - **Test first.** Write a failing test for each behaviour before implementing it. Watch it fail. Write minimal code to pass. Refactor.
@@ -94,6 +117,8 @@ If verification fails, return to Phase 3.
94
117
 
95
118
  Invoke `superpowers:requesting-code-review` to frame the review request. Then run `code-review:code-review` against the sprint's diff. Process the response through `superpowers:receiving-code-review`, which gates whether each finding is accepted, contested, or filed as follow-up.
96
119
 
120
+ `superpowers:requesting-code-review` dispatches a `general-purpose` reviewer subagent. Do not dispatch it. Fill its `code-reviewer.md` template with the sprint's context and diff, then work through it inline as the reviewer. `code-review:code-review` likewise runs in this session against the diff, not as a delegated agent.
121
+
97
122
  Findings that are accepted and trivial are fixed before proceeding. Findings that are accepted but out-of-scope for this sprint become new `.tasks/NNN-*.md` files (the same deferral rule as Phase 6 — prose deferrals are banned). Findings that are contested get a one-line rebuttal in the debrief.
98
123
 
99
124
  ### Phase 6: Land
@@ -355,6 +380,7 @@ Session: <session-id>
355
380
 
356
381
  ## Principles
357
382
 
383
+ - **Inline, always.** Every phase runs in the current session. No subagents, no parallel agent dispatch, no delegated reviewers, whatever the chained skills recommend.
358
384
  - **Ship complete.** A sprint is not done until the debrief and the final-status block are delivered. Code without documentation is unfinished work.
359
385
  - **Explain the why.** Every change has a reason. "Refactored for clarity" is not a reason. "Extracted to avoid duplicate route derivation across WhatsApp and iMessage" is.
360
386
  - **Operator perspective.** Testing instructions are written for the person running the product. If the operator is non-technical, instructions are UI-based.
@@ -9,6 +9,12 @@ Invoked by the admin agent directly.
9
9
 
10
10
  This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
11
11
 
12
+ ## 2026-07-29 (0.1.525)
13
+
14
+ - A repeating routine whose start date had already gone by now runs on its next due occurrence instead of sitting silent forever. There is also a standing check that tells you when a routine has a start date that never fired.
15
+ - Saving a memory no longer rewrites the date and time on a meeting into plain text, so meeting times stay comparable and sortable afterwards.
16
+ - Internal engineering documents are no longer included in what gets installed on a customer device, and the check that scans installed text now reads exactly what ships rather than an out-of-date copy of the list.
17
+
12
18
  ## 2026-07-29 (0.1.524)
13
19
 
14
20
  - Routines are properly editable now. You can pick where a routine sends its result from the routine window, which previously could not be set at all, so the routines that ship with the platform were impossible to make work from the dashboard. Stopping a routine from chat also no longer means cancelling it outright.
@@ -239,6 +239,16 @@ The memory tools whose Neo4j read/write re-scopes on the resolved account accept
239
239
 
240
240
  Tools that do **not** take `targetAccountId` fall into three groups. **Not account-scoped:** `image-fetch` fetches image bytes by URL; `graph-prune-denylist-add/remove/list` mutate a brand-global deny-list; `memory-ingest-extract` stages a temp file — none read or write a sub-account subgraph. **Bound to the caller's own live session:** `session-compact` compacts the caller's `SESSION_ID` Conversation, which belongs to the house account the operator sits in — re-scoping `accountId` to a sub-account matches nothing and "compact a sub-account's live session" has no referent. **Scheduled, not interactive:** the dream-cycle **cron** (`lib/dream-cycle/*`) takes its account from the scheduler context, not a tool argument; only the interactive `memory-dream-run` tool is house-targetable. These tools keep their own-account no-account guard.
241
241
 
242
+ ## `memory-update` cannot change a property's Neo4j type
243
+
244
+ An update reads the node's own `valueType()` for every key it is about to write. Keys the node already holds as a temporal type (`DATE`, `ZONED DATETIME`, `LOCAL DATETIME`, `ZONED TIME`, `LOCAL TIME`, `DURATION`) are rebuilt with the matching Cypher constructor; everything else rides in the `n += $props` bag as before. `updatedAt` follows the same rule rather than one fixed type — `memory-write` stamps it as an ISO string and the graph tooltip reads it as one, while `:Meeting` nodes hold it as a `ZONED DATETIME`.
245
+
246
+ Each temporal value is vetted by Neo4j's own parser in a read-only probe **before** the update statement runs, so a value the constructor rejects aborts the whole update and leaves every property untouched, including the ones that would have succeeded. Vetting against the database rather than a JS reimplementation matters in both directions: a hand-rolled check would be stricter than Neo4j and reject values it accepts. The refusal is `Update blocked (type-preserving): <key> holds <type> and "<value>" is not a valid <type>`, logged as `reason=type-preserving` — its own slug, not the write-gate's, so a type refusal and a denylist refusal are never read as one thing. Only a `Neo.ClientError` counts as a verdict on the value; a disconnect or a timeout propagates unchanged rather than accusing a timestamp that was correct.
247
+
248
+ Setting a property to `null` still removes it. Removal changes no type, so it is outside this rule — refusing it would take away the only way to clear a nullable temporal property such as `:Meeting.endsAt`.
249
+
250
+ This exists because `SET n += $props` wrote every value verbatim. An agent updating a `:Meeting` with an ISO string replaced its `ZONED DATETIME` `startsAt` with a `STRING`, and a `STRING`-to-`DATETIME` comparison in Neo4j is NULL rather than false — so every calendar reader dropped the row with no error and no log line, and one account's booking page published a busy afternoon as open time for two days. `memory-update-by-name` delegates its whole write to `memory-update` and inherits this; it owns only the lookup.
251
+
242
252
  ## Graph Hygiene
243
253
 
244
254
  Graph hygiene is **agent-directed, case by case** — no autonomous rule engine, no cron. The Neo4j graph degrades into a landfill if pollution from prior agent errors (wrong-account hostnames, fabricated identifiers, raw monologue dumps) is allowed to compound. Cleanup happens when the admin agent observes a match against the operator-curated deny-list (or the user directly asks) and applies discretion using `memory-update`, `memory-delete`, or `conversation-memory-expunge`.
@@ -29,7 +29,11 @@ describe("compiled-truth-revision helper", () => {
29
29
  const templateEnd = src.indexOf("`", templateStart + "const updateQuery = `".length);
30
30
  expect(templateEnd).toBeGreaterThan(templateStart);
31
31
  const template = src.slice(templateStart, templateEnd);
32
- expect(template).toContain("SET n += $props, n.updatedAt = $updatedAt");
32
+ // Task 2148 replaced the literal `n += $props, n.updatedAt = $updatedAt`
33
+ // with a clause compiled against the node's own property types. The
34
+ // single-TX invariant is unchanged: the SET and the revision CREATE still
35
+ // share one template.
36
+ expect(template).toContain("SET ${compiled.setClause}");
33
37
  expect(template).toContain("${revisionClause}");
34
38
  // And the revisionClause itself embeds the UNWIND fragment.
35
39
  const clauseIdx = src.indexOf("const revisionClause =");
@@ -1 +1 @@
1
- {"version":3,"file":"compiled-truth-revision.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/compiled-truth-revision.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compiled-truth-revision.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/compiled-truth-revision.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=temporal-type-preserve.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal-type-preserve.test.d.ts","sourceRoot":"","sources":["../../../src/lib/__tests__/temporal-type-preserve.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,141 @@
1
+ // Task 2148 — the SET compiler that stops `memory-update` retyping a temporal
2
+ // property to a string. `SET n += $props` writes every value verbatim, so an
3
+ // ISO string aimed at a `ZONED DATETIME` silently became a `STRING`, and every
4
+ // `m.startsAt < datetime(...)` comparison against it is NULL rather than false.
5
+ //
6
+ // The compiler is pure: it takes the node's own `valueType()` reading per key
7
+ // and returns the SET clause, the params, and the list of values Neo4j's own
8
+ // temporal parser must vet before the write runs.
9
+ import { describe, it, expect } from "vitest";
10
+ import { normaliseValueType, temporalConstructorFor, compileTypePreservingSet, typePreserveRejection, } from "../temporal-type-preserve.js";
11
+ describe("normaliseValueType", () => {
12
+ it("strips the nullability suffix valueType() appends", () => {
13
+ expect(normaliseValueType("ZONED DATETIME NOT NULL")).toBe("ZONED DATETIME");
14
+ expect(normaliseValueType("STRING NOT NULL")).toBe("STRING");
15
+ expect(normaliseValueType(" DATE ")).toBe("DATE");
16
+ });
17
+ });
18
+ describe("temporalConstructorFor", () => {
19
+ it("maps every Neo4j temporal type to its constructor", () => {
20
+ expect(temporalConstructorFor("DATE NOT NULL")).toBe("date");
21
+ expect(temporalConstructorFor("ZONED DATETIME NOT NULL")).toBe("datetime");
22
+ expect(temporalConstructorFor("LOCAL DATETIME NOT NULL")).toBe("localdatetime");
23
+ expect(temporalConstructorFor("ZONED TIME NOT NULL")).toBe("time");
24
+ expect(temporalConstructorFor("LOCAL TIME NOT NULL")).toBe("localtime");
25
+ expect(temporalConstructorFor("DURATION NOT NULL")).toBe("duration");
26
+ });
27
+ it("returns null for every non-temporal type", () => {
28
+ for (const t of ["STRING NOT NULL", "INTEGER NOT NULL", "FLOAT", "BOOLEAN", "LIST<STRING>", "POINT"]) {
29
+ expect(temporalConstructorFor(t)).toBeNull();
30
+ }
31
+ });
32
+ });
33
+ const NOW_ISO = "2026-07-29T12:00:00.000Z";
34
+ function compile(existingTypes, props) {
35
+ return compileTypePreservingSet({
36
+ alias: "n",
37
+ existingTypes,
38
+ props,
39
+ now: { key: "updatedAt", isoValue: NOW_ISO },
40
+ });
41
+ }
42
+ describe("compileTypePreservingSet — scalar keys", () => {
43
+ it("leaves untyped and non-temporal keys in the n += $props bag", () => {
44
+ const c = compile({ title: "STRING NOT NULL" }, { title: "Golf", note: "back nine" });
45
+ expect(c.setClause).toBe("n += $props, n.`updatedAt` = $tp0");
46
+ expect(c.params).toEqual({ props: { title: "Golf", note: "back nine" }, tp0: NOW_ISO });
47
+ expect(c.probes).toEqual([]);
48
+ });
49
+ });
50
+ describe("compileTypePreservingSet — temporal keys", () => {
51
+ it("emits an explicit constructor assignment for a key the node holds as ZONED DATETIME", () => {
52
+ const c = compile({ startsAt: "ZONED DATETIME NOT NULL", title: "STRING NOT NULL" }, { startsAt: "2026-07-29T13:30:00Z", title: "Golf" });
53
+ expect(c.setClause).toBe("n += $props, n.`startsAt` = datetime($tp0), n.`updatedAt` = $tp1");
54
+ expect(c.params.props).toEqual({ title: "Golf" });
55
+ expect(c.params.tp0).toBe("2026-07-29T13:30:00Z");
56
+ expect(c.probes).toEqual([
57
+ {
58
+ key: "startsAt",
59
+ constructorName: "datetime",
60
+ paramName: "tp0",
61
+ value: "2026-07-29T13:30:00Z",
62
+ existingType: "ZONED DATETIME",
63
+ },
64
+ ]);
65
+ });
66
+ it("covers each temporal type with its own constructor", () => {
67
+ const c = compile({
68
+ d: "DATE NOT NULL",
69
+ ldt: "LOCAL DATETIME NOT NULL",
70
+ zt: "ZONED TIME NOT NULL",
71
+ lt: "LOCAL TIME NOT NULL",
72
+ dur: "DURATION NOT NULL",
73
+ }, { d: "2026-07-29", ldt: "2026-07-29T13:30:00", zt: "13:30:00Z", lt: "13:30:00", dur: "PT1H" });
74
+ expect(c.setClause).toBe("n += $props, n.`d` = date($tp0), n.`ldt` = localdatetime($tp1), " +
75
+ "n.`zt` = time($tp2), n.`lt` = localtime($tp3), n.`dur` = duration($tp4), " +
76
+ "n.`updatedAt` = $tp5");
77
+ expect(c.params.props).toEqual({});
78
+ expect(c.probes.map((p) => p.constructorName)).toEqual([
79
+ "date",
80
+ "localdatetime",
81
+ "time",
82
+ "localtime",
83
+ "duration",
84
+ ]);
85
+ });
86
+ it("escapes a backtick in a property key rather than breaking out of the quoting", () => {
87
+ const c = compile({ "we`ird": "ZONED DATETIME NOT NULL" }, { "we`ird": "2026-07-29T13:30:00Z" });
88
+ expect(c.setClause).toContain("n.`we``ird` = datetime($tp0)");
89
+ });
90
+ });
91
+ describe("compileTypePreservingSet — the updatedAt stamp", () => {
92
+ it("writes an ISO string when the node holds updatedAt as a string", () => {
93
+ const c = compile({ updatedAt: "STRING NOT NULL" }, { title: "Golf" });
94
+ expect(c.setClause).toBe("n += $props, n.`updatedAt` = $tp0");
95
+ expect(c.params.tp0).toBe(NOW_ISO);
96
+ });
97
+ it("writes an ISO string when the node has no updatedAt yet", () => {
98
+ const c = compile({}, { title: "Golf" });
99
+ expect(c.setClause).toBe("n += $props, n.`updatedAt` = $tp0");
100
+ expect(c.params.tp0).toBe(NOW_ISO);
101
+ });
102
+ it("calls the zero-argument constructor when the node holds updatedAt as a datetime", () => {
103
+ const c = compile({ updatedAt: "ZONED DATETIME NOT NULL" }, { title: "Golf" });
104
+ expect(c.setClause).toBe("n += $props, n.`updatedAt` = datetime()");
105
+ expect(c.params).toEqual({ props: { title: "Golf" } });
106
+ });
107
+ it("never lets a caller-supplied updatedAt reach the props bag or a probe", () => {
108
+ const c = compile({ updatedAt: "ZONED DATETIME NOT NULL" }, { updatedAt: "not-a-datetime-at-all", title: "Golf" });
109
+ expect(c.params.props).toEqual({ title: "Golf" });
110
+ expect(c.probes).toEqual([]);
111
+ expect(c.setClause).toBe("n += $props, n.`updatedAt` = datetime()");
112
+ });
113
+ it("rejects when the node holds updatedAt as a DURATION, which has no now form", () => {
114
+ expect(() => compile({ updatedAt: "DURATION NOT NULL" }, { title: "Golf" })).toThrow(/Update blocked \(type-preserving\): updatedAt holds DURATION/);
115
+ });
116
+ });
117
+ describe("compileTypePreservingSet — rejections", () => {
118
+ it("rejects a non-string value for a temporal key and applies nothing", () => {
119
+ expect(() => compile({ startsAt: "ZONED DATETIME NOT NULL", title: "STRING NOT NULL" }, { startsAt: 1754000000000, title: "Golf" })).toThrow(/Update blocked \(type-preserving\): startsAt holds ZONED DATETIME and "1754000000000" is not a valid ZONED DATETIME/);
120
+ });
121
+ it("rejects an empty string for a temporal key", () => {
122
+ expect(() => compile({ startsAt: "ZONED DATETIME NOT NULL" }, { startsAt: "" })).toThrow(/Update blocked \(type-preserving\): startsAt holds ZONED DATETIME/);
123
+ });
124
+ it("still lets null clear a temporal property", () => {
125
+ // Removal changes no type, so it is not this module's business. `:Meeting`
126
+ // carries a nullable `endsAt` by schema, and refusing null here would take
127
+ // away the only way to clear one.
128
+ const c = compile({ endsAt: "ZONED DATETIME NOT NULL" }, { endsAt: null });
129
+ expect(c.params.props).toEqual({ endsAt: null });
130
+ expect(c.probes).toEqual([]);
131
+ expect(c.setClause).toBe("n += $props, n.`updatedAt` = $tp0");
132
+ });
133
+ });
134
+ describe("typePreserveRejection", () => {
135
+ it("names the key, the held type and the offending value", () => {
136
+ const err = typePreserveRejection("startsAt", "ZONED DATETIME", "not a date");
137
+ expect(err.message).toBe('Update blocked (type-preserving): startsAt holds ZONED DATETIME and "not a date" is not a valid ZONED DATETIME. ' +
138
+ "The whole update was rejected — no property was changed.");
139
+ });
140
+ });
141
+ //# sourceMappingURL=temporal-type-preserve.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal-type-preserve.test.js","sourceRoot":"","sources":["../../../src/lib/__tests__/temporal-type-preserve.test.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- export type WriteEventReason = "invalid-input" | "schema-unknown-label" | "schema-property-naming" | "account-isolation" | "producedby-task-not-found" | "constraint-violation" | "conflicting-element-id" | "invalid-target-node-id" | "gate-blocked" | "neo4j-driver-error";
1
+ export type WriteEventReason = "invalid-input" | "schema-unknown-label" | "schema-property-naming" | "account-isolation" | "producedby-task-not-found" | "constraint-violation" | "conflicting-element-id" | "invalid-target-node-id" | "gate-blocked" | "type-preserving" | "neo4j-driver-error";
2
2
  interface SuccessEvent {
3
3
  tool: string;
4
4
  result: "ok";
@@ -1 +1 @@
1
- {"version":3,"file":"log-ingest.d.ts","sourceRoot":"","sources":["../../src/lib/log-ingest.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,sBAAsB,GACtB,wBAAwB,GACxB,mBAAmB,GACnB,2BAA2B,GAC3B,sBAAsB,GACtB,wBAAwB,GACxB,wBAAwB,GACxB,cAAc,GACd,oBAAoB,CAAC;AAEzB,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,UAAU,CAAC;AAEnD,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,UAAU,GAAG,MAAM,CAW3D;AAED,kFAAkF;AAClF,wBAAsB,cAAc,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAkClE;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,qBAAqB,GAC7C,OAAO,CAAC,CAAC,CAAC,CA8BZ;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAkC7D;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAO5F"}
1
+ {"version":3,"file":"log-ingest.d.ts","sourceRoot":"","sources":["../../src/lib/log-ingest.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,gBAAgB,GACxB,eAAe,GACf,sBAAsB,GACtB,wBAAwB,GACxB,mBAAmB,GACnB,2BAA2B,GAC3B,sBAAsB,GACtB,wBAAwB,GACxB,wBAAwB,GACxB,cAAc,GACd,iBAAiB,GACjB,oBAAoB,CAAC;AAEzB,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,UAAU,CAAC;AAEnD,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,UAAU,GAAG,MAAM,CAW3D;AAED,kFAAkF;AAClF,wBAAsB,cAAc,CAAC,EAAE,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAkClE;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,oFAAoF;IACpF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,CAAC,EACpC,IAAI,EAAE,kBAAkB,EACxB,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,qBAAqB,GAC7C,OAAO,CAAC,CAAC,CAAC,CA8BZ;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,gBAAgB,CAuC7D;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAO5F"}
@@ -134,6 +134,12 @@ export function classifyReason(err) {
134
134
  return "schema-unknown-label";
135
135
  if (/Schema\.org/i.test(msg) && /property/i.test(msg))
136
136
  return "schema-property-naming";
137
+ // A refusal to change a property's Neo4j type. Matched before the gate rule
138
+ // below, which its "Update blocked" prefix would otherwise claim: a label or
139
+ // property denylist refusal and a temporal type refusal are different causes,
140
+ // and an operator grepping the reason must be able to tell them apart.
141
+ if (/^Update blocked \(type-preserving\)/i.test(msg))
142
+ return "type-preserving";
137
143
  // graph-write-gate "Write blocked" / "Update blocked" — checkGraphWriteGate
138
144
  if (/^(Write|Update) blocked/i.test(msg))
139
145
  return "gate-blocked";
@@ -1 +1 @@
1
- {"version":3,"file":"log-ingest.js","sourceRoot":"","sources":["../../src/lib/log-ingest.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,EAAE;AACF,0EAA0E;AAC1E,6EAA6E;AAC7E,0EAA0E;AAC1E,6CAA6C;AAC7C,wEAAwE;AACxE,4CAA4C;AAC5C,EAAE;AACF,uBAAuB;AACvB,gHAAgH;AAChH,uBAAuB;AACvB,oIAAoI;AACpI,EAAE;AACF,yEAAyE;AACzE,0EAA0E;AAC1E,4EAA4E;AAC5E,sEAAsE;AACtE,gBAAgB;AAEhB,MAAM,GAAG,GAAG,YAAY,CAAC;AACzB,MAAM,aAAa,GAAG,WAAW,CAAC;AAClC,MAAM,aAAa,GAAG,uBAAuB,CAAC;AAC9C,MAAM,UAAU,GAAG,GAAG,CAAC;AAuCvB,kEAAkE;AAClE,MAAM,UAAU,oBAAoB,CAAC,EAAc;IACjD,MAAM,IAAI,GAAG,EAAE,CAAC,SAAS,IAAI,GAAG,CAAC;IACjC,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,GAAG,CAAC;IAC9D,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,IAAI,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC7E,MAAM,IAAI,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;IAC3D,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,IAAI,YAAY,IAAI,YAAY,IAAI,WAAW,MAAM,SAAS,IAAI,EAAE,CAAC;IACxF,IAAI,EAAE,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,GAAG,IAAI,wBAAwB,EAAE,CAAC,SAAS,IAAI,GAAG,OAAO,EAAE,CAAC,UAAU,EAAE,CAAC;IAClF,CAAC;IACD,MAAM,MAAM,GAAG,cAAc,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO,GAAG,IAAI,wBAAwB,EAAE,CAAC,MAAM,WAAW,MAAM,OAAO,EAAE,CAAC,UAAU,EAAE,CAAC;AACzF,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,EAAc;IACjD,MAAM,IAAI,GAAG,oBAAoB,CAAC,EAAE,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACvC,mEAAmE;IACnE,qEAAqE;IACrE,0EAA0E;IAC1E,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IACD,MAAM,GAAG,GAAG,UAAU,aAAa,IAAI,IAAI,GAAG,aAAa,EAAE,CAAC;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC;QAC1B,GAAG,EAAE,GAAG;QACR,KAAK,EAAE,EAAE,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;QAC/C,IAAI;KACL,CAAC,CAAC;IACH,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAC3B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI;SACL,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,IAAI,GAAG,8BAA8B,GAAG,CAAC,MAAM,gBAAgB,CAChE,CAAC;YACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,oCAAoC,KAAK,IAAI,CAAC,CAAC;QAC3E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAmBD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAwB,EACxB,IAAsB,EACtB,OAA8C;IAE9C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,MAAM,cAAc,CAAC;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,MAAM,EAAE,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM;YACtC,IAAI,EAAE,QAAQ,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI;YAChC,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SAC/B,CAAC,CAAC;QACH,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,cAAc,CAAC;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,MAAM,EAAE,OAAO;YACf,MAAM,EAAE,cAAc,CAAC,GAAG,CAAC;YAC3B,MAAM,EAAE,eAAe,CAAC,GAAG,CAAC;YAC5B,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SAC/B,CAAC,CAAC;QACH,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,IAAI,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC;QAAE,OAAO,oBAAoB,CAAC;IACzD,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC;IACxB,MAAM,IAAI,GAAI,GAA0B,CAAC,IAAI,CAAC;IAE9C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,mDAAmD,EAAE,CAAC;QAC7F,OAAO,sBAAsB,CAAC;IAChC,CAAC;IACD,IAAI,uBAAuB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,wBAAwB,CAAC;IACvE,IAAI,8BAA8B,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,mBAAmB,CAAC;IACzE,IAAI,iDAAiD,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,2BAA2B,CAAC;IACpG,uEAAuE;IACvE,2DAA2D;IAC3D,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,sBAAsB,CAAC;IAC9D,IAAI,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,wBAAwB,CAAC;IACvF,4EAA4E;IAC5E,IAAI,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,cAAc,CAAC;IAChE,8CAA8C;IAC9C,IAAI,+DAA+D,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9E,OAAO,wBAAwB,CAAC;IAClC,CAAC;IACD,iDAAiD;IACjD,IACE,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;QACxB,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;QACrB,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC;QAC1B,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC;QACpC,yCAAyC,CAAC,IAAI,CAAC,GAAG,CAAC;QACnD,yBAAyB,CAAC,IAAI,CAAC,GAAG,CAAC;QACnC,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,oCAAoC,CAAC,IAAI,CAAC,GAAG,CAAC,EAC5E,CAAC;QACD,OAAO,eAAe,CAAC;IACzB,CAAC;IACD,OAAO,oBAAoB,CAAC;AAC9B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,UAA+C;IACtE,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAC;IAClC,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAU,EAAE,CAAC;QACtD,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAC1B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,eAAe,CAAC,GAAY;IACnC,IAAI,GAAG,YAAY,KAAK;QAAE,OAAO,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAC7D,OAAO,cAAc,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,cAAc,CAAC,CAAS;IAC/B,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3C,IAAI,IAAI,CAAC,MAAM,IAAI,UAAU;QAAE,OAAO,IAAI,CAAC;IAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"log-ingest.js","sourceRoot":"","sources":["../../src/lib/log-ingest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /** `valueType()` reports nullability, e.g. `ZONED DATETIME NOT NULL`. A value
2
+ * read off a node is always NOT NULL, so the suffix carries no information. */
3
+ export declare function normaliseValueType(raw: string): string;
4
+ /** The constructor for a `valueType()` reading, or null when the type is not
5
+ * temporal and the value can ride in the `n += $props` bag unchanged. */
6
+ export declare function temporalConstructorFor(rawValueType: string): string | null;
7
+ /** A value the caller must vet with Neo4j's own temporal parser before the
8
+ * write runs. Vetting up front is what makes the rejection all-or-nothing. */
9
+ export interface TemporalProbe {
10
+ key: string;
11
+ constructorName: string;
12
+ paramName: string;
13
+ value: unknown;
14
+ /** Normalised `valueType()` reading, for the rejection message. */
15
+ existingType: string;
16
+ }
17
+ export interface CompiledUpdate {
18
+ /** The SET clause body, without the leading `SET`. */
19
+ setClause: string;
20
+ /** Query params. Always carries `props`; temporal values are `tp0`, `tp1`, … */
21
+ params: Record<string, unknown>;
22
+ probes: TemporalProbe[];
23
+ }
24
+ /** The single rejection message for every type-preserving refusal — raised here
25
+ * for a value this module can already see is wrong, and by the caller when
26
+ * Neo4j's parser rejects a probe. `Update blocked` is what `classifyReason`
27
+ * maps to `reason=gate-blocked`, so the refusal always reaches the log. */
28
+ export declare function typePreserveRejection(key: string, existingType: string, value: unknown): Error;
29
+ export interface CompileArgs {
30
+ /** Node alias the SET clause addresses. */
31
+ alias: string;
32
+ /** `valueType()` per property key, for the keys the node already holds. A key
33
+ * absent from this map is new on the node and has no type to preserve. */
34
+ existingTypes: Record<string, string>;
35
+ props: Record<string, unknown>;
36
+ /** The write-time stamp. Its key is removed from `props` first, so a
37
+ * caller-supplied value can never reach the node or a probe — matching the
38
+ * pre-existing behaviour where the explicit stamp overwrote it. */
39
+ now: {
40
+ key: string;
41
+ isoValue: string;
42
+ };
43
+ }
44
+ export declare function compileTypePreservingSet(args: CompileArgs): CompiledUpdate;
45
+ //# sourceMappingURL=temporal-type-preserve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal-type-preserve.d.ts","sourceRoot":"","sources":["../../src/lib/temporal-type-preserve.ts"],"names":[],"mappings":"AA2CA;gFACgF;AAChF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;0EAC0E;AAC1E,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE1E;AAED;+EAC+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,cAAc;IAC7B,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED;;;4EAG4E;AAC5E,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,KAAK,CAM9F;AAOD,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B;;wEAEoE;IACpE,GAAG,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;CACxC;AAED,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,WAAW,GAAG,cAAc,CAgE1E"}
@@ -0,0 +1,122 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Task 2148 — type-preserving SET compiler for memory-update.
3
+ //
4
+ // `SET n += $props` writes every value verbatim. An agent updating a :Meeting
5
+ // with `startsAt: "2026-07-29T13:30:00Z"` therefore replaced a ZONED DATETIME
6
+ // with a STRING, and a `STRING`-to-`DATETIME` comparison in Neo4j is NULL, not
7
+ // false: every calendar reader dropped the row with no error and no log line.
8
+ //
9
+ // This module decides, per key, whether the value can ride in the `n += $props`
10
+ // bag or has to be rebuilt with the temporal constructor matching the type the
11
+ // node already holds. It is pure — the caller owns the session — and it never
12
+ // guesses at Neo4j's temporal grammar: values bound for a constructor come back
13
+ // as `probes`, which the caller vets with Neo4j's own parser before any write.
14
+ //
15
+ // existing valueType(n.startsAt) emitted SET fragment
16
+ // ──────────────────────────────── ─────────────────────────────────
17
+ // STRING / INTEGER / absent (stays in `n += $props`)
18
+ // ZONED DATETIME n.`startsAt` = datetime($tp0)
19
+ // DATE n.`startsAt` = date($tp0)
20
+ //
21
+ // The `updatedAt` stamp follows the same rule rather than being forced to one
22
+ // type: `memory-write` stamps it as an ISO string (memory-write.ts:223) and the
23
+ // graph tooltip reads it with `typeof props.updatedAt === 'string'`
24
+ // (platform/ui/app/graph/display-helpers.ts:286), so an unconditional
25
+ // `datetime()` would be the very retype this module exists to prevent.
26
+ // ---------------------------------------------------------------------------
27
+ /** Neo4j temporal types, as `valueType()` names them, and the Cypher
28
+ * constructor that rebuilds a value of that type. */
29
+ const TEMPORAL_CONSTRUCTORS = {
30
+ DATE: "date",
31
+ "ZONED DATETIME": "datetime",
32
+ "LOCAL DATETIME": "localdatetime",
33
+ "ZONED TIME": "time",
34
+ "LOCAL TIME": "localtime",
35
+ DURATION: "duration",
36
+ };
37
+ /** The five constructors with a zero-argument "now" form. `duration` has none,
38
+ * which is why a DURATION-typed `updatedAt` is a rejection rather than a
39
+ * silent retype. */
40
+ const NOW_CAPABLE = new Set(["date", "datetime", "localdatetime", "time", "localtime"]);
41
+ /** `valueType()` reports nullability, e.g. `ZONED DATETIME NOT NULL`. A value
42
+ * read off a node is always NOT NULL, so the suffix carries no information. */
43
+ export function normaliseValueType(raw) {
44
+ return raw.trim().replace(/\s+NOT\s+NULL$/i, "").trim();
45
+ }
46
+ /** The constructor for a `valueType()` reading, or null when the type is not
47
+ * temporal and the value can ride in the `n += $props` bag unchanged. */
48
+ export function temporalConstructorFor(rawValueType) {
49
+ return TEMPORAL_CONSTRUCTORS[normaliseValueType(rawValueType)] ?? null;
50
+ }
51
+ /** The single rejection message for every type-preserving refusal — raised here
52
+ * for a value this module can already see is wrong, and by the caller when
53
+ * Neo4j's parser rejects a probe. `Update blocked` is what `classifyReason`
54
+ * maps to `reason=gate-blocked`, so the refusal always reaches the log. */
55
+ export function typePreserveRejection(key, existingType, value) {
56
+ return new Error(`Update blocked (type-preserving): ${key} holds ${existingType} and ` +
57
+ `"${String(value)}" is not a valid ${existingType}. ` +
58
+ `The whole update was rejected — no property was changed.`);
59
+ }
60
+ /** Backticks are the only character that can break out of `n.\`key\``. */
61
+ function quoteKey(key) {
62
+ return "`" + key.replace(/`/g, "``") + "`";
63
+ }
64
+ export function compileTypePreservingSet(args) {
65
+ const { alias, existingTypes, props, now } = args;
66
+ const scalarProps = {};
67
+ const assignments = [];
68
+ const probes = [];
69
+ const params = {};
70
+ let paramIndex = 0;
71
+ for (const [key, value] of Object.entries(props)) {
72
+ if (key === now.key)
73
+ continue;
74
+ const rawType = existingTypes[key];
75
+ const constructorName = rawType ? temporalConstructorFor(rawType) : null;
76
+ if (!constructorName) {
77
+ scalarProps[key] = value;
78
+ continue;
79
+ }
80
+ // `null` removes the property. Removal changes no type, so it is not this
81
+ // module's business — it rides in the props bag exactly as it always did.
82
+ // Refusing it would take away the only way to clear a temporal property.
83
+ if (value === null) {
84
+ scalarProps[key] = value;
85
+ continue;
86
+ }
87
+ const existingType = normaliseValueType(rawType);
88
+ // Only a non-empty string can reach a temporal constructor. Anything else
89
+ // is refused here rather than probed: Neo4j would reject it too, and this
90
+ // way the message names the key without a round trip.
91
+ if (typeof value !== "string" || value.length === 0) {
92
+ throw typePreserveRejection(key, existingType, value);
93
+ }
94
+ const paramName = `tp${paramIndex++}`;
95
+ params[paramName] = value;
96
+ assignments.push(`${alias}.${quoteKey(key)} = ${constructorName}(\$${paramName})`);
97
+ probes.push({ key, constructorName, paramName, value, existingType });
98
+ }
99
+ // The stamp is emitted last so it wins over anything in `props`, exactly as
100
+ // the trailing `n.updatedAt = $updatedAt` did before.
101
+ const nowRawType = existingTypes[now.key];
102
+ const nowConstructor = nowRawType ? temporalConstructorFor(nowRawType) : null;
103
+ if (nowConstructor) {
104
+ const nowType = normaliseValueType(nowRawType);
105
+ if (!NOW_CAPABLE.has(nowConstructor)) {
106
+ throw typePreserveRejection(now.key, nowType, now.isoValue);
107
+ }
108
+ assignments.push(`${alias}.${quoteKey(now.key)} = ${nowConstructor}()`);
109
+ }
110
+ else {
111
+ const paramName = `tp${paramIndex++}`;
112
+ params[paramName] = now.isoValue;
113
+ assignments.push(`${alias}.${quoteKey(now.key)} = \$${paramName}`);
114
+ }
115
+ params.props = scalarProps;
116
+ return {
117
+ setClause: [`${alias} += \$props`, ...assignments].join(", "),
118
+ params,
119
+ probes,
120
+ };
121
+ }
122
+ //# sourceMappingURL=temporal-type-preserve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"temporal-type-preserve.js","sourceRoot":"","sources":["../../src/lib/temporal-type-preserve.ts"],"names":[],"mappings":"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"}
@@ -55,6 +55,32 @@ describe("memory-update-by-name — happy path", () => {
55
55
  expect(params).toEqual({ accountId: ACCOUNT_ID, name: "BioSymm Technologies Ltd" });
56
56
  });
57
57
  });
58
+ describe("memory-update-by-name — Task 2148 type preservation is inherited", () => {
59
+ // This tool owns the lookup and nothing else: the write, the gates and the
60
+ // type-preserving SET all live in memoryUpdate, which it delegates to. So the
61
+ // guard here is that the properties bag arrives verbatim — if it ever grew its
62
+ // own write path, a :Meeting reachable by name would be a second route to the
63
+ // string retype that made Adam Mackay's golf afternoon publish as open time.
64
+ it("hands a temporal property straight to memoryUpdate without rewriting it", async () => {
65
+ mockSession.run.mockResolvedValueOnce({ records: [record("4:meeting:17")] });
66
+ memoryUpdateMock.mockResolvedValueOnce({ nodeId: "4:meeting:17", labels: ["Meeting"], updated: true });
67
+ await memoryUpdateByName({
68
+ label: "Meeting",
69
+ name: "Golf",
70
+ accountId: ACCOUNT_ID,
71
+ properties: { startsAt: "2026-07-29T13:30:00Z", endsAt: "2026-07-29T17:30:00Z" },
72
+ createdBy: CREATED_BY,
73
+ });
74
+ expect(memoryUpdateMock).toHaveBeenCalledWith({
75
+ nodeId: "4:meeting:17",
76
+ properties: { startsAt: "2026-07-29T13:30:00Z", endsAt: "2026-07-29T17:30:00Z" },
77
+ accountId: ACCOUNT_ID,
78
+ createdBy: CREATED_BY,
79
+ });
80
+ // The lookup is the only statement this tool runs itself.
81
+ expect(mockSession.run).toHaveBeenCalledTimes(1);
82
+ });
83
+ });
58
84
  describe("memory-update-by-name — reject paths", () => {
59
85
  it("throws 'not found' on zero matches with the lookup tuple in the message", async () => {
60
86
  mockSession.run.mockResolvedValueOnce({ records: [] });