Skip to content

fix(platform): retire dead shared vocabulary and complete the config lanes - #3260

Merged
larryro merged 12 commits into
mainfrom
fix/shared-lib-dead-vocabulary
Sep 6, 2026
Merged

fix(platform): retire dead shared vocabulary and complete the config lanes#3260
larryro merged 12 commits into
mainfrom
fix/shared-lib-dead-vocabulary

Conversation

@larryro

@larryro larryro commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Shared-lib theme of the backend fix campaign: retire the 0.4 vocabulary that survived the rewrite with no reader, and finish the config lanes that were half-wired (the file-events watcher, redirect semantics in safeFetch, unread schema fields).

Findings fixed

  • lib-shared-rest-1 — lib/shared/platform/** (all but when_predicate) deleted; knip parking lines for the tree, metrics-window and text-matching dropped (surfaced + fixed one dead export each).
  • lib-shared-rest-2 — config watcher restored with chokidar so /events/file (TALE_FILE_EVENTS) actually emits; Vite plugin guard; env-reference row corrected (TALE_FILE_EVENTS) in en/de/fr.
  • lib-shared-rest-3 — resolve-provider-locale / expand-model-variants / model-list / file-url-batch deleted; canonicalize-config trimmed to sortObjectKeysDeep.
  • lib-shared-rest-4 — [TAG] system-message vocabulary, SystemNotice and the mock's [human_input_response] clause deleted.
  • lib-shared-rest-5 — config registry rewritten onto the 0.5 file tree; v8SyncseedSchemas; V8 fields/helpers gone.
  • lib-shared-rest-6 — MICROSOFT_AUTH_ENABLED removed from every injection site (server.ts, Vite inject-env, observatory/server fixtures).
  • lib-shared-rest-7 — safeFetch/safeFetchBinary share one followRedirects helper: 303, and 301/302 after a POST, switch to GET and drop body + content headers; redirect tests added.
  • lib-shared-schemas-7 — chatFilterConfigSchema.preferNonStreamingForFiltering removed (schema strips unknown keys, old files still parse); UI toggle removed.
  • lib-shared-schemas-9 — harnessDefinitionSchema.pinnedVersion dropped from the schema and the nine harness.yml files; Dockerfile ARGs are the pins.
  • lib-shared-schemas-10 — emptySsoConnectionFile, ssoUserInfoSchema.companyName/customAttributes removed (additive to fix(platform): close SSO login gaps and remove dead SSO paths #3224's edits of the same file).
  • lib-shared-schemas-11 — formatZodErrorFull deleted (folded into the rest-1 commit).
  • lib-shared-schemas-12 — completed instead of removed: both schemas are .strict() and all nine shipped packs declare icon+labels, so removal would refuse existing packs and need a migration. automationDisplayIcon/automationDisplayLabels resolvers added; the automations list renders the glyph (ConfigIcon, Sparkles fallback like the skills table) and the labels as chips. Docs catalog.md en/de/fr updated.

Skipped

(none)

Tests & gates observed

  • bunx tsc --noEmit — exit 0
  • bunx oxlint --type-aware — exit 0
  • bunx vitest --run --project serverTest Files 531 passed (531) / Tests 6124 passed (6124)
  • bunx vitest --run --config vitest.ui.config.ts app/features/automations/components/automations-list.test.tsxTest Files 1 passed (1) / Tests 8 passed (8)
  • git merge-tree --write-tree origin/main HEAD (main = 5127ecd) — clean, no rebase needed
  • bun run check — exit 1 solely at format:check: services/platform/backend/domains/tasks/reattach.stalled.test.ts — that file is byte-identical to origin/main (pre-existing, not touched by this branch). The pipeline short-circuits there, so its remaining stages were run explicitly:
    • uvx ruff@0.15.0 format --check — exit 0
    • bunx turbo run format:checkTasks: 8 successful, 8 total
    • bunx turbo run lint typecheck test test:uiTasks: 40 successful, 40 total; @tale/platform:test: Test Files 539 passed (539) / Tests 73363 passed (73363); @tale/platform:test:ui: Test Files 454 passed (454) / Tests 3507 passed (3507); @tale/ui:test: 124/1181, @tale/web:test: 23/201, @tale/docs:test: 31/201 all passed
  • bun run knip:check — exit 0 (one configuration hint, cron-parser in ignoreDependencies, pre-existing on main — this branch's knip diff never touches that line)
  • Integration proof (run-itest.sh, real Postgres): [itest] 495/496 checks passed across 135/135 lanes, no RUN TRUNCATED; the single FAIL is webdav re-home, red on main before this campaign (webdav theme)

Notes for the reviewer

Merged origin/main (42f903e) — 4080dff

Merge commit, no rebase. The only manual resolution was the "Feature flags" table in docs/{en,de,fr}/self-hosted/configuration/environment-reference.md: main's rows are kept verbatim (#3248's four TRUSTED_*_HEADER rows, #3262's TALE_DEPLOYMENT_CONFIG_ADMINS wording) and this branch's corrected TALE_FILE_EVENTS row replaces the stale FILE_EVENTS_ENABLED one. The en/fr descriptions were trimmed by a few words so the row fits main's existing column width — git diff origin/main HEAD -- 'docs/*/self-hosted/configuration/environment-reference.md' is exactly one row per locale (3 files changed, 3 insertions(+), 3 deletions(-)). knip.config.ts, governance.ts and the three messages/*.yml auto-merged.

Gates re-run on the merged head: bun run --filter @tale/docs test (31 files / 201 tests), build:search-index (tree clean afterwards), services/platform bunx tsc --noEmit, bunx oxlint --type-aware, bunx vitest --run --project server (Test Files 550 passed | 1 skipped / Tests 6246 passed | 5 skipped), root bun run knip:check (exit 0, same pre-existing cron-parser hint) — all green.

Merged origin/main (c70872f) — 7d64ffd

Second maintenance merge: knip.config.ts conflicted because #3253/#3256 dropped two ignore entries (lib/shared/constants/agents.ts, lib/shared/schemas/approvals.ts) from the same list this branch trims; resolved by keeping both sides' removals. Gates on the merge: platform tsc --noEmit clean, oxlint --type-aware clean, root knip:check clean (pre-existing cron-parser hint only).

lib/shared carried modules whose only importer was their own test: the
0.4 Convex/Puck platform vocabulary under lib/shared/platform (render
kinds, function bindings, run-capacity indicators, connector list pages —
everything but when_predicate.ts, which the task board still uses), the
provider-locale / model-variant / model-list / file-url-batch helpers
written for 0.4 screens 0.5 never ported (model-list was also a second
copy of @tale/shared/utils/model-list), the agent/workflow canonicalizers
in canonicalize-config.ts (only sortObjectKeysDeep is read, by
file_io.ts), and formatZodErrorFull whose "CLI gate" consumer never
existed.

Delete them. Drop the knip parking lines that hid the platform tree and
the two live modules metrics-window.ts / text-matching (they have real
importers) so knip audits them again — that surfaced one dead export in
each (utcDayStart is internal; BoundaryMode was never used), fixed here.

Findings: lib-shared-rest-1, lib-shared-rest-3, lib-shared-schemas-11.
lib/shared/constants/system-message-tags.ts declared a `[TAG]` prefix
contract for system-role chat rows that no 0.5 module writes: the six
body format/parse helpers had no caller at all, and the only reader of
SYSTEM_MSG_TAG / parseSystemMessageTag / getSystemMessageDisplay was
system-notice.tsx, whose own header admitted "nothing on this branch
writes those tags yet". The needs the tags once served were rebuilt on
another channel (StepLimitNotice reads usage.stepLimitHit; the arena
copies an untagged system row), and the mock's `[human_input_response]`
clause simulated a request_human_input resume the 0.5 chat has no tool
for.

Delete the module, its test, SystemNotice (+ test) — a system row now
renders through MessageParts like every untagged row already did — the
mock clause with the contract test that only existed to exercise it, and
the knip parking line.

Finding: lib-shared-rest-4.
lib/shared/config/registry.ts still narrated the 0.4 Convex split: a
`readContext` (node-direct / v8-action / v8-sync), a `dataModel`, a
`watcher` spec nothing ever populated, `V8_SYNC_DOMAINS`, `getV8SyncSpec`
and a `configCache` mirror table no migration creates. The one live
reader (organizations/scaffold.ts) only uses `name`, `scaffoldKind` and
the per-key schema lookup — a "which seeded file gets which schema"
table the `v8Sync` name misrepresented.

Drop the retired fields, types and helpers, rename `v8Sync` to
`seedSchemas`, rewrite the header and entry comments onto the file-tree
contract, un-export what nothing imports, and remove the knip parking
line so the module is audited again. scaffold.ts follows the rename and
loses the sentence that pointed at a deleted `getConfigDomain` caller.

Finding: lib-shared-rest-5.
server.ts and the Vite inject-env plugin both computed
`MICROSOFT_AUTH_ENABLED: !!process.env.AUTH_MICROSOFT_ENTRA_ID_ID` into
window.__ENV__, lib/env.ts typed it with its own getEnv overload and
default branch, and vite-env.d.ts declared a VITE_ twin — yet nothing
reads any of them: the Microsoft sign-in button is gated by the org's
SSO config (`ssoConfig?.enabled`), not by this flag. An operator reading
the servers would wrongly conclude AUTH_MICROSOFT_ENTRA_ID_ID toggles the
client.

Remove the field from every injection site, the env accessor and the two
test fixtures. backend/auth keeps reading AUTH_MICROSOFT_ENTRA_ID_ID.

Finding: lib-shared-rest-6.
lib/config-watcher.ts had been a permanent no-op since the backend
rewrite: createConfigWatcher logged "stubbed" and never invoked a
callback. Everything downstream was still live and switched on —
TALE_FILE_EVENTS=true in every dev compose, server.ts mounting the
authenticated per-org SSE door and raising Bun's idleTimeout for it, the
Vite plugin serving the same door, and use-file-events.ts holding a
reconnecting EventSource in every tab — so each tab paid for a stream
that could never carry an event, and the promised on-disk edit → cache
invalidation never fired.

Restore the producer with chokidar (already a dependency): watch
$TALE_CONFIG_DIR recursively, map `<orgSlug>/<domain>/<item>…` onto the
event the frontend cache keys on ({ type: <domain dir>, orgSlug, slug }),
skip dot entries (.history/, atomic-write temp files) at the watch level,
coalesce the burst one write produces into one event, and tear the
watcher down on close(). The domain is the dir name rather than a
registry lookup because live surfaces such as branding/ are not
registered. Every event carries an orgSlug, which the server's
default-deny fan-out requires. The Vite plugin drops its dead `examples`
fallback and says so when TALE_CONFIG_DIR is absent instead of watching
nothing silently.

The env reference documented this switch under the wrong name
(FILE_EVENTS_ENABLED is the window.__ENV__ field) with a wrong
description ("OneDrive-sync connector"); the row now names
TALE_FILE_EVENTS and what it does, in en/de/fr.

Finding: lib-shared-rest-2.
safeFetch and safeFetchBinary each carried their own copy of the redirect
loop, and both replayed the original method, body and content headers
against every Location: a POST answered with 303 — the common
"POST /things → 303 /things/123" pattern — re-executed the POST with its
JSON body (Authorization still attached on same-host hops), performing
the mutation twice or handing a body to a read endpoint. RFC 9110
§15.4.4 requires GET after a 303; 301/302 to a POST switch to GET in
every mainstream client. Real non-GET callers exist (tts/service.ts,
connectors/live-host.ts). No test covered redirects at all.

Fold the two loops into one fetchFollowingRedirects; per hop, a 303 (any
method but HEAD) or a 301/302 to a POST continues as GET with no body and
without Content-Type/Length/Encoding, while 307/308 keep method and body.
Cross-host credential stripping and per-hop re-validation are unchanged.
Tests pin POST→303, POST→301/302, POST→307/308, GET→302, HEAD→303 and the
binary sibling with a scripted fetch.

Finding: lib-shared-rest-7.
chatFilterConfigSchema.preferNonStreamingForFiltering was a live control
in Settings > Governance > Content safety — toggled, saved into the org's
chat-filter policy, seeded in the builtin catalog and the docs-demo
fixture — and no backend module ever read it: the guardrail chain
(lib/chat/guardrails.ts) streams the same way whatever the value. An
admin control that promises to change streaming and changes nothing is
a misleading control.

Remove the field from the schema, the toggle row with its state and
save wiring from the form, the contentSafety.preferNonStreaming* strings
from en/de/fr, and the key from the catalog file and the e2e fixture.
The schema strips unknown keys, so an org file that still carries the
key keeps parsing.

Finding: lib-shared-schemas-7.
harnessDefinitionSchema.pinnedVersion was declared in all nine
harness.yml files, yet no loader, test, image build or runtime read it:
the versions actually installed are the independent ARGs in
services/sandbox-runtime/Dockerfile, and nothing cross-checked the two.
qwen-code shows how such a copy rots — its yml pinned 0.10.3 while the
image installs no qwen CLI at all. A pin nothing validates misleads an
operator into believing a mismatch would be caught.

Remove the field from the schema, the nine ymls (with qwen's "MUST be
re-verified" note) and the test fixture; the Dockerfile ARGs stay the
single source of the installed versions.

Finding: lib-shared-schemas-9.
enterprise_sso.ts kept a private emptySsoConnectionFile() nothing in
the file (or anywhere else) called, and ssoUserInfoSchema declared
companyName and customAttributes that no OIDC/OAuth2/Graph adapter ever
populates and no provisioning code reads — identity fields the boundary
promised and no protocol front-half produced.

Delete the helper and the two fields; the adapters' SsoUserInfo values
are unchanged.

Finding: lib-shared-schemas-10.
@larryro
larryro force-pushed the fix/shared-lib-dead-vocabulary branch from 9ecc10d to 31d75ed Compare September 6, 2026 02:16
automationPresentationSchema.icon and .labels were copied from every
pack manifest into automations.presentation on upload and on
provisioning, and returned by the list route, but no surface read them:
the automations list showed name + slug only, so the glyph and the
catalog chips all nine shipped packs declare were stored and never seen.

Both the manifest and the presentation schema are strict, so dropping
the two fields would have refused every existing pack carrying them and
needed a migration for the stored rows; the doc-comments name the
surface they were meant for. Complete the path instead: the presentation
module gains automationDisplayIcon (the Iconify id ConfigIcon resolves
offline) and automationDisplayLabels beside automationDisplayName, and
the list's name cell renders the glyph like the skills table does —
neutral fallback for canvas-authored automations — with the labels as
slate chips beside the name. Docs (en/de/fr) name the two additions.

Finding: lib-shared-schemas-12.
@larryro
larryro marked this pull request as ready for review September 6, 2026 02:31
…ocabulary

# Conflicts:
#	docs/de/self-hosted/configuration/environment-reference.md
#	docs/en/self-hosted/configuration/environment-reference.md
#	docs/fr/self-hosted/configuration/environment-reference.md
@larryro
larryro merged commit c9c11d3 into main Sep 6, 2026
48 checks passed
@larryro
larryro deleted the fix/shared-lib-dead-vocabulary branch September 6, 2026 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant