docs: consolidate historical records and verify public manual publication
Publish documentation / publish (push) Successful in 29s

This commit is contained in:
Codex
2026-09-15 14:37:29 +02:00
parent 5f3a7f5975
commit 4ff91e8d6e
63 changed files with 633 additions and 6598 deletions
+9
View File
@@ -10,6 +10,8 @@ on:
- "scripts/build-docs.sh"
- "scripts/verify-public-docs.py"
- "scripts/test-verify-public-docs.py"
- "scripts/verify-auth-docs.py"
- "scripts/test-verify-auth-docs.py"
- "docs/requirements.txt"
- ".gitea/workflows/publish-docs.yml"
workflow_dispatch:
@@ -45,6 +47,13 @@ jobs:
- name: Test public documentation boundary
run: python scripts/test-verify-public-docs.py
- name: Test current authentication documentation
run: |
python scripts/verify-auth-docs.py auth
python scripts/verify-auth-docs.py dwh
python scripts/test-verify-auth-docs.py auth
python scripts/test-verify-auth-docs.py dwh
- name: Build documentation
run: |
mkdocs build --strict
+95 -719
View File
@@ -1,719 +1,95 @@
# ThothII — Project State
Last updated: 2026-09-15.
This file is the short operational snapshot. Stable commands and the architecture mental model
live in `AGENTS.md`; current design and runtime contracts live under `docs/architecture/`,
`docs/contracts/`, `docs/adr/`, and `docs/evidence.md`. Superseded plans and reports are
available from Git history rather than duplicated in the working tree.
The guarded server migration from a legacy checkout to the schema-v2 installation, Gitea source,
Authentik, internal catalog/embedding services, and the PSD workspace repository is documented in
`docs/operations/server-upgrade-gitea-workspace-v2.md`. Treat its operator gates and rollback
requirements as mandatory; do not replace the running server stack in place.
## Public documentation boundary — 2026-09-15
MkDocs now publishes only its explicit public-manual allowlist: 20 product, installation,
usage and administration pages, plus approved assets. Internal developer/project documents
remain in Git but are excluded from generated pages and search. The build and publication
workflow enforce this boundary with `scripts/verify-public-docs.py` and negative fixtures.
The critical review and proposed (not executed) source-file retirements are in
`docs/maintenance/2026-09-15-documentation-cleanup.md`. It also records pre-existing stale
paths later in this snapshot and obsolete authentication/DWH documentation verifiers;
those require a separate reference/test cleanup, not restoration of superseded guides.
Commit `043ffdfa` passed Gitea publication run 122 and the generated `pages` search index
contains only the 20 public pages. The served `/thothii-docs/` URL is still a stale static
copy dated 2026-08-26 with internal pages: locating/updating its actual server publication
path remains blocked on verified administrative access. Do not claim the live site is clean.
## Session review layout deployed — 2026-09-14
Session-only dialogs now use the visible app bounds: artifact/column review grows
up to 80rem wide and the available height; short confirmations grow to 40rem.
Existing primary actions appear above and below session forms and review content,
with shared handlers, validation and pending state. Stop/delete focus Cancel;
rename focuses the name field. Administration surfaces are unchanged. The activity
shortcut is relabelled To Administration / Vai all’Amministrazione, retaining its
workspace-management destination. 778 frontend tests, five responsive browser
scenarios, typecheck, translations and the production build passed. The owner
authorized deployment and the server frontend was recreated at 18:09 CEST with tag `b1723c34-session-dialogs-20260914`. Frontend is healthy,
Omics serves the new assets, and doctor passes 13/13 checks. Core and Omics web
were not restarted. See DESIGN.md for the layout contract and
`docs/reports/2026-09-14-session-dialogs-release.md` for provenance and rollback.
## Session composer and empty Memory fix deployed — 2026-09-14
The embedded shell now caps its height at the portal mount height, keeping steering
and Stop & save visible. Empty authoritative Memory archives return zero results
without requiring embedding/BM25; SQL-rule embedding is lazy and shared. The live
empty PSD archive reproduces 503 with the current image and succeeds with the
candidate. 54 Memory tests, 90 frontend tests, five browser scenarios and both image
builds passed. The owner authorized the restart and core/frontend were recreated on
2026-09-14 at 17:18 CEST with tags `49333a2d-session-memory-fix`, from code committed
as `d6cdffea`. Both are healthy; production Memory search returns `[]`, Omics serves
the corrected CSS, native doctor passes 13/13 checks, and admissions are reopened.
Session inventory is preserved. Backups and rollback images are retained. Native
CLI diagnostics must run as installation owner UID 10001 with access to Compose;
see `docs/reports/2026-09-14-session-layout-memory-fix.md` for exact commands and
the remaining interactive browser acceptance.
## Full/embedded shell and bilingual interface
Full/embedded shell, EN/IT UI, immutable session interaction language, dark theme,
fullscreen and full-mode logout are implemented. The local Mac descriptor explicitly
sets `shell.mode: full` and `shell.defaultLocale: en`; the native `tht` and local
core/frontend images were updated on 2026-09-13. Omics uses embedded with server-side
identity verification and a replaceable presentation-only PortalAdapter; this does
not mean this branch was verified on the production server. Its changes are
in Omics commit `95154e1`; production deployment remains pending.
See `docs/operations/shell-and-localization.md` for integration and installation
instructions and `docs/reports/2026-09-13-full-shell-implementation.md` for tests,
independent reviews, local browser checks and rollback details.
### Documentation handoff before branch closure — 2026-09-13
Current rendering architecture is in `docs/architecture/application-shell.md`;
the exact portal identity/proxy contract is in `docs/install/authentication-upstream.md`.
`docs/operations/server-codex-handoff.md` is the current server delivery/deploy
runbook, including Omics source integration from GitHub, configuration, tests and
rollback. It supersedes the earlier Omics repository-relay instructions. The acceptance matrix is
`docs/testing/authentication-manual-acceptance.md`. README, documentation navigation,
user/installation/authentication guides and descriptor examples point to these
paths. Local examples explicitly use full/en; the projected server example is
for standalone OIDC, not Omics upstream. No runtime configuration or deployment
was changed by that documentation pass. The 2026-09-14 delivery is prepared for
promotion to main; the actual merge is recorded in Git. PSD acceptance remains pending.
### Navigation readiness and session accordions — 2026-09-13
The Workspace navigation button now carries an accessible green/red readiness
dot instead of a separate text row. Green requires a selected workspace and a
successful, current `ready` response; checking, unavailable and other states are
red, with the state exposed through the tooltip and accessible description.
The backend readiness gate is unchanged.
Active sessions (non-archived) and Archive both start collapsed. Their adjacent
headers are the only visible content below the scope tabs: no Sessions heading
or external selection toolbar. Each nonempty panel owns its Select all and
bulk-delete controls, scoped to that list and preserving the other's selection.
As of 2026-09-14, only one section can be open at a time, and either can be
collapsed. Empty lists show only "No sessions yet." The open section uses the
remaining sidebar height, with scrolling content capped at `min(18rem, 35dvh)`. The mobile
navigation dialog also provides a bounded height. Keyboard controls and labels
are retained. See `DESIGN.md` and `docs/guida-utente.md` for the UI contract.
Verification: 768 frontend unit tests, 20 browser scenarios (including 80 mocked
sessions at 390/1280px), TypeScript and the production frontend build passed.
Only the Mac frontend was recreated; it is healthy at `127.0.0.1:8080`.
Core, catalog, Qdrant and embedding containers were not changed. The prior
frontend image is retained as
`thothii-frontend:before-single-session-accordion-20260914` for rollback.
### Current Omics delivery and server handoff — 2026-09-14
The owner corrected the delivery requirement: Omics is obtained from GitHub,
not relayed to another repository as part of this deployment. Any optional
server-side repository copy is solely the owner's separate concern. This
supersedes the 2026-09-13 relay agreement, including historical delivery notes
in the Omics branch. Do not make another remote publication a prerequisite.
GitHub branch `codex/thothii-embedded-shell` at
`https://github.com/Dallavilla-Tiziano/omics_portal.git` was reverified at
`fca10901a73666ca257d8f4cc4b77066295c400a` (functional commit `95154e1`).
The server operator integrates it with the current code in the confirmed Omics
checkout, normally `/home/chirone/omics_portal`, preserving later server changes.
`docs/operations/server-codex-handoff.md` is the authoritative ordered procedure:
inventory, source verification, native CLI and installation projections,
embedded/upstream auth, Omics templates/static assets/proxy, coordinated rollout,
acceptance and rollback. Server deployment and real IdP acceptance remain pending.
## Current product shape
### Shared Memory/Evidence typography — 2026-09-13
Both detail readers now share Manrope and fixed reading roles: 24px card title,
20px section headings, 16px/1.65 narrative text, and 14px metadata/code. Authored
Markdown subheadings remain subordinate to field headings; inline/fenced code no
longer shrinks cumulatively. Full-width layout, paragraph separation and isolated
FAKE examples are retained. All 762 frontend tests and 18 browser scenarios pass,
including computed typography checks at 390/1280/2400px; typecheck, i18n and Docker
build pass. Only Mac frontend was recreated: `87fca0dc5e19`, image `19f1704b6bb2`,
healthy. Core and data services are unchanged. Rollback image:
`thothii-frontend:before-knowledge-typography-20260913`. See
`docs/reports/2026-09-13-knowledge-typography.md`. No PSD/Omics deployment.
### Knowledge reading and isolated fake Memory examples — 2026-09-13
Memory/Evidence details now use all available width, with display-only paragraph
splitting for long plain prose and preserved code/Markdown/source data. Evidence
provenance renders Markdown; copy controls are accessible two-sheet icons.
Memory's separate Formatting examples section contains four FAKE cards (one per
family), automatically expanded for an empty archive. These client-side examples
cannot be edited/saved/indexed and are never submitted to the model or Memory API.
No real archive content was changed. 762 tests, 18 browser scenarios, typecheck,
i18n and Docker build pass. Only the Mac frontend was recreated: `fc4286b5406d`,
image `cbe0fe0dce55`, healthy; other services unchanged. Rollback image:
`thothii-frontend:before-knowledge-reading-20260913`. Details in
`docs/reports/2026-09-13-knowledge-reading.md`; no PSD/Omics deployment.
### Unified Session entry and complete tab borders — 2026-09-13
The sidebar has one Session/Sessione button: return to the current unfinished
session (including pending creation) without resetting/reconnecting it; otherwise
prepare a new question with existing readiness and dirty-edit guards. Creation
still requires submitting a question. A cold document panel is not a running session.
Session tabs now have 11px horizontal/3px vertical padding, at least 38px height,
and matching 1px borders on every side (gray inactive, red active), with no shared
baseline or overlapping bottom border. This supersedes the earlier border removal.
757 frontend tests, 15 browser scenarios, typecheck, i18n and Docker build pass.
Mac frontend `2844778d311c`, image `d58dccfee3f9`, is healthy; only that service
was recreated. Core/data services are unchanged, with no Omics or server deploy.
Rollback image: `thothii-frontend:before-session-navigation-20260913`.
### Login copy and language-selector focus — 2026-09-13
Removed the redundant login eyebrow/icon and installation-account explanation;
the main sign-in heading remains. The full-header language select no longer
shows an outer focus ring after pointer interaction; keyboard focus remains
visible, including after returning with Tab. EN/IT login and both themes are
covered by 36 targeted tests and 14 browser scenarios; typecheck/build pass.
Only the Mac frontend was rebuilt/recreated: `204efd40d3eb`, image `7dd0752ff823`,
healthy. Other containers are unchanged. Rollback image:
`thothii-frontend:before-login-focus-20260913`. No production deployment.
### Full-header and layout refinements — 2026-09-13
The owner's four visual adjustments are implemented: full header uses Omics
`#CB333B` in both themes with a light complete wordmark/controls; Database status
has 16px clearance below its top divider; workspace/model/Done share a compact
desktop row; session-scope tabs have no bottom border. Embedded has no extra header.
Verified with 71 targeted frontend tests, 11 browser scenarios, typecheck and
Docker production build. The Mac frontend was recreated alone and is healthy
(`8a1608ad7fc6`, image `a2f489ebe9de`); Core/data-service containers are unchanged.
Full/en is retained. Rollback image: `thothii-frontend:before-header-layout-20260913`.
See `docs/reports/2026-09-13-header-layout-refinements.md` for validation and rollback.
### Visual review integrated with the full/embedded shell
At the owner's request, the seven commits through `a59624a6` from
`codex/ui-visual-review` are integrated with the shell/i18n work in this checkout,
`codex/prototype-administration-pages`. Both branches started at `2d1b714e`; the
first full-shell build omitted that lateral branch and regressed the installed UI.
The integrated source retains bundled Manrope, shared type roles, catalog/context
alignment, wordmark sizes and composer autosizing alongside full/embedded and EN/IT.
Local Docker was updated at 12:54 UTC on 2026-09-13: frontend image `605a6e6bba68`,
healthy; Core and all data-service containers were unchanged. Mac remains full/en.
The immediate pre-merge frontend is retained as
`thothii-frontend:before-visual-shell-merge-20260913`.
Verification: 755 frontend tests, 11 Playwright visual/interaction scenarios at five
widths, translation-catalog checks, typecheck and production build. See
`docs/reports/2026-09-13-visual-shell-integration.md` for delivery, provenance and
rollback details. The separate visual-review worktree and its rollback images are
retained. No merge to `main`, push or production deployment is part of this delivery.
Gitea #28–#31 follow-up is implemented in this checkout: Workspace's four tabs,
Database list-first entry without the preparation footer, full-height Pi instructions
with installation-host OS selection, and short Admin navigation labels. Core and
session behavior are retained. At the owner's request, these changes were rebuilt into
local Docker on 2026-09-12 at 18:31 UTC. Core/frontend are healthy and the UI at
`http://127.0.0.1:8080` serves the updated bundle. The host projection now supplies
`THT_HOST_PLATFORM=darwin`, so Pi selects macOS rather than the container's Linux OS.
Persistent dependency containers and volumes were unchanged. Rollback images are tagged
`thothii-core:before-admin-28-31-20260912` and
`thothii-frontend:before-admin-28-31-20260912`. See
`docs/reports/2026-09-12-admin-issues-28-31.md` for verification and deployment details.
The latest context-shelf A and five Administration pages are implemented locally.
At the owner's request, local Docker project `thothii-18998cca7b0a` was rebuilt and
its core/frontend recreated on 2026-09-12. The real UI at `http://127.0.0.1:8080`
serves shelf A; both services and their existing dependencies are healthy.
Runtime model projections were regenerated as schema v2 with the single
`zai/glm-5.3` interaction default. Persistent services/volumes were not recreated.
The local launcher `/private/tmp/thothii-memory-preview.sh` now adds
`/private/tmp/thothii-context-a.compose.yaml` last, building from this checkout
instead of the earlier Memory worktree. Previous images are retained under
`thothii-core:before-context-a-20260912` and
`thothii-frontend:before-context-a-20260912`. Remote server deployment remains pending.
Core/session behavior is retained; one independently remembered workspace/model
pair controls both Core and Admin. Unsaved Admin changes block navigation and
context changes; operation activity locks the selectors. See
`docs/reports/2026-09-12-context-shelf-a-implementation.md` for verification and the
remaining server/Omics integration gate. Prototype alternatives remain untouched.
ThothII is a human-in-the-loop datamart builder with three independently built layers:
```text
frontend (React/SSE) → backend (Fastify) → pi --mode rpc → tht/harness → DWH (read-only)
```
The harness owns the deterministic eight-phase NL→SQL workflow and all session persistence.
The backend remains a process/RPC/SSE bridge for sessions and now also owns an isolated PostgreSQL
metadata catalog for administrative database configuration. The frontend renders the review gates
and keeps the live transcript in memory. See
`docs/architecture/components.md` for the detailed component and data-flow map.
## Memory M1–M3, Evidence E1–E3 and joint workflow repair X1 implemented
Browser authorization now derives permissions from validated session roles using the current
catalog. This fixes Memory/Evidence navigation remaining disabled for administrators whose
remembered login predates those permissions; refreshing the page loads the updated permissions.
The owner requested two independent administration projects: Memory management and Evidence
management. Their navigation entries must sit immediately after Database management, as peers;
neither page belongs to Database management. Both require complete browsing, filtering, and CRUD
without an active core session. Shared requirements and the two project briefs are linked from
`docs/plans/2026-09-08-memory-evidence-administration.md`. Memory administration is implemented;
Evidence administration is implemented through external file editing and explicit consolidation.
Evidence editing requires an explicit evolution of the current authoring/publication contract.
The M1 specification is at `docs/plans/2026-09-08-memory-m1-spec.md` and published as
[M1 — Archivio autorevole e amministrazione delle Memory Card](https://git.tylconsulting.it/mptyl/ThothII/issues/27)
with the `ready-for-agent` and `enhancement` labels.
It covers the authoritative store, administrative CRUD, projection recovery, and current
Memory/exemplar producer integration. Its accepted test boundaries are the public harness
service, Fastify APIs, and the AppShell page, joined by a focused real-stack browser path.
The owner confirmed those boundaries and authorized publication on 2026-09-08.
M1 is implemented locally: PostgreSQL authority in `thoth_memory`, versioned installation
migrations, admin CRUD for all four families, structured dependencies and links, explicit
projection recovery, verified recall and current workflow producers. The page requires no
active session or DWH binding. The existing `catalog-migrate` preparation service now runs
Memory migrations as well. Existing installations need that preparation before using M1;
the initial implementation did not deploy or migrate the owner's stacks.
The integrated browser check passed with real authentication, Fastify, ThtRunner, harness,
PostgreSQL and Qdrant; embeddings were deterministic and unrelated Pi/session activity used
test fixtures. See `docs/plans/2026-09-08-memory-m1-validation.md` for results and commands.
M2 is implemented locally: Memory dense/BM25 fusion, physical and business scope filters,
bounded outgoing-link expansion and joint ranking, all resolved against current PostgreSQL
authority. Migration `002_hybrid_projection.sql` makes old dense projections pending until
explicit retry/rebuild; Reference remains separate. The real retrieval check uses the configured
`qwen3-embedding:0.6b` model, a separate Ollama process with a read-only model-volume mount,
and isolated PostgreSQL/Qdrant resources. See `docs/plans/2026-09-09-memory-m2-validation.md`.
M3 is implemented: editable F8 summary grounded in effective approved decisions, explicit
updates with concurrent-edit protection, selected-card/link transactions and durable review
receipts. Finalization no longer saves exemplars implicitly. SQL rules and explained errors
are consulted in the existing F4/F6/F7 gates. Successful Catalog physical synchronization
performs dependency cleanup, preserving the original removals for recovery across restarts.
Migration `003_review_receipts.sql` is required. Validation includes a real GLM 5.3 generation
case against synthetic PostgreSQL data; see `docs/plans/2026-09-09-memory-m3-validation.md`.
Evidence administration and the joint X1 conflict-repair increment are now implemented.
The same local preview was subsequently rebuilt with M3 and migration 003 applied.
Core and frontend now include the final Memory review and physical dependency cleanup.
On 2026-09-09, at the owner's request, the local PSD Docker installation
`thothii-18998cca7b0a` was updated from this worktree. Core/frontend images were rebuilt,
Catalog and Memory migrations completed, and all five services became healthy. The UI is at
`http://127.0.0.1:8080`, using the existing local authentication and persistent volumes.
The `psd-clinical` authoritative Memory archive is initially empty (no legacy import).
The existing installation configuration remains in `/Users/mp/projects/ThothII/deploy/psd/`;
`/private/tmp/thothii-memory-preview.sh` invokes its Compose files with a final build-context
and migration-command override from this worktree. A future build from the main checkout
will use that checkout's code, so retain the worktree override until the changes are integrated.
Memory revision history and compatibility with existing development sessions are not requirements.
The agreed Memory scope includes reusable domain clarifications, SQL construction rules, solved
questions, and explained, approved mistakes to avoid. This extends the current runtime's
`concept_clarified`-only reusable Memory contract. The owner also approved an editable final summary
for proposed additions/updates and Memory consumption in the relevant existing review gates.
Further agreed behavior includes persistent corrections for Memory/Evidence conflicts through
explicit choices, deletion of Memory with invalid dependencies after successful physical schema
synchronization, and bounded functional/regression tests instead of a general quality benchmark.
In-house Memory evolution is approved, including hybrid Qdrant retrieval and explicit card links
traversed in core, without a dedicated graph database. Both capabilities belong to the current
scope. PostgreSQL is the agreed authority for Memory Cards, links, and schema dependencies;
Qdrant is a rebuildable index. Links are reviewed with cards and editable in Administration;
deleting a card removes its incident links while preserving the other cards. The accepted,
implemented architecture is recorded in
`docs/adr/0018-use-postgres-for-memory-and-qdrant-for-retrieval.md`.
For Evidence administration, Q12 rejects a separate editorial publication workflow.
The final R0 direction uses external editors and a manual consolidation command that
validates structure, reports required corrections, updates derived metadata, and
activates the local Evidence index. The operator then checks the diff and runs Git
commit/push manually. No watcher, automated Git, or web editor is required.
The owner's later simplification
instruction removes the requirement to support administrative edits while core work
is in progress. Deliberate corrections made by the workflow itself remain supported.
The context specialist writes Evidence drafts independently of the installation and
without PostgreSQL access; the system refines them and stores them locally for use
and maintenance. The revised Q11 recommendation separates external drafts from a
durable local canonical file archive, removing automatic commit/push from CRUD.
The owner has now accepted local files and requires discoverable, editable Markdown
for nontechnical domain specialists, with no JSONL management surface. Editors on
Mac/PC or vim/nano on the server are the chosen R0 editing surface. Evidence
management keeps browsing/filtering/detail and clearly identifies the persistent
working tree, each Markdown file's actual host path, and the manual commands.
E1 implements editable Curated Evidence v4, deterministic legacy conversion, persistent
local files and a consolidation API with immutable candidates, manual provenance,
deletion records and recoverable activation. E2 connects its installed command, runtime source
selection and administration page. The operator completes Git steps manually.
All 35 PSD units were converted on an isolated copy with identical IDs and typed content.
Tests exercise visible edits through normalization, indexing and recall with real Qdrant;
see `docs/plans/2026-09-09-evidence-e1-validation.md` and
`docs/contracts/curated-evidence-v4.md`. E2 is now running on the local Docker preview:
all 35 PSD units were converted and indexed through the installed command. The editable
host archive is `/Users/mp/projects/ThothII/deploy/psd/evidence-registry/repo/psd-clinical/evidence`.
The original registry-volume checkout and original author repository were retained.
The installation descriptor now includes `workspace-bindings.yaml` and `evidence-host.yaml`
so native maintenance uses the same volumes and checkout as the preview. Descriptor backup:
`/private/tmp/thothii-installation-before-e2.yaml`; previous native binary: `/private/tmp/tht-before-e2`.
The launcher remains `bash /private/tmp/thothii-memory-preview.sh`; its worktree build override
is still required until integration. Runtime lease filenames now include rendered bytes so
upgrading the Evidence renderer does not collide with old immutable configs; Catalog
input fingerprints and readiness are unchanged. See `docs/plans/2026-09-09-evidence-e2-validation.md`.
E3 adds explicit source acquisition/refinement and durable comparisons in Evidence management.
Local drafts go in `evidence/incoming/`; original local documents and configured HTTP/S3
sources are reacquired only by **Import or refresh sources** or installed
`tht workspace evidence refresh --workspace <id>`. Keep/replace decisions, including the
native `workspace evidence decide` command, activate through the existing archive/index
path and have durable retry state. Acquired source versions and remote provenance stay
local; old documentary lineage can coexist with current manual declarations.
The installed preview's 35 PSD sources were verified unchanged with no new proposals.
The previous native CLI is backed up at `/private/tmp/tht-before-e3`.
See `docs/plans/2026-09-09-evidence-e3-validation.md` for tests and validation limits.
X1 adds `reviewer_archive_repair`: closed alternatives with complete before/after content,
explicit rejection/reformulation, admin-only application, durable session receipts and
retry of saved but inactive corrections. The coordinator uses the canonical Memory and
Evidence services; phase approval remains separate. Migration `004_archive_repairs.sql`
is required. See `docs/contracts/archive-repair.md` for authorization and recovery limits.
X1 validation is recorded in `docs/plans/2026-09-09-archive-repair-x1-validation.md`:
both archives were corrected and retrieved through the actual CLI with real PostgreSQL
and Qdrant; desktop/mobile widget behavior and permission failures were verified separately.
The local preview images include X1 and migration 004 is applied. Follow-up technical
acceptance passed with configured GLM 5.3 generating closed conflict alternatives and
the chosen Memory correction persisted and retrieved. An authenticated browser test
also verified both administration pages, navigation order, Evidence filtering, workspace
404s, desktop/mobile layout, and Evidence survival after Memory deletion. All approved
technical increments/checks are complete. A real PSD domain-conflict session remains
the reviewer's semantic acceptance check; automated cases did not modify PSD knowledge.
The joint browser inspection also fixed mobile archive navigation: below 768px,
Memory and Evidence keep the full content width and open navigation in the shared
accessible dialog. Desktop retains its sidebar; the local frontend image includes this fix.
The owner accepted the remaining simplifications and requested explicit clarification
of the core format change and the simple terminal-based Git check. E1 must adapt
the Evidence parser, renderer, authoring, validation, and normalization; convert
existing files and reindex; and verify that visible edits reach core consumption.
Preserve the internal typed model where possible. This precedes the administrative
page and is not merely a presentation change. Human inspection uses normal Git
status and optional line-level diff commands; no custom diff viewer or mandatory
double review is required.
The suggestion of making PostgreSQL the Evidence authority was withdrawn after this
clarification; it was never implemented or accepted as a replacement for Q11.
Q13 keeps manual corrections active when updated sources contradict them, until an
administrator resolves the comparison; deleted Evidence must not be regenerated
automatically. Q14 allows direct manual creation and records a manual declaration
as the current source, preserving any original document as distinct provenance.
Q15 refreshes external sources only on explicit administrator request; normal saves
and lookups do not reacquire them. These decisions, now implemented through E1–E3, are recorded in
`docs/adr/0019-author-evidence-in-app-with-automatic-activation.md`.
The decision-by-decision review is recorded in
`docs/plans/2026-09-08-memory-evidence-simplification-review.md`; it distinguishes the
new constraints from the revised technical recommendations. It also specifies a
sequential save with minimal durable retry state and direct Memory cleanup after
successful schema synchronization, without new queues or event infrastructure.
The delivery order remains Memory, Evidence, and persistent conflict repair between
both modules. Local curated Evidence must survive preprocessing Clear and be backed
up as primary data; the Qdrant projection remains rebuildable.
No runtime gate change or administrative page is implemented yet.
## Evidence restructuring — accepted
The evidence restructuring and PSD migration completed real acceptance on 2026-08-25.
- The curated PSD revision contains 35 approved Evidence units and 60 review items.
- The PSD authoring repository publishes all 35 units using Curated unit schema v3. Its table-free
presentation uses hidden canonical metadata, wrapping Markdown scope lists, list-based enum
values, and collapsed technical provenance. Long domain rules now have a deterministic
human-readable presentation while retaining their exact canonical text for vector ingestion.
`tht evidence migrate <workspace-root>` performs the deterministic v1/v2 upgrade and older-v3
presentation rewrite without model calls. The structured-rule PSD rewrite is currently local and
pending commit/publication.
- The accepted snapshot is
`psd-clinical-675990d90eae51da6f2bd51b1ae2609f245772ef-snapshot`.
- The active generation is `gen:f968b3bd7a553dbfef3cf47093698f2bc7f95f11`.
- Retrieval acceptance reached 20/20 Hit@10.
- A real session, `20301df7-cad7-403d-a4c1-9f35c9d07b66`, completed F1–F8 with five
receipts, three CTEs, and a final result of 78 patients.
- The durable acceptance record is
`docs/testing/evidence/evidence-restructuring-psd-acceptance-2026-08-25.md`.
The canonical authoring, validation, publication, materialization, and preprocessing flow is
documented in `docs/evidence.md`. The governing contracts are
`docs/contracts/workspace-evidence-v3.md` and
`docs/contracts/workspace-preprocessing-cli.md`.
The incremental server procedure for the `260906-preprocessing-complete` release is
`docs/operations/server-handoff-260906-preprocessing-complete.md`.
## Workspace preprocessing and configuration
The native host CLI `tht` is the operator surface. Workspace preprocessing runs through:
```sh
tht --installation /absolute/path/thothii-installation.yaml \
workspace preprocess run --workspace <workspace-id>
```
This complete one-shot command uses the profile-gated `workspace-maintenance` service. Partial DWH,
schema, Evidence, and vector mutation commands are retired.
The right Administration sidebar invokes that same operation for the selected workspace. It shows
only current readiness or the latest bounded failure diagnostic; there is no preprocessing history.
Known non-ready state disables **Session** when it would start a new question,
while backend admission remains authoritative.
The same control exposes an inline-confirmed **Clear** action to remove replaceable reference
vectors, LSH, corpus, and checkpoints while preserving the separate Memory collection. The host CLI
equivalent is `workspace preprocess clear`.
Each workspace now uses `<workspace>-reference` for Schema, relationships, and Evidence and
`<workspace>-memory` for `memory` and `solved_question`. Clear and preprocessing own only the former.
LSH ownership additionally binds the Catalog database ID and Metadata Content Revision, so derived
values cannot be reused across database identities or Catalog revisions.
Workspace descriptors use schema v4 and contain only workspace identity and optional Evidence.
PostgreSQL Metadata Catalog owns database identity, binding, schema, descriptions, sensitivity, and
relationships; model, provider, embedding, and vector-store configuration is installation-owned. For
PSD, workspace content and runtime roots point to the
separate uncommitted repository `/Users/mp/projects/tht-workspace-psd`. Secrets remain outside
Git and are supplied only through installation-local protected files.
## Installation Model Catalog
`thothii-installation.yaml` schema version 2 is the only operator-authored source for session,
metadata-generation, and embedding models. The host `tht` lifecycle validates `modelCatalog` and
regenerates the backend catalog, Pi `models.json`/`settings.json`, and Compose override under the
installation-local `generated/` directory. Those projections are replaceable runtime adapters:
they are not edited, backed up, or treated as configuration.
Core and Administration now share one canonical `modelCatalog.defaults.interaction` per installation,
independent of workspace. Runtime catalog schema v2 contains only `defaultInteraction`; apply the host,
backend, and regenerated projections together. Equal legacy defaults normalize on read; conflicting
ones require an explicit operator choice. With Admin AI configured, selectable models are the
intersection of the Pi and LiteLLM adapters; Core-only installations remain supported without Admin AI.
The existing Core and Database controls share the operational selection. Explicit model choices are
remembered per authenticated user/application mount in this browser, not in installation settings.
Resume uses that selection/default while preserving the historical workspace/revision and manifest.
Every model must be manually exercised in both Core and Admin as documented in
`docs/general/pi-configuration.md`; validation is not a live model certification.
These changes and shelf A are now deployed to local Docker; remote deployment remains pending.
PSD DeepSeek Pro/Flash now share canonical `deepseek/...` identities across native Pi and LiteLLM,
using the existing `DEEPSEEK_API_KEY` bundle entry. Its value was confirmed identical to the working
Pi key without exposing it; no secret files were changed. The duplicate `deepseek-metadata` descriptor
is removed locally and the tracked example uses the shared provider. `secret_env` overrides legacy
Pi auth only inside temporary runtime snapshots and fails closed if the bundle key is missing.
The original auth store/history and `zai/glm-5.3` default are preserved. Local Docker projections
and core/frontend were updated together on 2026-09-12. Regenerate projections with
the matching release for the separate server deployment.
Provider authentication declares
one explicit mode (`secret_env`, `pi_auth`, or `none`); `secret_env` names a protected bundle key.
The backend settings store now owns only the selected workspace and thinking level. Existing v1
installations use the explicit catalog migration command; schema-v3 workspace descriptors are
converted deterministically in their curator-owned repository before commit. Strict runtime loading
does not silently infer or merge legacy sources. ADR 0013 and
`docs/plans/2026-09-02-installation-model-catalog.md` record the decision and implementation.
## Database management
The database, table, and authoritative physical-schema catalog slices are implemented. Database
Management now opens the Fleet Ledger presentation by default inside `AppShell`, lists every YAML
workspace, creates at most one PostgreSQL database configuration per workspace, edits direct
PostgreSQL, REST API, or SSH-tunnel installation bindings, replaces write-only encrypted secrets,
and tests supported connector bindings. The surface keeps one responsive AG Grid visible at a time:
databases lead to tables, tables lead to columns, and relationships are a sibling database view.
Parent navigation remains explicit through the breadcrumb and emphasized back control.
Selection-scoped operations use one action selector plus an explicit **Run** control; ineligible
actions remain visible with their disabled reason, while row-scoped actions stay in the pinned final
column. The KPI strip reads installation-wide or selected-database aggregates from
`GET /catalog/metrics`. Database configuration, metadata editors, synchronization history,
description history, and sensitive-field review/history use the production APIs in right-side
drawers rather than prototype fixtures; closing a history drawer does not stop its background run.
Sensitive-field review is now driven by the versioned local `sensitivity-v4` policy, not by a
catalog model. The backend reads selected source tables through read-only, database-specific
adapters and makes every `sensitive | non_sensitive` draft decision in the TypeScript
`SensitivityClassifier`. A single validated match protects the column. Tables up to 1,000 rows are
fully scanned; larger tables use breadth-first 300, 1,000, and text-only 3,000-value targets, with a
five-second limit per source query and no global request deadline. Source failures fail the run
instead of yielding `unknown`; coverage remains visible separately from the proposal. Draft
assessments remain transient until an administrator explicitly saves them. Optional GLiNER2
evidence is CPU-only, offline, opt-in, and never replaces the deterministic decision point; see
`docs/operations/sensitivity-analysis.md`. The earlier v1 PSD shadow comparison kept NER disabled by
default; see `docs/reports/2026-09-02-psd-sensitivity-shadow.md`. The v2 comparison completed all
2,275 columns: CPU NER added 18 sensitive proposals and increased warm runtime from 50.1 to 61.3
seconds; see `docs/reports/2026-09-03-psd-progressive-sensitivity-shadow.md`.
Version 4 excludes declared `bigint` primary-key columns and conventionally named `pk bigint`
columns before source inspection, reporting both as non-informative structural identifiers while
distinguishing declared constraints from inferred roles.
Physical membership, source
comments, column types/default/nullability/PK positions, and constraint-level ordered FK pairs are
projections of the external schema. They cannot be created, renamed, or structurally edited by
hand, but administrators can explicitly clear catalog tables, columns, or relationships without
touching the source database, binding, configuration, or secrets. Table deletion cascades through
columns and relationships; table-scoped relationship cleanup includes incoming and outgoing
relationships. Curated and generated descriptions are editable; generated descriptions start null
and Database Management can generate or consolidate them for selected tables, selected columns,
all targets, or only targets whose Generated Description is missing.
Relationship Management is now reachable directly from each configured Fleet database. One
Relationship Map shows read-only Physical Relationships together with Generated and Manual Logical
Relationships, with Active, Excluded, and All filters. Administrators can add a single-column
relationship, run deterministic name/PK/type inference, exclude or restore a logical relationship,
or delete it permanently. Exclusion retains a tombstone that a rebuild cannot reactivate; permanent
deletion allows a later rebuild to infer the same endpoints again. Inference uses no LLM, embedding,
or source values. It supports normalized table-qualified names, unique non-generic PK names,
composite-PK source columns, and the `*time_key -> dim_time.<single PK>` warehouse convention while
ignoring bare generic names. Explicit table/column metadata cleanup remains a destructive boundary: it removes
the attached logical relationships and exclusions and requires a full schema synchronization before
inference or runtime publication can continue.
The previous Database Management renderer remains a temporary comparison fallback for development
and staging only: `?db-ui=legacy` is honored in Vite development or when
`VITE_DB_MANAGEMENT_LEGACY=true`; it is not a production presentation. The standalone Fleet Ledger
prototype on port `5173` also remains temporary until owner acceptance of the integrated surface,
after which both migration aids can be removed.
Schema refresh is one durable asynchronous engine with database-table, selected-table-column,
relationship, and full-database actions. Database-level menus expose only the table, relationship,
and full scopes; selecting tables exposes column synchronization plus manual column and relationship cleanup for that subset. Database selections
also expose manual table and relationship cleanup. Cleanup selections are atomic and share the
one-active-operation-per-database exclusion with synchronization. Runs have leases and
restart recovery, atomic apply, destructive-diff confirmation with re-scan, cancellation before
apply, retained history, and a live SSE log with polling fallback. Null metadata renders blank
rather than as a placeholder.
Direct PostgreSQL and strict known-host-verified OpenSSH use `pg_catalog`. REST bindings use the
typed full-snapshot `POST /rpc/schema_snapshot` contract when available. Servers such as the
current PSD endpoint that exposes only `POST /rpc/run_query` use one catalog-owned read-only query
to return the exact same strict v1 snapshot in a single round trip. Both paths remain fail-closed:
an absent capability, query error, partial result, or invalid snapshot applies no catalog changes.
SSH is not yet enabled for NL→SQL session runtime.
The catalog runs in the internal `catalog-db` PostgreSQL service. Kysely migrations are an explicit
one-shot `catalog-migrate` operation; `scripts/run-stack.sh` runs it before local startup. Runtime
sessions consume an immutable Catalog JSON snapshot tied to the runtime-config lease. It contains
the tables, columns, effective descriptions, sensitivity flags, and active relationships used by
the harness; PostgreSQL is the exclusive runtime authority for database metadata. Authored
workspace YAML remains limited to workspace identity and optional Evidence configuration. The
accepted design is recorded in ADR 0016 and the contracts under `docs/contracts/`.
Semantic aliases, value descriptions, synonyms, and concepts remain deferred to their dedicated
slices.
AI Description Generation uses the catalog's human-owned Sensitive Data Flag. The flag defaults to
`false`, including for newly synchronized columns. An administrator may request a local sensitivity
analysis for one selected database, selected tables, or selected columns. One deterministic
TypeScript classifier combines metadata, bounded source-content rules, and optional CPU-only NER;
no generative model decides the result. Its `sensitive` or `non_sensitive` assessments remain an
unsaved draft until the human reviews and saves any chosen flag changes, including a downgrade to
non-sensitive. Coverage is reported separately; interrupted history may count unprocessed columns.
Each started analysis records a separate Sensitivity Analysis Run with aggregate counters and safe
ordered events. The progress drawer opens before the synchronous request completes, polls the run,
and displays sanitized source-scan and local-NER phase/batch activity while classification is in
progress. This operational history never stores per-column assessments, source values,
matched spans, prompts, or free-form diagnostics. Saving a sensitive decision persists a sanitized
Sensitivity Reason as column Catalog Metadata alongside the human-owned flag; clearing the flag
clears that reason. Reloading still discards an unsaved review draft.
For unprotected columns, up to five source rows and five representative non-null values may be sent
transiently to the configured model provider. Protected columns are omitted from source reads and
replaced in the prompt by deterministic plausible values derived only from their metadata. Existing
descriptions are not regenerated when a flag changes.
The accepted AI-description design is recorded in
`docs/plans/2026-08-28-ai-catalog-description-generation.md`, with the formal specification in the
adjacent `-spec.md` document and Gitea issue #4. Gitea issues #5–#11 deliver the implementation.
The runtime deliberately keeps ThothAI's simple operating model: one installation-wide sequential
run owned by the backend, one short-lived Python/LiteLLM completion helper per request, and
persistence limited to the run, its safe ordered text events, and each Generated Description as
soon as it succeeds. The helper performs at most one provider retry and never falls back to another
model. Stop terminates the current helper and retains prior results; three consecutive exhausted
technical batches fail the run. Startup marks stale queued/running work interrupted, and Unlock is
available only when no local start, worker, or helper is live. Runs remain inspectable through a
live SSE log with ordered polling fallback; there is no automatic resume or user-facing generation
CLI. ADRs 0009–0010 record the runtime and source-sampling decisions.
The Installation Model Catalog accepts the protected `DEEPSEEK_API_KEY` and `ZAI_API_KEY`
references for metadata-generation providers.
It also accepts a model with no secret reference only when its OpenAI-compatible endpoint is
explicit; this covers the VPN-only AritmoLab Qwen 3.6 server without creating a fake operator
credential. The Python client supplies only its fixed non-secret compatibility placeholder.
The AritmoLab entry also sets `disableThinking: true`, mapped to the endpoint's chat-template flag,
because its default reasoning prose would violate the worker's exact JSON response contract.
Logical relationship integration with core schema-linking is complete: session creation and resume
materialize the active physical/generated/manual map, retrieval-pack generation and Pi receive the
same runtime config, and snapshot validation fails closed on a declared missing, invalid, or orphaned
endpoint. Broader publication of other Catalog metadata to schema-linking remains a separate future
slice.
**Deferred follow-up — Sensitive Data Policy in schema-linking.** The policy is first delivered
and tested in catalog description generation. Its enforcement for core schema-linking remains
out of scope until the current tickets are closed and the owner has completed the acceptance test.
At that gate, resume the design: `tht` must receive a read-only projection of the current Sensitive
Data Flags and exclude values from columns marked sensitive from every LSH result before it is
given to Pi. Do not start this integration before the owner gives final approval after that test.
## Active deployment work and manual gates
### PSD server deployment program
The approved design and executable entry point are:
- `docs/plans/2026-08-20-psd-server-deployment-program-design.md`
- `docs/plans/2026-08-20-psd-server-deployment-program.md`
- `docs/plans/2026-08-20-psd-server-survey.md`
- `docs/plans/2026-08-20-psd-server-project-a-standalone.md`
- `docs/plans/2026-08-20-psd-server-project-b-authentik.md`
Last recorded state:
- survey: `SURVEY_NO_GO`;
- Project A: `BLOCKED_BY_SURVEY_AND_MUTATION_GATE`;
- Project B: `BLOCKED_BY_PROJECT_A_AND_PRE_B_GATE`.
The deployment is a clean replacement: legacy sessions, indexes, and application configuration
are not migration inputs. The existing stack remains intact until its documented mutation and
rollback gates are explicitly approved. Shared Omics/LocalLLM networks, ETL Evidence, DWH,
`dwh-auth`, Supabase, Authentik, Superset, and Aritmolab are outside cleanup scope.
Human acceptance guides and sanitized report templates live under `docs/testing/` and
`docs/testing/evidence/`. The remediation checklist is
`docs/operations/psd-server-survey-remediation-checklist.md`.
### Authentication
The local/OIDC authentication remediation passed its automated review on 2026-08-18. Release and
PSD mutation gates remain governed by:
- `docs/architecture/authentication.md`;
- `docs/plans/2026-08-18-thothii-authentication-acceptance-and-psd-deployment.md`;
- `docs/operations/psd-dwh-auth-rollout.md`;
- `docs/testing/authentication-manual-acceptance.md`.
Do not infer authorization for server, Nginx, Authentik, database, credential, or cutover changes
from an automated PASS.
## Verification status
- The P1.1 workspace-directory registry and P2–P6 preprocessing workstreams are implemented and
have automated coverage.
- Evidence restructuring has a real PSD acceptance PASS as recorded above.
- AI Description Generation has automated coverage across installation setup, model selection,
generation/consolidation scopes, bounded sampling, cancellation/recovery, history, SSE/polling,
and the LiteLLM helper boundary.
- L2 tests requiring real providers or remote databases remain opt-in.
- Server deployment, release, and owner-operated acceptance steps remain pending wherever the
referenced runbooks require explicit approval.
Run the layer-specific checks documented in `AGENTS.md`. For release-sensitive changes, also run
the repository contract scripts in `scripts/` and build the MkDocs site.
## Operational invariants
- `tht`'s `-c`/`--config` option follows the subcommand; it is not a global option.
- `--json` commands write pristine JSON to stdout.
- Persisted phase documents and the decision ledger are the source of session truth; chat is not.
- UI chrome is English; workspace document content retains the workspace language.
- The backend refuses resume for finalized or archived sessions.
- A resume must send `/riprendi-sessione <id>`; a new session must send `/nuova-domanda`.
- DWH access is read-only.
# Project state
Updated: 2026-09-15. This is a current snapshot, not a release diary. Stable commands
and invariants are in [AGENTS.md](AGENTS.md); prior snapshots remain in Git.
## Current contracts
- React supports full/embedded rendering independently of local/OIDC/upstream auth,
with EN/IT UI and immutable session interaction language. See
[application shell](docs/architecture/application-shell.md) and
[localization](docs/operations/shell-and-localization.md).
- PostgreSQL Metadata Catalog owns database identity, binding, schema, descriptions,
sensitivity and relationships for all core consumers. Workspace schema v4 contains
identity and optional Evidence only. Installation schema v2 is the authored model
catalog source. See [overview](docs/architecture/overview.md) and
[model configuration](docs/general/pi-configuration.md).
- The harness owns workflow persistence; chat is not the durable session record.
Memory uses PostgreSQL authority and derived Qdrant dense/BM25 search. Editable
Evidence has local archive authority and manual consolidation. File save, search
activation and Git publication have distinct outcomes. See
[Memory](docs/gestione-memory.md), [Evidence](docs/contracts/curated-evidence-v4.md)
and [consolidated release evidence](docs/reports/knowledge-archives-release.md).
- Reference preprocessing must not clear Memory. Use the installation-scoped
`tht --installation /absolute/path/thothii-installation.yaml workspace preprocess run`
and its [contract](docs/contracts/workspace-preprocessing-cli.md).
- DWH sessions are read-only. SSH tunnels support database-management diagnostics
and metadata synchronization, not NL→SQL session creation; use direct or REST
transport for sessions.
## Installation and workspace boundaries
Fresh standalone installations follow the manual terminal procedures in
[Italian](docs/install/standalone-manual-it.md) or
[English](docs/install/standalone-manual-en.md), without an installer or launcher.
The [Compose reference](docs/operations/compose-reference.md) is for maintainers,
not another quick start. Catalog and Memory migrations are explicit.
Descriptors, authentication, provider credentials, certificates and runtime bindings
stay in protected installation-local paths. Do not copy secrets into examples or
workspace Git. Legacy runtime snapshots may use absolute paths and protected
`harness/.env`; do not silently relocate them.
PSD authoring is separate at `/Users/mp/projects/tht-workspace-psd`. Its GitHub
repository was copied to private Gitea
[workspace_psd](https://git.tylconsulting.it/mptyl/workspace_psd), preserving both
branches. It is a copy, not automatic synchronization. Running installations were
not repointed to a different workspace remote.
## Recorded deployments and server authority
Use the ordered [server handoff](docs/operations/server-codex-handoff.md) for
coordinated ThothII/Omics upgrades. Omics integration uses GitHub
`Dallavilla-Tiziano/omics_portal`, with no Gitea relay prerequisite. Omics uses
embedded/upstream identity, not another ThothII OIDC login. Read
[upstream authentication](docs/install/authentication-upstream.md) before changes.
Last recorded application deliveries (not a fresh runtime attestation):
- [Session dialogs](docs/reports/2026-09-14-session-dialogs-release.md):
`b1723c34-session-dialogs-20260914`, frontend-only.
- [Session layout/Memory fix](docs/reports/2026-09-14-session-layout-memory-fix.md):
`49333a2d-session-memory-fix`, core/frontend.
Keep those reports and rollback instructions while operator gates remain open.
A later deployment does not prove every earlier acceptance item passed.
## Remaining acceptance and design gates
- Fresh-machine Mac, Windows/WSL2 and Linux installation acceptance, including real
DWH/model endpoints, remains a separate operator exercise.
- Real IdP/portal login, logout, embedded interaction and PSD semantic acceptance
follow the [manual matrix](docs/testing/authentication-manual-acceptance.md) and
delivery reports; synthetic tests do not close them.
- [Security hardening](docs/plans/2026-09-08-security-hardening-prd.md) is a draft.
Revalidate SEC01–12 and obtain design approval before implementation or real
server/IdP/DWH mutation.
- Optional NER remains opt-in; labeled Italian quality, benchmark and licensing
acceptance are not implied by document cleanup.
- Semantic aliases, value descriptions, synonyms/concepts, dialect and multi-schema
extensions remain explicit design work. Current sensitivity delivery follows the
Catalog contract; additional policies require their own acceptance.
- Legacy database UI fallback (`?db-ui=legacy`, dev/staging) and prototype removal
remain subject to owner acceptance.
## Documentation maintenance
MkDocs publishes only 20 product/operator pages and five approved assets.
Architecture, contracts, ADRs, plans, research, tests and release evidence are
excluded from HTML and search. The repository itself is public: editorial exclusion
is not confidentiality.
The [cleanup record](docs/maintenance/2026-09-15-documentation-cleanup.md) records
retired sources and retained gates. Main contains source; Actions generates the
`pages` branch. The live site requires the separate explicit deployment described
in [public manual publication](docs/operations/public-docs-publication.md).
+15 -61
View File
@@ -30,71 +30,25 @@ installation, use and administration. Developer architecture, contracts, ADRs, t
plans and release records remain in this repository but are excluded from MkDocs
pages and search. This is an editorial boundary, not an access restriction on the
public repository. See the [documentation cleanup review](docs/maintenance/2026-09-15-documentation-cleanup.md)
for the proposed consolidation; no historical source documents have been deleted.
for the executed consolidation and the inventory of historical sources retained in Git.
## Docker Compose: local startup
## Docker Compose and installation
Requirements: Docker Engine with Compose v2. The mandatory stack is `frontend`, `core`,
`catalog-db`, `qdrant`, `embedding`, and the one-shot `embedding-model-init`. DWH and LLM remain external,
configurable endpoints—even when they are co-located with ThothII.
For a fresh installation, follow the complete manual procedure in
[Italian](docs/install/standalone-manual-it.md) or
[English](docs/install/standalone-manual-en.md). Configure protected files first;
then run the documented build, explicit migrations and startup commands with the
same installation descriptor and Compose project. There is no installer or launcher.
From a fresh clone, run these commands from the repository root:
The mandatory stack includes frontend, core, PostgreSQL catalog, Qdrant, Ollama
and the embedding initializer. DWH and LLM endpoints remain external dependencies.
Pi is included in the core image. Credentials and certificates belong in protected
installation-local files, never in the workspace repository.
```sh
cp deploy/env/local.env.example deploy/env/local.env
# Copy docs/install/examples/thothii-installation.local.yaml to a protected operator path,
# replace its placeholders, chmod it 600, and set that exact THT_INSTALLATION_CONFIG_SOURCE.
# Edit deploy/env/local.env, including PI_AUTH_FILE, THT_SECRETS_FILE, and external endpoints.
./scripts/run-stack.sh
```
The low-level stack script builds the core, starts `catalog-db`, runs the explicit one-shot Kysely migrations,
then runs the base+local stack in the foreground. Migrations never run implicitly in backend
startup. The core image contains its Pi runtime; no host `pi` executable is used. For a server
installation, build the image, start the catalog, and run the same migration service before the
application rollout:
```sh
cp deploy/env/server.env.example deploy/env/server.env
# Prepare a mode-600 thothii-installation.yaml from the server example and set its exact
# path as THT_INSTALLATION_CONFIG_SOURCE. Edit all remaining storage/secret/endpoint paths.
sudo scripts/prepare-server-pi-state.sh /srv/thothii/pi-state 10001 10001
docker compose --env-file deploy/env/server.env \
-f compose.yaml -f deploy/compose.server.yaml \
-f deploy/compose.session-server.yaml.example build core
docker compose --env-file deploy/env/server.env \
-f compose.yaml -f deploy/compose.server.yaml \
-f deploy/compose.session-server.yaml.example up -d catalog-db
docker compose --env-file deploy/env/server.env \
-f compose.yaml -f deploy/compose.server.yaml \
-f deploy/compose.session-server.yaml.example run --rm catalog-migrate
docker compose --env-file deploy/env/server.env \
-f compose.yaml -f deploy/compose.server.yaml \
-f deploy/compose.session-server.yaml.example up --build -d
```
The initializer is required for an empty or restored server Pi-state bind. It atomically creates
the three regular targets hidden below the writable parent bind; protected Pi auth and tracked
model/settings sources remain separate read-only mounts. See the server manual before substituting
a root other than `/srv/thothii/pi-state`.
Workspace descriptors come from the Git remote configured by `THT_WORKSPACE_GIT_REMOTE`; their
runtime endpoint and secret bindings remain installation-local. Open
<http://127.0.0.1:8080> (set `THOTH_HTTP_PORT` in `deploy/env/local.env` to choose another
loopback port).
Credentials and certificates are local protected files. Do not put them in environment examples,
workspace YAML, URLs, or Compose interpolation values.
Application state is split across the named `settings`, `pi-state`, `workspace-registry`,
`sessions`, `qdrant-data`, and `embedding-models` volumes. `docker compose down` keeps them.
`qdrant-data` is a derived but persistent index store; `embedding-models` is an Ollama model
cache for `qwen3-embedding:0.6b` with fixed `1024`-dimension embeddings. Only an explicit destructive command such as `docker compose
down --volumes` removes them.
The frontend depends on the core health check and proxies `/health` and `/api/*` to it. The
application health endpoint intentionally checks process readiness only; external dependency
diagnostics are exposed by `tht doctor` and do not prevent the UI from starting.
For developer topology, overlays and lifecycle details, see the internal
[Compose reference](docs/operations/compose-reference.md). Ordinary stop/down keeps
persistent data; removing volumes is destructive and is not an upgrade step.
Process health is distinct from external dependency checks performed by doctor.
## Git-backed workspace repository
+19 -12
View File
@@ -81,20 +81,20 @@ The model proposes; a human reviewer decides at gates through widgets:
The frontend renders these widget descriptors (registry in `src/widgets/`). It rebuilds the live transcript in memory from the SSE stream (`src/store/sessionStore.ts`); it is **not persisted**.
## Curated and immutable Evidence
## Evidence sources, local authority and search projections
The workspace repository is the publication boundary. The curator prepares `evidence/source/`,
reviews units in `evidence/curated/`, validates them, and merges them. With `evidence.schema_version: 2`,
the runtime materializes the full `evidence/` tree from the exact Git commit, but the renderer passes
only `curated/**/*.md` from the immutable revision root to preprocessing. Sources, manifests, and
evaluation data remain available for traceability. The runtime never modifies, stages, commits, or
publishes the authoring repository.
The workspace repository publishes revision-pinned workspace identities and Evidence sources.
An initialized editable Evidence archive is maintained locally through external editors and
explicit consolidation; normal preprocessing or source refresh must not overwrite its manual
corrections. File save, active search generation and a later human Git commit/push are separate
outcomes. See [the current Evidence contract](../contracts/curated-evidence-v4.md) and
[source/publication boundaries](../contracts/workspace-evidence-v3.md).
Before indexing, the curated corpus from the pinned revision is validated. Each workspace has two
physical Qdrant collections with different lifecycles. `reference` contains Schema, relationships,
and Evidence and may be replaced or cleared by preprocessing; `memory` contains `memory` and
`solved_question` records and is never preprocessing output. Only the `reference` collection has the
sparse `bm25` vector with `idf`, and only the Evidence stage writes sparse values.
Each workspace has separate Qdrant collections. `reference` contains Schema, relationships and
Evidence and may be replaced or cleared by preprocessing. Memory uses its own dense/BM25
projection, rebuilt from authoritative PostgreSQL cards rather than old vector payloads or
session artifacts. Both collections can contain sparse vectors; their ownership and cleanup
lifecycles remain separate. See [Memory](../gestione-memory.md).
The Administration control can clear the replaceable reference collection, LSH, corpus, and derived
checkpoints. The operation preserves the memory collection and makes preprocessing required before
@@ -115,6 +115,13 @@ the core can admit a new session.
## Runtime composition
PostgreSQL is the current database-metadata authority for all core consumers, not a deferred
catalog-to-core integration. The historical research on a separate catalog service and
`annotations.yaml` publication is superseded by ADRs 0004 and 0016. Preserve read-only DWH access,
installation-local bindings/secrets, revision-pinned workspace identity, and fail-closed readiness
when changing those boundaries. The exact snapshot interface is the
[Catalog Schema Snapshot contract](../contracts/catalog-schema-snapshot.md).
The local stack is started by `./scripts/run-stack.sh` after the installation descriptor and
`deploy/env/local.env` exist. `core` includes Pi. `catalog-db`, Qdrant, and the Ollama embedding
service are internal Compose services; only the DWH and model-provider endpoint remain external.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

@@ -1,32 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Archify automated browser evidence · thothii-core-sequence.html</title>
<style>
*{box-sizing:border-box}body{margin:0;padding:24px;background:#e9eef5;color:#172033;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}header{max-width:1500px;margin:0 auto 18px}h1{margin:0 0 6px;font-size:20px}p{margin:0;color:#526176}.grid{max-width:1500px;margin:auto;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}figure{margin:0;padding:10px;background:white;border:1px solid #c9d4e3;border-radius:12px;box-shadow:0 10px 30px rgba(15,23,42,.08)}img{display:block;width:100%;height:auto;border:1px solid #e2e8f0}figcaption{padding:9px 4px 2px;color:#526176}@media(max-width:900px){.grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<header><h1>Automated browser evidence</h1><p>thothii-core-sequence.html · visual-check containment pass · perceptual visual review pending</p></header>
<main class="grid">
<figure>
<img src="thothii-core-sequence.visual-check.1440x900.light.png" alt="light 1440 by 900">
<figcaption><strong>LIGHT</strong> · 1440×900 · containment pass</figcaption>
</figure>
<figure>
<img src="thothii-core-sequence.visual-check.1440x900.dark.png" alt="dark 1440 by 900">
<figcaption><strong>DARK</strong> · 1440×900 · containment pass</figcaption>
</figure>
<figure>
<img src="thothii-core-sequence.visual-check.2048x1320.light.png" alt="light 2048 by 1320">
<figcaption><strong>LIGHT</strong> · 2048×1320 · containment pass</figcaption>
</figure>
<figure>
<img src="thothii-core-sequence.visual-check.2048x1320.dark.png" alt="dark 2048 by 1320">
<figcaption><strong>DARK</strong> · 2048×1320 · containment pass</figcaption>
</figure>
</main>
</body>
</html>
@@ -1,548 +0,0 @@
{
"schemaVersion": 1,
"ok": true,
"command": "visual-check",
"evidenceKind": "automated-browser",
"status": "pass",
"visualReview": "pending",
"artifact": {
"path": "/Users/mp/projects/ThothII/docs/architecture/thothii-core-sequence.html",
"sha256": "b521eb942f7889cfc3a5e29010546ba59eeab1cf485c22c533d271ccef9a28d5",
"bytes": 716881
},
"state": {
"detail": "read",
"motion": "still"
},
"chrome": {
"status": "available",
"executable": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
},
"diagnostics": [],
"containment": {
"status": "pass",
"viewports": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1194,
"diagramWidth": 1164,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1600,
"height": 1000,
"theme": "light",
"innerWidth": 1600,
"innerHeight": 1000,
"scrollWidth": 1600,
"scrollHeight": 1000,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1242,
"diagramWidth": 1212,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1920,
"height": 1080,
"theme": "light",
"innerWidth": 1920,
"innerHeight": 1080,
"scrollWidth": 1920,
"scrollHeight": 1080,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1402,
"diagramWidth": 1372,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1818,
"diagramWidth": 1768,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
}
]
},
"readability": {
"status": "pass",
"minimumProjectedNodeTextPx": 6,
"viewports": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1194,
"diagramWidth": 1164,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1600,
"height": 1000,
"theme": "light",
"innerWidth": 1600,
"innerHeight": 1000,
"scrollWidth": 1600,
"scrollHeight": 1000,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1242,
"diagramWidth": 1212,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1920,
"height": 1080,
"theme": "light",
"innerWidth": 1920,
"innerHeight": 1080,
"scrollWidth": 1920,
"scrollHeight": 1080,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1402,
"diagramWidth": 1372,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1818,
"diagramWidth": 1768,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
}
]
},
"viewerChrome": {
"status": "pass",
"viewports": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1194,
"diagramWidth": 1164,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1600,
"height": 1000,
"theme": "light",
"innerWidth": 1600,
"innerHeight": 1000,
"scrollWidth": 1600,
"scrollHeight": 1000,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1242,
"diagramWidth": 1212,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1920,
"height": 1080,
"theme": "light",
"innerWidth": 1920,
"innerHeight": 1080,
"scrollWidth": 1920,
"scrollHeight": 1080,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1402,
"diagramWidth": 1372,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1818,
"diagramWidth": 1768,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
}
]
},
"captures": {
"status": "pass",
"screenshots": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1194,
"diagramWidth": 1164,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light",
"file": "thothii-core-sequence.visual-check.1440x900.light.png"
},
{
"width": 1440,
"height": 900,
"theme": "dark",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1194,
"diagramWidth": 1164,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "dark",
"file": "thothii-core-sequence.visual-check.1440x900.dark.png"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1818,
"diagramWidth": 1768,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light",
"file": "thothii-core-sequence.visual-check.2048x1320.light.png"
},
{
"width": 2048,
"height": 1320,
"theme": "dark",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1818,
"diagramWidth": 1768,
"viewBoxWidth": 1080,
"minimumProjectedNodeTextPx": 7,
"minimumProjectedNodeText": "browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "dark",
"file": "thothii-core-sequence.visual-check.2048x1320.dark.png"
}
],
"contactSheet": "thothii-core-sequence.visual-check.html"
},
"sidecars": {
"receipt": "thothii-core-sequence.visual-check.json",
"contactSheet": "thothii-core-sequence.visual-check.html"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

@@ -1,32 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Archify automated browser evidence · thothii-runtime.html</title>
<style>
*{box-sizing:border-box}body{margin:0;padding:24px;background:#e9eef5;color:#172033;font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}header{max-width:1500px;margin:0 auto 18px}h1{margin:0 0 6px;font-size:20px}p{margin:0;color:#526176}.grid{max-width:1500px;margin:auto;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}figure{margin:0;padding:10px;background:white;border:1px solid #c9d4e3;border-radius:12px;box-shadow:0 10px 30px rgba(15,23,42,.08)}img{display:block;width:100%;height:auto;border:1px solid #e2e8f0}figcaption{padding:9px 4px 2px;color:#526176}@media(max-width:900px){.grid{grid-template-columns:1fr}}
</style>
</head>
<body>
<header><h1>Automated browser evidence</h1><p>thothii-runtime.html · visual-check containment pass · perceptual visual review pending</p></header>
<main class="grid">
<figure>
<img src="thothii-runtime.visual-check.1440x900.light.png" alt="light 1440 by 900">
<figcaption><strong>LIGHT</strong> · 1440×900 · containment pass</figcaption>
</figure>
<figure>
<img src="thothii-runtime.visual-check.1440x900.dark.png" alt="dark 1440 by 900">
<figcaption><strong>DARK</strong> · 1440×900 · containment pass</figcaption>
</figure>
<figure>
<img src="thothii-runtime.visual-check.2048x1320.light.png" alt="light 2048 by 1320">
<figcaption><strong>LIGHT</strong> · 2048×1320 · containment pass</figcaption>
</figure>
<figure>
<img src="thothii-runtime.visual-check.2048x1320.dark.png" alt="dark 2048 by 1320">
<figcaption><strong>DARK</strong> · 2048×1320 · containment pass</figcaption>
</figure>
</main>
</body>
</html>
@@ -1,548 +0,0 @@
{
"schemaVersion": 1,
"ok": true,
"command": "visual-check",
"evidenceKind": "automated-browser",
"status": "pass",
"visualReview": "pending",
"artifact": {
"path": "/Users/mp/projects/ThothII/docs/architecture/thothii-runtime.html",
"sha256": "ad19195852c7c643228663e5bf7f3cb273afb0456fedbe295d4df24bc345b6c7",
"bytes": 724277
},
"state": {
"detail": "read",
"motion": "still"
},
"chrome": {
"status": "available",
"executable": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
},
"diagnostics": [],
"containment": {
"status": "pass",
"viewports": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 968,
"diagramWidth": 938,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 6.20735294117647,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1600,
"height": 1000,
"theme": "light",
"innerWidth": 1600,
"innerHeight": 1000,
"scrollWidth": 1600,
"scrollHeight": 1000,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1009,
"diagramWidth": 979,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 6.478676470588235,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1920,
"height": 1080,
"theme": "light",
"innerWidth": 1920,
"innerHeight": 1080,
"scrollWidth": 1920,
"scrollHeight": 1080,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1172,
"diagramWidth": 1142,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 7.557352941176471,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1583,
"diagramWidth": 1533,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
}
]
},
"readability": {
"status": "pass",
"minimumProjectedNodeTextPx": 6,
"viewports": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 968,
"diagramWidth": 938,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 6.20735294117647,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1600,
"height": 1000,
"theme": "light",
"innerWidth": 1600,
"innerHeight": 1000,
"scrollWidth": 1600,
"scrollHeight": 1000,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1009,
"diagramWidth": 979,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 6.478676470588235,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1920,
"height": 1080,
"theme": "light",
"innerWidth": 1920,
"innerHeight": 1080,
"scrollWidth": 1920,
"scrollHeight": 1080,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1172,
"diagramWidth": 1142,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 7.557352941176471,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1583,
"diagramWidth": 1533,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
}
]
},
"viewerChrome": {
"status": "pass",
"viewports": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 968,
"diagramWidth": 938,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 6.20735294117647,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1600,
"height": 1000,
"theme": "light",
"innerWidth": 1600,
"innerHeight": 1000,
"scrollWidth": 1600,
"scrollHeight": 1000,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1009,
"diagramWidth": 979,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 6.478676470588235,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 1920,
"height": 1080,
"theme": "light",
"innerWidth": 1920,
"innerHeight": 1080,
"scrollWidth": 1920,
"scrollHeight": 1080,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1172,
"diagramWidth": 1142,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 7.557352941176471,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1583,
"diagramWidth": 1533,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light"
}
]
},
"captures": {
"status": "pass",
"screenshots": [
{
"width": 1440,
"height": 900,
"theme": "light",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 968,
"diagramWidth": 938,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 6.20735294117647,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light",
"file": "thothii-runtime.visual-check.1440x900.light.png"
},
{
"width": 1440,
"height": 900,
"theme": "dark",
"innerWidth": 1440,
"innerHeight": 900,
"scrollWidth": 1440,
"scrollHeight": 900,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 968,
"diagramWidth": 938,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 6.20735294117647,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 51,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "dark",
"file": "thothii-runtime.visual-check.1440x900.dark.png"
},
{
"width": 2048,
"height": 1320,
"theme": "light",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1583,
"diagramWidth": 1533,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "light",
"file": "thothii-runtime.visual-check.2048x1320.light.png"
},
{
"width": 2048,
"height": 1320,
"theme": "dark",
"innerWidth": 2048,
"innerHeight": 1320,
"scrollWidth": 2048,
"scrollHeight": 1320,
"overflowX": false,
"overflowY": false,
"ok": true,
"readerWidth": 1583,
"diagramWidth": 1533,
"viewBoxWidth": 1360,
"minimumProjectedNodeTextPx": 9,
"minimumProjectedNodeText": "Browser",
"minimumProjectedNodeTextDetail": "context",
"minimumRequiredNodeTextPx": 6,
"readabilityOk": true,
"hasLegend": true,
"hasNavigationDock": true,
"legendDockIntersectionArea": 0,
"dockStageIntersectionArea": 0,
"dockStageGap": 10.21875,
"requiredDockStageGap": 10,
"viewerChromeStageOk": true,
"viewerChromeReserve": 41,
"viewerChromeActive": true,
"viewerChromeOk": true,
"resolvedTheme": "dark",
"file": "thothii-runtime.visual-check.2048x1320.dark.png"
}
],
"contactSheet": "thothii-runtime.visual-check.html"
},
"sidecars": {
"receipt": "thothii-runtime.visual-check.json",
"contactSheet": "thothii-runtime.visual-check.html"
}
}
+2 -2
View File
@@ -244,5 +244,5 @@ The first installed consolidation performs this conversion automatically when th
legacy manifest is present, then validates and activates the result. Preserve the
existing checkout in backups before upgrading. The E1 validation used an isolated
copy; E2 also converted and indexed all 35 units on the running local preview.
See the [E1 validation report](../plans/2026-09-09-evidence-e1-validation.md) and
[E2 validation report](../plans/2026-09-09-evidence-e2-validation.md).
See the [E1 validation report](../reports/knowledge-archives-release.md) and
[E2 validation report](../reports/knowledge-archives-release.md).
+1 -1
View File
@@ -226,5 +226,5 @@ error. Preparation is repeatable and checks migration checksums.
See [the M1 specification](plans/2026-09-08-memory-m1-spec.md) and
[ADR 0018](adr/0018-use-postgres-for-memory-and-qdrant-for-retrieval.md) for the
approved scope and acceptance boundaries.
See [M2 implementation and validation](plans/2026-09-09-memory-m2-validation.md)
See [M2 implementation and validation](reports/knowledge-archives-release.md)
for the retrieval checks and real embedding test command.
-137
View File
@@ -1,137 +0,0 @@
# Docker installation in the current operating contexts
Rendering and authentication are independent of these Docker contexts. Explicitly
select full/en for the Mac, full/OIDC for an autonomous server, or
embedded/upstream for Omics. The same frontend image supports both renderings;
generated `config.js` and the host page decide the container, while the backend
and trusted proxy decide identity. See [shell configuration and deploy](operations/shell-and-localization.md)
and [server portal authentication](install/authentication-upstream.md). Do not
apply the standalone server authentication projection to the Omics upstream path.
ThothII uses one Compose topology:
- `frontend`
- `core`
- `catalog-db`
- `qdrant`
- `embedding`
- `embedding-model-init`
- `catalog-migrate` (one-shot)
Qdrant and Ollama embedding are required internal Compose services. Only DWH and the LLM remain
external. The fixed model is `qwen3-embedding:0.6b` with 1024 dimensions and cosine distance;
`embedding-model-init` prepares it before `core` starts.
```mermaid
flowchart TB
INSTALL["Installation descriptor"] --> CONTEXT{Context}
CONTEXT --> LOCAL["Local\nCompose local"]
CONTEXT --> SERVER["Server\nCompose server"]
CONTEXT --> SESSION["Session server\noperator services"]
CONTEXT --> AUTH["Auth runtime\nprojection services"]
LOCAL --> BUNDLE["Common secret bundle"]
SERVER --> BUNDLE
SESSION --> BUNDLE
AUTH --> BUNDLE
BUNDLE --> SERVICES["Frontend, core, catalog DB, vector, embedding"]
```
## Short ownership contract
| Componente | Ownership | Contratto operativo |
| --- | --- | --- |
| DWH | External | External endpoint configured by the installation. |
| LLM | External | Endpoint or policy outside the internal semantic infrastructure. |
| Qdrant | Internal | Required internal Compose service with persistent `qdrant-data` volume. |
| Ollama embedding | Internal | Required internal Compose service for `qwen3-embedding:0.6b`. |
## Local path: setup, migration, start
The normal local path is [Install and first start](install/first-start.md). It uses `tht setup`
to create the selected installation-local descriptor and runs the catalog migration explicitly
before application startup.
If an operator intentionally prepares the descriptor and protected files by hand, the equivalent
foreground launch is:
```sh
cp deploy/env/local.env.example deploy/env/local.env
cp deploy/secrets/thothii.secrets.example deploy/secrets/thothii.secrets
chmod 600 deploy/secrets/thothii.secrets
# Copy docs/install/examples/thothii-installation.local.yaml to a protected operator path,
# replace every placeholder, chmod it 600, then set that exact path as
# THT_INSTALLATION_CONFIG_SOURCE in deploy/env/local.env.
./scripts/run-stack.sh
```
Set these values in `deploy/env/local.env`:
- `PI_AUTH_FILE`
- `THT_SECRETS_FILE`
- `THT_INSTALLATION_CONFIG_SOURCE` (the exact protected host `thothii-installation.yaml`)
- `THT_WORKSPACE_GIT_REMOTE`
- DWH endpoint
- LLM endpoint
Do not put secrets in `.env`. Runtime secrets belong in the
`deploy/secrets/thothii.secrets` bundle.
## Secret bundle
The documented and supported bundle keys are:
```dotenv
THT_MODEL_API_KEY=...
THT_DWH_API_KEY=...
OPENAI_API_KEY=...
```
`THT_MODEL_API_KEY` is available only as an explicitly declared catalog bundle key. A
metadata-generation provider references one audited bundle name from `deploy/secrets/README.md`
through `modelCatalog.providers.<provider>.authentication.apiKeyEnv`.
`apiKeyEnv` may be omitted only for an explicit endpoint that accepts unauthenticated requests;
hosted/default endpoints remain keyed.
Provider/model/endpoint settings stay in the protected installation descriptor; raw keys do not.
Compose mounts exactly `THT_INSTALLATION_CONFIG_SOURCE` into `core` as a read-only config and sets
the backend-only runtime path `THT_INSTALLATION_CONFIG_FILE` to
`/run/thothii-installation/thothii-installation.yaml`. Do not set the runtime path in the host env.
Descriptor and bundle changes are loaded only after application restart.
A private PEM CA remains outside the bundle and must be mounted through a reviewed Compose override.
## Preprocessing
Preprocessing runs through the native host CLI and the installation descriptor:
```sh
tht --installation /percorso/assoluto/thothii-installation.yaml \
workspace preprocess run --workspace <workspace-id>
```
Per rimuovere soltanto gli indici e gli artifact ricostruibili, preservando Memory e domande risolte:
```sh
tht --installation /percorso/assoluto/thothii-installation.yaml \
workspace preprocess clear --workspace <workspace-id>
```
The CLI runs the profile-gated `workspace-maintenance` service. See the
[preprocessing contract](contracts/workspace-preprocessing-cli.md) and the
[Evidence guide](evidence.md) for details.
## Server
For server installations, use the server profile with the session overlay:
```sh
docker compose --env-file deploy/env/server.env \
-f compose.yaml -f deploy/compose.server.yaml \
-f deploy/compose.session-server.yaml.example up --build -d
```
Also see [Workspace operations](operations/workspaces.md). Server deployment, reverse-proxy,
backup, and recovery remain manual-gated operations; do not treat the local profile as a server
replacement.
@@ -1,9 +1,78 @@
# Revisione e proposta di bonifica della documentazione
# Revisione e bonifica della documentazione
Data: 15 settembre 2026. Baseline: `6a4634dcf1b1df4ad29510a4da371245abd5c666`.
Ambito: 121 Markdown e 23 altri file già presenti in `docs/`, navigazione, collegamenti
locali, build e verificatori documentali. Questa è una proposta di manutenzione interna,
non una pagina del manuale pubblico e non un'autorizzazione a cancellare i file elencati.
locali, build e verificatori documentali. Il documento conserva la proposta iniziale
e registra sotto l'esecuzione successivamente autorizzata dall'utente. È interno,
non una pagina del manuale pubblico.
## Bonifica eseguita dopo approvazione
Ritirati 35 file dal working tree: 23 Markdown e 12 artefatti visuali generati
(otto PNG, due HTML e due JSON). Recuperabili integralmente dalla revisione
`5f3a7f5975b96fae1e1cdd0da08b4d60d41064cc`, precedente a questo intervento,
mediante `git show REVISIONE:percorso`. Non eliminata né riscritta la storia Git.
- Dettagli Compose utili consolidati in [riferimento developer](../operations/compose-reference.md);
README rinvia alle sole due procedure manuali IT/EN per installazioni nuove.
- M1–M3, E1–E3, X1 e i piani Catalog/model già implementati consolidati nel
[record di rilascio](../reports/knowledge-archives-release.md). Test storici non
presentati come collaudi attuali; estensioni e accettazioni aperte conservate.
- Prompt security assorbito nel PRD, senza approvare il progetto di hardening.
- Snapshot di progetto riscritto; overview corretta su Catalog, Memory ed Evidence.
- Verificatori auth/DWH e fixture riallineati ai documenti attuali: controlli su
permessi, diagnostica, segreti, TLS, separazione dei servizi e link. Le simulazioni
del vecchio journal scanner, non più presente nei documenti, non sono conservate
come finto collaudo del runtime. Le suite applicative non vengono modificate.
- Rimandi Markdown verificati; contratti, ADR (anche superati), progetti con
decisioni ancora aperte e runbook/report con rollback o accettazione pendente
restano nel repository, esclusi dal sito. Non spostati gate in nuove issue né
dichiarati chiusi: lo snapshot corrente li rende espliciti. ADR 0019 conservato
senza dedurre dal solo nome che tutte le sue estensioni siano implementate.
- Pubblicazione effettiva distinta dal branch `pages`; procedura ripetibile nel
[runbook del manuale](../operations/public-docs-publication.md).
File ritirati (percorsi dalla radice):
- `docs/architecture/thothii-core-sequence.visual-check.1440x900.dark.png`
- `docs/architecture/thothii-core-sequence.visual-check.1440x900.light.png`
- `docs/architecture/thothii-core-sequence.visual-check.2048x1320.dark.png`
- `docs/architecture/thothii-core-sequence.visual-check.2048x1320.light.png`
- `docs/architecture/thothii-core-sequence.visual-check.html`
- `docs/architecture/thothii-core-sequence.visual-check.json`
- `docs/architecture/thothii-runtime.visual-check.1440x900.dark.png`
- `docs/architecture/thothii-runtime.visual-check.1440x900.light.png`
- `docs/architecture/thothii-runtime.visual-check.2048x1320.dark.png`
- `docs/architecture/thothii-runtime.visual-check.2048x1320.light.png`
- `docs/architecture/thothii-runtime.visual-check.html`
- `docs/architecture/thothii-runtime.visual-check.json`
- `docs/installazione-docker-4-contesti.md`
- `docs/plans/2026-08-26-metadata-catalog-from-thothai.md`
- `docs/plans/2026-08-28-ai-catalog-description-generation-spec.md`
- `docs/plans/2026-08-28-ai-catalog-description-generation.md`
- `docs/plans/2026-09-02-installation-model-catalog.md`
- `docs/plans/2026-09-08-memory-m1-validation.md`
- `docs/plans/2026-09-08-security-hardening-resume-prompt.md`
- `docs/plans/2026-09-09-archive-repair-x1-validation.md`
- `docs/plans/2026-09-09-evidence-e1-validation.md`
- `docs/plans/2026-09-09-evidence-e2-validation.md`
- `docs/plans/2026-09-09-evidence-e3-validation.md`
- `docs/plans/2026-09-09-memory-m2-validation.md`
- `docs/plans/2026-09-09-memory-m3-validation.md`
- `docs/research/2026-08-23-legacy-thothai-metadata-capabilities.md`
- `docs/research/2026-08-23-postgresql-catalog-deployment-constraints.md`
- `docs/research/2026-08-23-thothii-metadata-publication-qdrant-seams.md`
- `docs/research/2026-09-07-antigravity-gemini-flash-value.md`
- `docs/research/2026-09-07-deepseek-harness-terminal.md`
- `docs/research/2026-09-07-omp-codex-zcode.md`
- `docs/research/2026-09-07-pi-config-vs-oh-my-pi.md`
- `docs/research/2026-09-07-zai-coding-plan-cli-quota.md`
- `docs/research/2026-09-07-zed-acp-omp-pi.md`
- `docs/research/text-to-sql-products.md`
Le sezioni successive e l'inventario fotografano la revisione iniziale: la presente
sezione prevale per le azioni eseguite. Gli altri ritiri sono rinviati finché non
si chiudono i relativi gate; non si cancellano documenti solo perché datati.
## Esito e modifica applicata
@@ -297,7 +366,7 @@ al sito. «Consolidare» e «ritirare» non indicano cancellazioni eseguite.
| `testing/evidence-lifecycle-test-plan.md` | Developer/QA | Conservare criteri ripetibili e gate; separare risultati di singola release. |
<!-- inventory:end -->
## Verifica della modifica
## Verifica iniziale, prima dell'esecuzione dei ritiri
- Build MkDocs con `--strict` e dipendenze bloccate: superata.
- Verifica confine pubblico: 20 pagine, nessun file interno generato o indicizzato.
@@ -309,7 +378,7 @@ al sito. «Consolidare» e «ritirare» non indicano cancellazioni eseguite.
La pubblicazione remota va verificata separatamente dalla build locale: il ramo sorgente
`main`, il ramo generato `pages` e il sito servito non sono la stessa prova.
### Esito remoto del 15 settembre 2026
### Primo esito remoto del 15 settembre 2026 — diagnosi storica
Il commit `043ffdfa` è stato pubblicato su `main`. La
[pipeline 122](https://git.tylconsulting.it/mptyl/ThothII/actions/runs/122) è terminata
@@ -326,4 +395,21 @@ Il tentativo SSH in sola lettura con la configurazione locale non è proseguito:
manca una host key ED25519 conosciuta per `git.tylconsulting.it`. Non è stata disabilitata
la verifica dell'identità del server. Servono host/alias amministrativo verificato e
percorso o meccanismo di pubblicazione prima di intervenire sul sito effettivo.
La bonifica dei sorgenti e della build è completata; quella della copia servita resta aperta.
Al momento di quella verifica la copia servita restava da aggiornare. L'accesso è
stato poi risolto usando l'alias SSH già configurato e fidato `contabo`; nessuna
host key è stata aggirata.
## Verifica della bonifica eseguita
- Build MkDocs strict e confine pubblico: superati, 20 pagine.
- Otto fixture del confine pubblico: superate.
- Contratti auth/DWH correnti e relative fixture di mutazione: superati.
- Suite installazione/workspace, Compose canonico e contratto comandi deployment: superate.
- Link Markdown locali di README, PROJECT_STATE e tutti i documenti: nessun target mancante.
- `git diff --check`: superato.
- `test-no-deployment-coupling.sh`: resta rosso su rilievi preesistenti relativi
a contenuti locali PSD e route Omics. Eseguita anche la versione dello script
precedente alla bonifica: stesso esito e identico insieme di rilievi, nessuno nuovo.
Non è un collaudo del sito e non è stato indebolito per nascondere i risultati.
- Nessuna nuova certificazione applicativa, modifica del runtime o chiusura di
collaudi reali. I gate elencati nello snapshot restano espliciti.
+48
View File
@@ -0,0 +1,48 @@
# Compose reference for maintainers
This is an internal topology reference, not a second fresh-installation recipe. Operators
start with the [Italian](../install/standalone-manual-it.md) or
[English](../install/standalone-manual-en.md) manual. It replaces the duplicated four-context
Docker guide without changing the runtime.
The base topology contains frontend, core, catalog-db, qdrant, embedding, embedding-model-init,
catalog-migrate and profile-gated workspace-maintenance. Pi runs in core; DWH and generative
model endpoints remain installation settings. Reference preprocessing and Memory have distinct
lifecycles and collections. See [preprocessing](../contracts/workspace-preprocessing-cli.md).
## Configuration and migration boundaries
- Use one physical absolute installation descriptor path, its generated operator environment,
Compose project name, base/profile overlays, transport overlays and generated model overlay.
Mixing a generic `deploy/env/local.env` invocation with a native `tht` installation creates
a different stack; it is not an equivalent lifecycle command.
- `THT_INSTALLATION_CONFIG_SOURCE` identifies the protected host descriptor. The read-only
backend runtime mount is `/run/thothii-installation/thothii-installation.yaml`, exposed through
`THT_INSTALLATION_CONFIG_FILE`; the runtime path is not a host source path.
- Catalog runtime/migrator passwords and provider credentials are protected files. A provider's
`authentication.apiKeyEnv` names an allowed bundle entry; it is not a raw key. Private CAs
are separately mounted PEM files, not bundle values. See the repository's
`deploy/secrets/README.md` and the [model catalog guide](../general/pi-configuration.md).
- Generate projections after descriptor edits. Do not edit generated model/auth/frontend files.
Apply the installation's normal restart process when authored configuration changes.
- Explicitly start catalog-db and run catalog-migrate before application rollout on a fresh
database or after an approved schema update. That service runs Catalog and Memory migrations;
neither normal backend startup nor `tht start` implicitly performs them.
- Server deployments retain their reviewed session/auth/network/storage overlays. A writable
server Pi-state parent must be initialized with the regular targets expected by the read-only
nested mounts; use `scripts/prepare-server-pi-state.sh` with the installation's verified UID/GID.
## Deployment-specific authority
For the prepared generic server environment, the base/profile/session override
combination is `-f compose.yaml -f deploy/compose.server.yaml
-f deploy/compose.session-server.yaml.example`, with `--env-file` supplied before
the overrides. Add the reviewed transport/generated overlays for that installation;
this fragment alone is not a complete startup command.
The current [server handoff](server-codex-handoff.md) and
[legacy upgrade runbook](server-upgrade-gitea-workspace-v2.md) retain maintenance, backup and
rollback gates. Embedded/upstream identity is not standalone OIDC. Local/standalone setup
does not authorize replacing a running server stack, resetting volumes, or copying another
machine's descriptor. `scripts/run-stack.sh` remains a low-level path for an explicitly
prepared generic environment, not the public manual's default startup command.
@@ -0,0 +1,90 @@
# Publishing the public manual
This is an internal operator runbook. Publishing documentation must not recreate
the ThothII application, Gitea, proxy, DWH or other documentation containers.
## Three separate states
1. `main` contains reviewed source and the public allowlist in `mkdocs.yml`.
2. Gitea Actions builds and validates the generated `pages` branch. A successful
Actions run alone does **not** update the live site.
3. The live [manual](https://git.tylconsulting.it/thothii-docs/) is served by the
dedicated `thothii-docs` nginx container on the host reached through the existing
trusted SSH alias `contabo`. Its Compose project/service are `thothii-docs`/`docs`.
This procedure is manual. No timer, webhook, credential or automatic deployment
has been added. Use the operator's existing SSH authorization and known host key;
never disable host verification to make a publication work.
## Build and stage
From a clean ThothII `main` checkout already pushed to Gitea:
```sh
git status --short
git rev-parse HEAD
./scripts/build-docs.sh
bash scripts/auth-docs-smoke.sh
bash scripts/verify-dwh-auth-docs.sh
bash scripts/test-auth-docs-smoke.sh
bash scripts/test-verify-dwh-auth-docs.sh
```
The strict build also verifies the public boundary: exactly 20 navigation pages,
five approved source assets, no internal pages/assets, and matching search entries.
Theme-generated assets are separate from those five source assets.
Choose a unique release identifier consisting of UTC timestamp and source SHA.
Record the current symlink and container identity before proceeding:
```sh
ssh -oBatchMode=yes -oStrictHostKeyChecking=yes contabo \
'readlink /srv/thothii-docs/current; docker inspect --format "{{.Id}} {{.State.Health.Status}}" thothii-docs'
```
On the server, create `/srv/thothii-docs/releases/RELEASE/site` only if RELEASE
does not exist. Copy the current `nginx.conf` unchanged into the new release.
Transfer the local built `site/` into that new empty directory with rsync over
the same verified SSH connection. Do not use `--delete` against `current`, and do
not edit routing, TLS, network or authentication configuration.
## Activate only this site
Replace RELEASE below with the validated identifier, not a user-supplied path.
Keep the previously recorded release for rollback.
```sh
cd /srv/thothii-docs
test -f releases/RELEASE/site/index.html
test -f releases/RELEASE/nginx.conf
docker exec thothii-docs nginx -t
ln -s releases/RELEASE current.next
mv -Tf current.next current
docker compose --project-name thothii-docs -f docker-compose.yml \
up -d --no-deps --force-recreate docs
```
Check that `current.next` does not already exist before creating it; stop if it
does, because another publication or recovery may be in progress. Changing the
symlink alone is insufficient: an existing Docker bind mount still resolves to
the old release. The service recreation above remounts the new directory. It may
briefly interrupt this manual only.
## Verify, record, or roll back
- Wait for `docker inspect` to report this container healthy; verify mounted
paths and nginx configuration. Stop after a bounded timeout (for example 60 s).
- Without login/cookies, require HTTP 200 for home, search and both
`install/standalone-manual-it/` and `install/standalone-manual-en/`.
- Compare live home and `search/search_index.json` checksums to the build. Search
must contain only the 20 approved pages, never plans, reports or architecture.
- Require HTTP 404 for retired/internal paths, including `architecture/overview/`,
`plans/2026-09-08-memory-management/`, and `operations/compose-reference/`.
- Record source SHA, release ID, previous release and checks in the cleanup/release
record. Do not call the publication complete solely because `pages` was pushed.
If health or public verification fails, point `current` back to the recorded
previous release using a fresh temporary symlink and the same atomic rename, then
recreate **only** service `docs` with the same Compose command. Verify health and
old site availability. Do not delete either release during recovery. Historical
releases are retained; pruning them requires a separate retention decision.
@@ -722,6 +722,6 @@ la nuova installazione è ferma ma ispezionabile.
- [OIDC generico](../install/authentication-oidc.md)
- [Operazioni workspace](workspaces.md)
- [Configurazione dei modelli Pi](../general/pi-configuration.md)
- [Contesti Docker](../installazione-docker-4-contesti.md)
- [Contesti Docker](compose-reference.md)
- [Database Management](database-management.md)
- [Analisi locale della sensibilità](sensitivity-analysis.md)
@@ -1,695 +0,0 @@
# Metadata Catalog di ThothII: ricognizione ThothAI e percorso incrementale
Data: 2026-08-26; aggiornato 2026-08-27
Stato: ricognizione e progettazione completate; navigazione, CRUD Workspace Database, Catalog
Table, Catalog Column, Catalog Relationship e sincronizzazione durevole dello schema implementati
il 2026-08-27. Generazione AI e integrazione con il workflow core restano negli step successivi.
## Obiettivo
ThothII deve introdurre un contesto amministrativo separato, il **Metadata Catalog**, per gestire
il database associato a ciascun workspace, la sua struttura fisica introspezionata e i metadati
semantici oggi rappresentati da `schema/annotations.yaml`.
Il programma procede per step indipendenti. Il primo step ha aggiunto l'accesso dalla sidebar; il
secondo ha sostituito la superficie vuota con il CRUD di configurazione, il PostgreSQL interno e i
test di connessione; gli step successivi hanno aggiunto navigazione gerarchica, colonne, relazioni
fisiche e sincronizzazione durevole dell'intero schema. Non introduce ancora generazione AI o
integrazione con il workflow core.
Questa analisi usa come riferimento il working tree legacy osservato in
`Thoth/ThothAI`. Non è stato verificato che quel contenuto corrisponda a una release o a un tag
canonico; i percorsi e i comportamenti descrivono il sorgente disponibile il 2026-08-26.
## Decisioni già confermate
1. Ogni Workspace Database appartiene a un solo workspace tramite un `workspace_id` obbligatorio e
univoco; un workspace può avere al massimo un Workspace Database. Poiché i workspace non sono
righe del catalogo PostgreSQL, l'associazione è un riferimento logico validato contro
`thoth-workspaces.yaml`, non una foreign key SQL.
2. Il CRUD non crea né rinomina workspace. Identità e lista ordinata dei workspace restano
autorevoli in `thoth-workspaces.yaml`; il catalogo conserva il loro identificatore stabile.
3. La struttura fisica viene acquisita interrogando il database esterno tramite i dati di
connessione registrati per il Workspace Database.
4. I contenuti semantici equivalenti a `annotations.yaml` vengono generati con l'AI e conservati nel
PostgreSQL interno.
5. Per PSD è prevista l'importazione delle annotations esistenti. Gli altri database partiranno
dalla struttura introspezionata e genereranno i metadati semantici da zero.
6. `annotations.yaml` sarà sostituito anche come input del core in uno step futuro. Il repository è
in fase di test e non è richiesta la conservazione delle sessioni esistenti durante il cutover.
7. La gestione catalogo resta una superficie separata dal processo NL→SQL. La futura integrazione
deve essere esplicita e non deve modificare fasi, gate o semantica del workflow.
8. Il link iniziale è visibile agli utenti con `workspace.manage`, usa stato React locale e non
introduce un router.
9. La pagina iniziale è vuota, segue il tema, nasconde l'intera colonna core e non interrompe una
sessione live. Le azioni di apertura, resume o creazione sessione riportano al core.
10. La compatibilità con il modello ThothAI è semantica, non una copia letterale: configurazione e
contenuti semantici sono campi relazionali mutabili, mentre identità e appartenenza della
struttura fisica derivano dall'introspezione; i segreti restano nel secret store e lo stato dei
job non viene mescolato ai dati amministrativi.
11. Il CRUD amministra il Metadata Catalog e non esegue DDL sul database esterno, che resta
read-only.
12. La prima versione supporta PostgreSQL; il confine di introspezione dovrà permettere di
aggiungere altri dialetti senza cambiare il modello del catalogo.
13. I segreti dei Workspace Database riusano il secret store cifrato di ThothII. Il catalogo
conserva riferimenti ai segreti e nessuna API, esportazione o log ne restituisce i valori.
14. La UI usa AG Grid Community per la lista master e un pannello React separato per il dettaglio;
non dipende dalle funzionalità master-detail di AG Grid Enterprise.
15. Un Workspace Database il cui `workspace_id` scompare dal catalogo YAML non viene cancellato
automaticamente: diventa orphaned e può soltanto essere recuperato, riassegnato o eliminato
esplicitamente da un amministratore.
16. La prima vertical slice gestisce configurazione del Workspace Database, riferimenti ai segreti,
test di connessione e stato. La seconda gestisce le Catalog Table: la collezione e i nomi sono
controllati dall'introspezione, mentre la descrizione curata è modificabile. Le slice successive
hanno aggiunto Catalog Column, Catalog Relationship e sincronizzazione durevole dello schema.
17. Il modello non conserva il `name` libero di ThothAI: nome e ID visualizzati appartengono al
workspace YAML, mentre `database_name` identifica il database PostgreSQL esterno.
18. Database management supporta i tre trasporti già riconosciuti da ThothII: `postgres_direct`,
`rest_api` e `ssh_tunnel`. PSD rimane un solo Workspace Database: usa la connessione diretta sul
server e l'endpoint REST in locale tramite una Database Binding specifica dell'installazione.
Questo supporto non abilita automaticamente `ssh_tunnel` nel runtime NL→SQL.
19. Una configurazione può essere salvata prima di una connessione riuscita. Il test separato
produce uno stato `untested`, `reachable` o `failed`; attivazione e introspezione richiedono uno
stato raggiungibile.
20. Il CRUD e il test di connessione richiedono `database.manage`; inserimento e sostituzione dei
segreti continuano a richiedere `workspace.secrets.manage`.
21. Il Workspace Database e il modo di raggiungerlo sono entità distinte. Ogni catalogo di
installazione conserva una sola Database Binding attiva per workspace: PSD usa `rest_api` in
locale e `postgres_direct` sul server senza duplicare il Workspace Database.
22. Nel modello finale il Metadata Catalog è autorevole per engine, `database_name`, schema,
capacità e binding. Lo YAML resta autorevole per identità e contenuti del workspace; i campi
DWH correnti saranno importati, confrontati e rimossi soltanto durante un cutover esplicito.
23. La lista master è l'unione fra workspace YAML e record del catalogo: mostra workspace
`unconfigured`, database configurati e record `orphaned`.
24. Ogni introspezione registra le capability disponibili. Una capability `unavailable` non viene
rappresentata come una collezione osservata ma vuota; REST può completare con successo anche
quando indici o enum non sono supportati.
25. Il Metadata Catalog non introduce snapshot, draft o pubblicazioni. Configurazione e contenuti
semantici, inclusi quelli futuri generati dall'AI, sono normali campi modificabili; la struttura
osservata cambia soltanto con una sincronizzazione esplicita.
26. Il normale Delete elimina realmente il Workspace Database, la Database Binding e i relativi
record catalogo e segreti. Non modifica il DWH esterno né il repository YAML; il workspace torna
visibile nella lista master come `unconfigured`.
27. La prima versione gestisce un solo schema obbligatorio per Workspace Database, identificato
dalla coppia `database_name + schema`; per PSD la coppia è `postgres + datawarehouse`.
28. I record mantengono soltanto `created_at`, `updated_at` e un contatore `version` per optimistic
concurrency. Non esistono storico delle revisioni, rollback o audit applicativo delle modifiche.
29. `workspace_databases` conserva soltanto UUID, `workspace_id` unique, engine, `database_name`,
schema, timestamp e version. Il nome visualizzato appartiene al workspace YAML.
30. Ogni Workspace Database ha al massimo una riga `database_bindings`. Una singola tabella usa
check constraint dipendenti da `transport` per i campi direct, REST e SSH; non esiste un flag
`active`, perché ciascuna installazione conserva una sola binding.
31. `rest_api` configura il Thoth REST Connector tipizzato: base URL, autenticazione e TLS sono dati
della binding, mentre path RPC e shape delle risposte appartengono al contratto applicativo e non
sono liberamente configurabili.
32. Il test connessione usa soltanto una configurazione già salvata ed è associato alla sua
`version`. Ogni modifica della binding o dei segreti invalida il risultato precedente e riporta
lo stato a `untested`.
33. Password, API key e chiavi sono write-only: l'API espone soltanto `configured`, un campo vuoto
conserva il valore esistente e la sostituzione è un'azione esplicita. Delete rimuove anche i
segreti associati.
34. La pagina usa AG Grid come master e un form React come detail, con sezioni Database, Connection
e TLS/SSH condizionali. Non esiste un'azione globale `Add database`: ogni riga `unconfigured`
offre `Configure catalog`, apre il form già vincolato a quello specifico workspace YAML e crea il
record soltanto al Save; `workspace_id` non è selezionabile né modificabile.
35. La grid mostra separatamente revisione/Evidence del workspace, binding runtime NL→SQL e
configurazione del Metadata Catalog, oltre a database, schema, endpoint e ultimo aggiornamento.
Su schermi piccoli il dettaglio occupa il pannello completo. Il cambio riga con modifiche non
salvate e Delete richiedono conferma, senza conferma testuale tipizzata.
36. La Database Binding conserva `connection_status`, `tested_version`, `last_tested_at`, un codice
errore e un messaggio breve sanificato. Non conserva stack trace, DSN, credenziali o output grezzo
del driver.
37. Le API vivono sotto `/api/catalog`: list/create di `/databases`, get/patch/delete di
`/databases/:id`, sostituzione dei segreti sotto `/databases/:id/secrets`, test connessione sotto
`/databases/:id/test` e list/patch/sync delle tabelle sotto `/databases/:id/tables`.
38. `GET /api/catalog/databases` restituisce l'intera master list unificata; AG Grid Community applica
client-side ricerca, filtri e ordinamento. La prima versione non introduce paginazione server o
funzionalità AG Grid Enterprise.
39. Il Metadata Catalog vive nello stesso processo Fastify come modulo isolato con repository,
service, route, diagnostica e readiness proprie. L'indisponibilità del catalogo non modifica
sessioni, SSE o health del core e non giustifica ancora un microservizio separato.
40. Il backend mantiene `pg@8.22.0` e aggiunge `kysely@0.29.5` per query e transazioni tipizzate. Le
migrazioni Kysely sono timestampate, compilate con il backend ed eseguite da un comando
`catalog:migrate` separato; l'applicazione non migra automaticamente il database all'avvio.
41. Lo stack aggiunge un servizio interno `catalog-db` con volume persistente, ruolo runtime DML,
ruolo migrator DDL e job one-shot `catalog-migrate`. Un catalogo indisponibile produce 503 sulle
sole route catalogo.
42. La prima vertical slice è amministrativa: scrive il catalogo ma non cambia ancora il runtime di
sessioni e workflow, che continua a usare YAML e binding correnti fino al cutover esplicito.
43. `Configure` precompila senza salvare engine, database e schema dal descriptor e i dati non
sensibili dalla binding effettiva. L'amministratore verifica, inserisce i segreti e salva; non
esiste importazione silenziosa.
44. Unit e route test usano un repository fake; una suite PostgreSQL Testcontainers separata verifica
migrazioni, constraint, transazioni, optimistic concurrency e cascade. SQLite ed emulatori non
sono sostituti ammessi per questi test.
45. La navigazione delle entità catalogo è gerarchica e senza scorciatoie globali: `Databases →
Database → Overview | Tables → Table`. Non esistono una voce globale Tables, un filtro globale
Database o una preselezione implicita; Columns continuerà sotto Table e Relationships sotto
Database.
46. Una Catalog Table conserva nome fisico, `source_comment`, descrizione curata nullable,
`generated_description` nullable per lo step AI futuro, version e timestamp. La UI mostra come
tre campi indipendenti senza fallback visivo: source comment read-only, generated description
modificabile e description modificabile. I valori null restano celle e controlli vuoti.
47. Le Catalog Table non possono essere aggiunte o rinominate manualmente. Un amministratore può
però ripulire esplicitamente le proiezioni nel Metadata Catalog senza modificare il database
esterno; `Sync tables` legge le tabelle PostgreSQL ordinarie e partizionate dello schema scelto,
mentre viste e materialized view sono escluse.
48. La sincronizzazione è esplicita. La scansione avviene fuori dalla transazione del catalogo; il
diff viene applicato atomicamente soltanto se la version del Workspace Database è ancora quella
sottoposta a scansione. Una scansione fallita non modifica il catalogo.
49. Tabelle nuove vengono create, i commenti sorgente vengono aggiornati e quelle non più osservate
vengono eliminate definitivamente. La rimozione di tabelle, colonne o relazioni richiede la
conferma dell'esatto piano distruttivo; se il secondo scan produce una fotografia differente,
l'applicazione richiede una nuova conferma.
50. Un rename fisico è intenzionalmente delete più create e perde i metadati curati. Le colonne e
relazioni dipendenti vengono eliminate in cascade insieme alla Catalog Table.
51. L'introspezione vive nel modulo catalogo Fastify dietro un adapter. PostgreSQL diretto e tunnel
SSH usano il catalogo `pg_catalog`; REST preferisce il contratto tipizzato
`POST /rpc/schema_snapshot` e, quando quell'RPC non è esposto, usa come fallback compatibile una
singola query read-only tramite `POST /rpc/run_query`. Entrambi i percorsi devono produrre la
stessa fotografia v1 stretta descritta in `docs/contracts/catalog-schema-snapshot.md`.
52. Test connessione e sincronizzazione sono serializzati per Workspace Database, hanno timeout e
richiedono che la binding nella version corrente abbia un test `reachable` prima di qualsiasi
Catalog Sync Run. La scansione asincrona ha un timeout separato, di default dieci minuti.
53. Il tunnel SSH usa OpenSSH in modalità stdio `-W`, chiave privata e passphrase opzionale dal
secret store, `known_hosts` obbligatorio, `StrictHostKeyChecking=yes`, agent e configurazione
globale disabilitati. Non è ammesso TOFU. TLS PostgreSQL con CA e server name resta verificato
anche attraverso il tunnel.
54. In questo slice `ssh_tunnel` è una binding supportata da Database management per Test connection
e Schema Sync. Il renderer e il runtime delle sessioni NL→SQL restano fuori scope e continuano a
rifiutarla finché non verrà deciso il relativo cutover.
55. I menu di azione a livello Workspace Database espongono separatamente `Synchronize tables`,
`Synchronize relationships` e `Synchronize all`. Su una selezione di
database lo scope scelto viene avviato per ogni database idoneo; non viene sostituito
implicitamente con una sincronizzazione completa.
56. Lo scope Columns è disponibile dalla grid Tables e limita la riconciliazione alle tabelle
selezionate; la pagina Columns non espone azioni di sincronizzazione. La grid Tables espone
`Synchronize columns` sulle tabelle selezionate.
## Correzione del modello mentale corrente
`schema/annotations.yaml` non contiene l'intero schema del database.
- `physical.yaml` è un artefatto derivato dall'introspezione. Contiene database, schema, timestamp,
tabelle, colonne, tipi, nullability, default, primary key, commenti sorgente, esempi, foreign key
fisiche e indici.
- `annotations.yaml` contiene metadati curati: descrizioni e concetti delle tabelle; descrizioni,
sinonimi, concetti, evidence, note e override `eligible` delle colonne; foreign key logiche.
- Il rendering M-Schema fonde questi due input. Le annotations prevalgono sui commenti sorgente e
le relazioni logiche vengono unite alle foreign key fisiche.
La sostituzione del solo file annotations non elimina automaticamente l'introspezione fisica. Il
nuovo catalogo dovrà conservare una distinzione esplicita fra fatti osservati nel database e
contenuto semantico modificabile.
## Architettura ThothII rilevante
### Autorità e revisionamento attuali
Il repository dei workspace contiene:
```text
thoth-workspaces.yaml
<workspace-id>/workspace.yaml
<workspace-id>/schema/annotations.yaml
<workspace-id>/evidence/**
```
Il backend legge descriptor e annotations allo stesso commit Git. Durante l'attivazione valida il
blob, lo copia atomicamente nello snapshot immutabile della revisione e registra commit, blob ID e
digest. Le nuove sessioni vengono legate a quella revisione; resume e SQL salvato riaprono lo stesso
snapshot.
Punti principali:
- `backend/src/workspaces/schema.ts`: descriptor v3 e singolo `dwh.database`/`dwh.schema`;
- `backend/src/workspaces/git-repository.ts`: lettura sicura del blob annotations al commit;
- `backend/src/workspaces/registry.ts`: validazione e attivazione atomica;
- `backend/src/workspaces/annotations-sync.ts`: materializzazione revision-qualified;
- `backend/src/workspaces/runtime-config-lease.ts`: binding dello snapshot al runtime;
- `harness/tht/mschema/models.py`: contratti `PhysicalSchema` e `Annotations`;
- `harness/tht/mschema/render.py`: fusione fisico/semantico;
- `harness/tht/cli/vector_cmd.py`: indicizzazione schema in Qdrant.
### Consumatori da preservare al cutover futuro
Le annotations incidono oggi su:
- override `eligible` prima del campionamento LSH;
- suggerimento, controllo e accettazione delle foreign key logiche;
- descrizioni, concetti e sinonimi dei record schema in Qdrant;
- retrieval delle tabelle e colonne candidate;
- rendering M-Schema usato dal gate F4 e dalla generazione SQL;
- digest della revisione accettata durante il preprocessing.
Il futuro cutover non potrà limitarsi a rimuovere il file: dovrà fornire al core lo stesso contenuto
effettivo, con un'identità coerente e test di equivalenza. Poiché non occorre preservare le sessioni
di test esistenti, non serve progettare compatibilità con i vecchi manifest, ma resta necessario
evitare letture parziali o semanticamente incoerenti.
## Inventario ThothAI
### Modelli legacy
I modelli sono definiti in `Thoth/ThothAI/backend/thoth_core/models.py`.
#### `SqlDb`
Campi di connessione osservati:
- `name`;
- `db_host`, `db_port`;
- `db_type`;
- `db_name`, `schema`;
- `user_name`, `password`;
- `db_mode`;
- configurazione SSH e Informix opzionale.
Il modello contiene anche scope, JSON dello scope, ERD, direttive, campi GDPR, collegamento a
`VectorDb` e numerosi campi di stato/task/log per lavori AI asincroni.
I tipi legacy dichiarati sono Informix, MariaDB, MySQL, Oracle, PostgreSQL, SQL Server e SQLite.
Questo elenco non costituisce automaticamente un requisito per ThothII: il core corrente supporta
PostgreSQL e l'estensione ad altri dialetti dovrà essere decisa separatamente.
#### `SqlTable`
- `name`;
- `description`;
- `generated_comment`;
- foreign key obbligatoria a `SqlDb`, con cancellazione cascade.
#### `SqlColumn`
- `original_column_name` e alias `column_name`;
- `data_format` normalizzato;
- `column_description`;
- `generated_comment`;
- `value_description`;
- stringhe denormalizzate `pk_field` e `fk_field`;
- foreign key obbligatoria a `SqlTable`, con cancellazione cascade.
#### `Relationship`
Contiene quattro foreign key obbligatorie:
- `source_table` e `source_column`;
- `target_table` e `target_column`.
Il form admin verifica che le tabelle appartengano allo stesso database e che ogni colonna
appartenga alla tabella selezionata. Il database non impone però gli stessi check.
#### `Workspace`
ThothAI usa `Workspace.sql_db` come foreign key nullable verso `SqlDb`: un workspace seleziona un
solo DB, mentre lo stesso DB può essere riusato da più workspace. ThothII adotterà invece una
relazione uno-a-uno: `workspace_id` deve essere unico nel catalogo.
### Lacune dei constraint legacy
Non risultano constraint database-level per:
- unicità del nome database nel workspace;
- unicità `(database, table name)`;
- unicità `(table, column name)`;
- unicità degli estremi di una relationship;
- appartenenza degli estremi della relationship allo stesso database;
- corrispondenza fra colonna e tabella dichiarata.
ThothII deve applicare queste invarianti sia nel database interno sia nel servizio applicativo. La
sola validazione del form non è sufficiente perché API, import e job la possono aggirare.
### Django Admin e UX da replicare concettualmente
ThothAI espone il CRUD tramite il Django Admin standard, registrato da
`backend/thoth_core/admin.py` e pubblicato su `/admin/`.
Capacità utili:
- lista database con ricerca per nome, host, tipo, database e schema;
- fieldset separati per identità, connessione, autenticazione, SSH e stato;
- lista tabelle filtrabile per database;
- lista colonne filtrabile in cascata per database e tabella;
- lista relazioni con estremi leggibili e filtri per database e tabelle;
- form relazione con dropdown dipendenti database → tabella → colonna;
- validazione degli estremi prima del salvataggio;
- azioni separate per test connessione, introspezione, import/export e generazione AI;
- azioni bulk sulle righe selezionate.
ThothII deve replicare i contratti di interazione e validazione, non il rendering server-side o i
template Django.
### Introspezione legacy
`Thoth/ThothAI/backend/thoth_core/dbmanagement.py` usa `thoth-dbmanager` per:
1. costruire l'adapter del dialetto;
2. acquisire tabelle;
3. acquisire e normalizzare colonne e tipi;
4. acquisire relazioni;
5. creare le eventuali colonne mancanti necessarie alle relazioni;
6. aggiornare i campi PK/FK denormalizzati.
Il comportamento è principalmente additivo: usa `get_or_create` o controlli `exists`, aggiorna
alcuni commenti, ma non riconcilia in modo completo rename, rimozioni o drift. Non va copiato così
com'è. Il processo ThothII implementato distingue scansione, differenze osservate e applicazione
della nuova snapshot.
### Generazione AI legacy
ThothAI dispone di azioni e workflow per:
- commenti delle tabelle;
- commenti delle colonne;
- scope del database;
- ERD Mermaid;
- documentazione del database;
- analisi GDPR.
Per il requisito attuale sono direttamente rilevanti descrizioni di tabelle e colonne, scope e
metadati semantici. ERD, documentazione aggregata e GDPR sono estensioni future, non prerequisiti
del CRUD iniziale.
La separazione `description`/`generated_comment` del legacy non offre versioning o approvazione
robusti. Nei passi successivi andrà deciso se l'output AI è una proposta revisionabile o diventa
immediatamente il valore editabile corrente.
### Import ed export legacy
ThothAI offre:
- CSV di database, tabelle, colonne e relazioni;
- export di struttura per workspace;
- import mediante `import_db_structure`;
- script SQL dei commenti per più dialetti;
- aggiornamento delle descrizioni colonna da CSV.
Il futuro import PSD dovrà leggere il contratto YAML corrente e convertirlo su chiavi naturali,
non riutilizzare gli ID numerici Django. Deve essere idempotente e produrre un report di elementi
creati, aggiornati, ignorati o non risolti.
## Comandi osservati in ThothAI
### Backend locale
Eseguiti da `Thoth/ThothAI/backend`:
```sh
uv sync
uv run python manage.py migrate
uv run python manage.py createsuperuser
uv run python manage.py runserver 8200
uv run pytest
```
Import catalogo legacy:
```sh
uv run python manage.py import_db_structure --source local
uv run python manage.py load_defaults --only-level 4 --source local
```
Test mirati rilevanti:
```sh
uv run pytest tests/test_relational_database_operations.py -v
uv run pytest tests/test_ssh_tunnel_configuration.py -v
```
### Stack Docker legacy
ThothAI dichiara `postgres:16-alpine` nel profilo `internal-db`, con volume persistente e
healthcheck `pg_isready`.
```sh
docker compose --profile internal-db up --build
```
Il wrapper legacy abilita lo stesso profilo quando `POSTGRES_INTERNAL=true`:
```sh
POSTGRES_INTERNAL=true ./docker-up.sh
```
Questi comandi documentano il riferimento osservato; non sono comandi di installazione per
ThothII.
## Cosa copiare in ThothII
### Parità necessaria
- gerarchia Workspace Database → Table → Column;
- relazione strutturale fra colonne sorgente e destinazione;
- navigazione e filtri dipendenti workspace/database/tabella;
- test di connessione separato dal salvataggio;
- introspezione esplicita e ripetibile;
- descrizioni generate dall'AI ma modificabili dall'utente;
- validazione cross-entity delle relazioni;
- azioni di import/export senza segreti;
- stato leggibile dei job lunghi;
- PostgreSQL interno persistente con migrazioni esplicite;
- test di CRUD, cardinalità, cascade/restrict, isolamento per workspace e idempotenza.
### Parità semantica con `annotations.yaml`
Il modello futuro deve poter rappresentare almeno:
- descrizione, concetti e note per tabella;
- descrizione, sinonimi, concetti, evidence, note ed `eligible` per colonna;
- foreign key logiche;
- distinzione fra commento fisico osservato e descrizione curata;
- provenienza del contenuto importato o generato.
L'eventuale esclusione di uno di questi campi deve essere una decisione esplicita perché cambia
rendering, retrieval, LSH o SQL generation.
### Vincoli minimi da progettare
- `workspace_id` obbligatorio e unico sul Workspace Database, con esistenza validata contro il
catalogo YAML dal servizio applicativo;
- nome tabella unico nel database e schema appropriato;
- nome colonna unico nella tabella;
- relationship unica secondo il modello, anche per chiavi composite;
- estremi della relationship nello stesso Workspace Database;
- appartenenza certa della colonna alla tabella;
- mutazioni aggregate transazionali;
- gestione esplicita di concorrenza fra CRUD e introspezione.
## Cosa non copiare
- Django, Django Admin, Django ORM, DRF, template admin e frontend Next;
- modello Workspace legacy e condivisione dello stesso DB fra più workspace;
- password o passphrase come normali campi testuali;
- password incluse in CSV o export completi;
- token SSO inseriti nella query string;
- migrazioni generate automaticamente all'avvio;
- validazioni presenti soltanto nel form;
- `pk_field` e `fk_field` testuali come fonte di verità;
- duplicazione di tabella e colonna negli estremi senza constraint coerenti;
- introspezione additiva che non segnala rename, delete o drift;
- azioni admin che possono mostrare successo dopo output AI non valido;
- dipendenza del workflow core dalla disponibilità della UI o del PostgreSQL amministrativo.
## Aspetti di sicurezza da non ereditare
L'export legacy della struttura include username e password in chiaro. Il modello conserva inoltre
password, passphrase SSH e altri segreti in `CharField`; non è stata trovata cifratura applicativa,
nonostante un testo admin affermi il contrario.
ThothII distingue i metadati di connessione dai riferimenti al secret store cifrato. In ogni caso:
- nessun endpoint o export deve restituire segreti;
- log ed errori devono sanificare DSN e credenziali;
- le credenziali di migrazione non devono essere disponibili al runtime CRUD;
- il catalogo non deve riusare credenziali del DWH, delle sessioni o di Qdrant;
- test connessione e introspezione devono usare timeout e privilegi read-only.
La binding REST corrente richiede una verifica prima del cutover: il renderer emette
`ssl_ca_file`, mentre il modello Python espone `ssl_ca`; il percorso della CA privata potrebbe quindi
non essere consumato. PSD richiede TLS con CA privata in locale, perciò questo disallineamento deve
essere corretto e coperto da un test end-to-end prima di affidare il profilo REST al catalogo.
## Percorso incrementale
### Step 1: accesso alla superficie vuota
Implementato in questo worktree:
- pulsante `Database management` nella sidebar destra;
- visibilità legata a `workspace.manage`;
- superficie centrale React separata e vuota;
- nessun router, endpoint, fetch o stato catalogo;
- sessione e SSE conservati in background;
- ritorno al core tramite creazione, apertura o resume di una sessione;
- test frontend dedicati.
Comandi di verifica:
```sh
cd frontend
npx vitest run src/shell/AppShell.database-management.test.tsx
npx vitest run src/shell/AppShell.new-session.test.tsx \
src/shell/AppShell.session-target.test.tsx \
src/shell/AppShell.session-mgmt.test.tsx
npx tsc -b
```
### Step 2: contratto di dominio e schema relazionale
Progettazione della vertical slice completata: Workspace Database, Database Binding, singolo schema,
riferimenti al secret store, optimistic concurrency e capability per trasporto hanno contratti
espliciti. Configurazione e contenuti semantici restano mutabili; la struttura fisica osservata è
sincronizzata e non modificabile manualmente.
### Step 3: PostgreSQL interno e migrazioni
PostgreSQL interno con volume e ruoli runtime/migrator separati. Il modulo catalogo usa Kysely sopra
il driver `pg`; le migrazioni compilate vengono applicate soltanto dal comando `catalog:migrate` e
mai allo startup Fastify. Health, readiness e diagnostica restano dedicate; l'indisponibilità del
catalogo non cambia `core /health` e non interrompe una sessione.
### Step 4: API CRUD
Contratti HTTP, autorizzazione, paginazione, filtri, errori, optimistic concurrency e transazioni.
Gli endpoint dovranno vivere sotto un namespace catalogo e non riutilizzare le route sessione.
### Step 5: UI CRUD
Workspace Database, Catalog Table, Catalog Column e Catalog Relationship sono implementati con
React/Vite e il design system ThothII.
La navigazione è gerarchica e locale al database (`Overview | Tables`), senza menu o filtri globali
per tipo di entità. La grid delle tabelle non offre Add o cancellazione della singola configurazione;
le selezioni espongono invece la pulizia esplicita dei metadati. Il dettaglio full-width mantiene
immutabili i fatti fisici e consente di modificare separatamente Description e Generated
Description. Colonne e relazioni seguono la stessa gerarchia: Columns appartiene al dettaglio
della tabella, Relationships al database. I valori descrittivi null sono mostrati come celle e
campi vuoti, senza fallback visivi o placeholder `Not set` che nascondano quale sorgente è
effettivamente valorizzata.
Le griglie che dispongono di azioni massive usano checkbox e una toolbar contestuale con conteggio,
menu `Actions` e cancellazione della selezione. La selezione identifica ID espliciti, può essere
accumulata attraverso i filtri e viene azzerata dopo successo, nuova sincronizzazione o uscita
dalla pagina; un'azione è all-or-nothing se un elemento non è idoneo. I menu a livello database
espongono gli scope fisici come azioni distinte: `Synchronize tables`, `Synchronize relationships`
e `Synchronize all`. La grid Tables espone invece `Synchronize columns` per le tabelle selezionate;
la pagina Columns non espone sincronizzazione. Le selezioni database aggiungono `Delete all tables` e
`Delete all relationships`; le selezioni tabelle aggiungono `Delete all columns` e `Delete all
relationships`. Queste operazioni sono atomiche, richiedono conferma e non modificano database
esterno, binding, configurazione o segreti. Test connection resta un'azione distinta; griglie senza
azioni non mostrano controlli di selezione inerti.
### Step 6: introspezione
Catalog Table, Catalog Column e Catalog Relationship sono implementate per PostgreSQL diretto,
Thoth REST Connector e tunnel SSH. La scansione read-only è separata dalla transazione; una
riconciliazione atomica crea, aggiorna i commenti sorgente ed elimina, dopo conferma, i fatti fisici
assenti senza rendere modificabile manualmente la struttura osservata. Gli scope autorevoli sono
Tables per database e Physical Relationships per database. Per Columns, `tableIds` vuoto include
tutte le Catalog Table correnti, mentre una lista di ID limita lo scope al sottoinsieme esplicito;
`Synchronize all` osserva tutti e tre gli scope in un unico snapshot e li riconcilia insieme. Tutti
gli scope sono eseguiti come Catalog Sync Run durevoli in background, non attraverso implementazioni
sincrone e asincrone separate. Un run che prevede cancellazioni conserva il diff, attende una
conferma esplicita e verifica nuovamente lo snapshot prima dell'applicazione; se la sorgente è
cambiata, invalida la conferma. Ogni applicazione è atomica e fail-closed: errori, timeout o
capability non disponibili non producono aggiornamenti parziali.
PK e FK devono essere visibili sulle Catalog Column senza duplicare le stringhe denormalizzate di
ThothAI. La posizione nella primary key è un fatto osservato della colonna; membership e conteggio
FK sono proiezioni derivate dalle Catalog Relationship e dalle loro coppie ordinate, aggiornate
nella stessa transazione di riconciliazione.
Ogni scope registra la versione della Database Binding osservata e l'istante dell'ultima
sincronizzazione. Una modifica della binding conserva il catalogo precedente ma lo marca stale;
solo un `Synchronize all` riuscito rende nuovamente corrente l'intero schema.
### Step 7: generazione AI dei metadati
Generated Description è una proposta distinta e modificabile: un revisore può correggerla prima
di consolidarla esplicitamente come Description. Lo slice AI dovrà decidere e implementare anche
alias semantici, descrizioni dei valori, sinonimi e concetti per tabelle e colonne, oltre alla
gestione esplicita di errori e output non validi. La generazione AI e l'azione di consolidamento non
appartengono allo slice di introspezione dello schema.
### Step 8: migrazione PSD
Import idempotente delle annotations PSD, riconciliazione contro la struttura introspezionata,
report degli orfani e confronto semantico con il rendering corrente. Gli altri workspace non
ricevono import legacy.
### Step 9: sostituzione dell'input core
Rimuovere la dipendenza da `annotations.yaml` soltanto dopo avere un contratto equivalente,
test di rendering/search/Qdrant e una policy di disponibilità. Le sessioni di test esistenti
possono essere eliminate, ma le nuove sessioni non devono osservare aggiornamenti parziali.
Questo cutover è esplicitamente rinviato fino al completamento del database dei metadati. Il primo
gate successivo obbligatorio sarà valutare l'integrazione del Catalog Schema Snapshot con il
workflow core e lo schema-linking corrente; il rinvio non autorizza a dimenticare o assorbire
implicitamente il lavoro in altri slice.
### Step 10: operazioni e accettazione
Backup/restore reale, diagnostica, metriche, permessi definitivi, hardening degli export e
test di failure isolation fra catalogo e workflow. I Catalog Sync Run hanno un solo job attivo per
Workspace Database, sono concorrenti fra database diversi e usano un lock persistente. Un pannello
operativo non modale rimane visibile durante la navigazione del database, mostra fasi, contatori,
tempo trascorso e log sanitizzato via SSE con polling di fallback, e offre Confirm, Cancel e Retry
quando consentiti. Un restart marca `interrupted` i run rimasti attivi; il retry crea un nuovo run.
Le modifiche ai metadati restano consentite durante la scansione e sono preservate dall'applicazione.
Il worker gira inizialmente nello stesso servizio Fastify ma dietro un'interfaccia estraibile, con
coda, lease e heartbeat persistiti nel catalog-db. I riepiloghi dei run non scadono; gli eventi
dettagliati sono conservati per 30 giorni, mentre snapshot e diff completi vengono eliminati dopo
la conclusione lasciando conteggi, decisioni e una sintesi sanitizzata dell'esito.
## Verifiche del core da conservare per il cutover
Comandi attuali rilevanti:
```sh
tht --installation <absolute>/thothii-installation.yaml workspace preprocess dwh \
--workspace <id> --json
tht --installation <absolute>/thothii-installation.yaml workspace schema suggest-fks \
--workspace <id> --json
tht --installation <absolute>/thothii-installation.yaml workspace schema check \
--workspace <id> --json
tht --installation <absolute>/thothii-installation.yaml workspace schema accept \
--workspace <id> --run <run-id> --yes --json
tht --installation <absolute>/thothii-installation.yaml workspace index-schema \
--workspace <id> --json
```
Suite che documentano il comportamento da preservare:
```sh
cd backend
npx vitest run test/workspaces-git-annotations.test.ts \
test/registry-annotations.test.ts \
test/annotations-sync.test.ts \
test/workspace-runtime-config-lease.test.ts \
test/workspace-preprocessing-service.test.ts
npx tsc --noEmit -p .
cd ../harness
.venv/bin/pytest -q \
tests/test_annotations_root.py \
tests/test_schema_fk_annotations.py \
tests/test_mschema_render.py \
tests/test_qdrant_cli_commands.py
```
Questi test non implicano che la futura implementazione debba continuare a usare file YAML.
Definiscono gli effetti semantici e le guardie da mantenere o sostituire consapevolmente.
## Decisioni rinviate
Le seguenti scelte non appartengono allo step 1:
- lifecycle dei riferimenti ai segreti durante sostituzione e cancellazione;
- criteri per aggiungere dialetti successivi a PostgreSQL;
- criteri per un'eventuale estensione futura a più schemi per database;
- lifecycle e gestione amministrativa delle future Logical Relationship;
- alias semantici, descrizioni dei valori, sinonimi e concetti prodotti o assistiti dall'AI;
- formato e momento del cutover dal file al database interno;
- permission definitiva separata da `workspace.manage`.
Ognuna sarà affrontata nel relativo step, senza anticipare scelte tecnologiche nel presente
documento.
@@ -1,251 +0,0 @@
# AI-generated descriptions for Catalog Tables and Catalog Columns
## Problem Statement
ThothII already stores a Generated Description separately from the curated Description for Catalog
Tables and Catalog Columns, but administrators cannot populate it with AI. ThothAI provides the
useful core workflow—generate table and column comments from schema context and small real-data
samples—but its execution, configuration, and interaction model cannot be copied directly into
ThothII.
Administrators need an asynchronous workflow integrated into Database Management. They must be
able to choose an installation-approved model, generate descriptions for selected or missing
targets, observe understandable progress, stop or recover a stuck operation, review generated
text, and explicitly consolidate it. The solution must retain ThothAI's practical simplicity and
must not introduce a general job platform, model gateway, distributed scheduler, or competing
user-facing CLI.
## Solution
Add Description Generation to Database Management as one installation-wide, sequential background
run owned by the Fastify backend. The browser starts a run and remains responsive while the backend
processes bounded requests one at a time. Each completion is delegated to a short-lived internal
Python helper using LiteLLM. Models, their default, and any API-key secret references are declared in
application setup YAML and are independent of both workspaces and Pi configuration.
Each valid result is written immediately to the target's Generated Description. A minimal run row
and ordered text events provide status, counters, history, and a live log. A stopped or crashed run
is not resumed automatically; completed results remain in place and Generate Missing supplies the
simple recovery path. An Unlock action marks a stale recorded run interrupted only when no helper
or backend generation loop is alive.
Prompts use catalog context and, when available, no more than five real rows and five representative
non-null examples. Samples are transient and never logged or persisted. A valid inability to infer
a description produces a standard application-localized value such as `Non generabile`; provider,
timeout, and response-validation failures remain technical errors.
Generated text remains separate from Description until an administrator uses the existing
checkbox selection and Actions control to consolidate it. Consolidation retains Generated
Description and never writes comments to the external Workspace Database.
## User Stories
1. As an installation operator, I want to declare the models allowed for metadata generation in setup YAML, so that model availability is controlled centrally.
2. As an installation operator, I want to declare one default metadata-generation model, so that administrators begin with a safe operational choice.
3. As an installation operator, I want each model to reference its own API-key secret, so that credentials are not stored in workspaces or browser-visible settings.
4. As an installation operator, I want metadata-generation models to remain independent of Pi models, so that changing this workflow cannot disrupt the core NL-to-SQL experience.
5. As an installation operator, I want invalid model setup to fail validation clearly, so that the application does not start with ambiguous provider behavior.
6. As a Catalog Administrator, I want generation controls to explain when no model is configured, so that I know why the action is unavailable.
7. As a Catalog Administrator, I want to select an approved model from a selector initialized to the setup default, so that I control which model performs the work.
8. As a Catalog Administrator, I want to generate descriptions for selected Catalog Tables, so that I can work on a focused part of the catalog.
9. As a Catalog Administrator, I want to generate descriptions for selected Catalog Columns, so that I can work on individual fields without regenerating a whole table.
10. As a Catalog Administrator, I want to generate all eligible descriptions for a Workspace Database, so that I can initialize a catalog in one operation.
11. As a Catalog Administrator, I want to generate only missing descriptions, so that I can continue interrupted work without replacing completed proposals.
12. As a Catalog Administrator, I want a full run to process Catalog Columns before their Catalog Tables, so that table descriptions can benefit from column descriptions.
13. As a Catalog Administrator, I want generation to run asynchronously after I start it, so that the browser remains usable and progress is not tied to one HTTP request.
14. As a Catalog Administrator, I want only one Description Generation Run active in the installation, so that provider traffic and operational behavior remain predictable.
15. As a Catalog Administrator, I want a second start attempt to return a clear conflict, so that I cannot accidentally overlap generation runs.
16. As a Catalog Administrator, I want synchronization, cleanup, consolidation, and edits for the target Workspace Database blocked during generation, so that the simple sequential run sees stable catalog state.
17. As a Catalog Administrator, I want to see the run's model, scope, status, counters, and timestamps, so that I understand what is happening.
18. As a Catalog Administrator, I want a chronological text log, so that I can follow completed targets and diagnose errors.
19. As a Catalog Administrator, I want live log updates with a polling fallback, so that temporary SSE problems do not hide run progress.
20. As a Catalog Administrator, I want completed and interrupted runs to remain inspectable, so that I can understand prior activity.
21. As a Catalog Administrator, I want to stop an active run, so that I can halt an incorrect or unexpectedly costly operation.
22. As a Catalog Administrator, I want stopping a run to terminate its current model helper and prevent later targets from starting, so that stop has prompt operational effect.
23. As a Catalog Administrator, I want valid results completed before a stop or failure to remain saved, so that useful work is not discarded.
24. As a Catalog Administrator, I want a run left active by a backend restart to become interrupted, so that the UI does not claim nonexistent work is still running.
25. As a Catalog Administrator, I want to unlock a stale active run when no generation process is alive, so that an erroneous recorded lock cannot block future work.
26. As a Catalog Administrator, I want Unlock rejected while a live generation process exists, so that recovery cannot create an overlapping run.
27. As a Catalog Administrator, I want Generate Missing to continue after interruption, so that recovery does not require a special resume mechanism.
28. As a Catalog Administrator, I want one retry for a transient model failure, so that a brief provider fault does not immediately lose a batch.
29. As a Catalog Administrator, I want the run to fail after three consecutive technical failures, so that a broken provider does not generate an unbounded stream of attempts.
30. As a Catalog Administrator, I want a successful request to reset the consecutive-failure count, so that isolated errors do not prematurely stop a useful run.
31. As a Catalog Administrator, I want a completed-with-errors result when isolated batches fail but the run reaches its end, so that partial problems remain visible.
32. As a Catalog Administrator, I want no automatic fallback to a different model, so that the selected model remains truthful and predictable.
33. As a Catalog Administrator, I want malformed or ambiguous model output rejected without writing it, so that descriptions cannot be assigned to the wrong target.
34. As a Catalog Administrator, I want an inability to infer a description represented by standard localized text, so that every valid outcome is understandable in the workspace language.
35. As a Catalog Administrator, I want technical failures kept distinct from non-generatable outcomes, so that provider problems are not mistaken for catalog knowledge.
36. As a Catalog Administrator, I want generated prose written in the workspace language, so that it matches the catalog's intended audience.
37. As a Catalog Administrator, I want generated text stored separately from curated Description, so that AI output remains a reviewable proposal.
38. As a Catalog Administrator, I want to edit a Generated Description manually, so that I can improve a proposal before consolidation.
39. As a Catalog Administrator, I want to select one or more tables or columns and run “Move generated description to Description” from the existing Actions control, so that review remains integrated into the current grids.
40. As a Catalog Administrator, I want consolidation to retain the Generated Description, so that I can still see the proposal from which the curated text was copied.
41. As a Catalog Administrator, I want selected records without a Generated Description skipped and reported, so that the bulk action does not erase curated text.
42. As a Catalog Administrator, I want consolidation and generation to modify only the Metadata Catalog, so that no external database comment is changed.
43. As a Catalog Administrator, I want prompts to use schema facts and existing catalog text, so that generated descriptions are grounded in available metadata.
44. As a Catalog Administrator, I want prompts to use at most five real source rows and five representative values when available, so that the model has useful examples without unbounded disclosure.
45. As a Catalog Administrator, I want to be warned that real source samples are sent to the selected provider, so that I can make an informed disclosure decision.
46. As a Catalog Administrator, I want sampled rows and values excluded from persistence and logs, so that operational history does not become a secondary data store.
47. As a security operator, I want API keys, prompts, samples, and complete provider payloads redacted from logs, so that diagnostics do not leak secrets or source data.
48. As a support operator, I want concise per-target and per-batch event messages, so that failures can be diagnosed without provider-specific internals.
49. As an authorized administrator, I want all generation, cancellation, unlock, and consolidation actions protected by database-management permission, so that ordinary users cannot mutate catalog metadata.
50. As an unauthorized user, I want generation controls hidden or disabled and API calls rejected, so that frontend visibility is not treated as authorization.
51. As an operator, I want setup changes to take effect after an application restart, so that configuration lifecycle remains simple and explicit.
52. As a product owner, I want the first release to avoid queues, parallel calls, distributed locks, and automatic resume, so that effort remains focused on generating and reviewing useful descriptions.
## Implementation Decisions
- The Fastify backend owns one installation-wide Description Generation Run and its sequential
processing loop. It does not delegate lifecycle ownership to Pi or Python.
- A Description Generation Run has one of `queued`, `running`, `completed`,
`completed_with_errors`, `cancelled`, `failed`, or `interrupted`. It stores the Workspace
Database, requested scope, selected model identifier, workspace language, progress counters,
timestamps, and an optional final error summary.
- Ordered Description Generation Events store timestamp, severity, and safe human-readable text.
When an event identifies a target, it uses the object type and qualified physical name, such as
`Column "patients.birth_date"` or `Table "patients"`; catalog UUIDs remain internal identifiers.
No durable per-target jobs, model invocation rows, prompt snapshots, sample snapshots, leases,
heartbeats, registry revisions, or provenance chains are introduced.
- Starting a run schedules an in-process background loop and returns the run immediately. The API
exposes start, run/history lookup, event listing and streaming, cancellation, stale-run unlock,
and the safe list of configured model choices. There are no retry-item or resume endpoints.
- One in-memory generation manager enforces the installation-wide active-run rule. The existing
Catalog Operation Coordinator reserves the target Workspace Database for the duration of the
run, without being generalized into a new operation framework.
- On backend startup, persisted `queued` or `running` Description Generation Runs become
`interrupted`. The application performs no automatic replay or resume.
- Unlock succeeds only when no live generation loop or helper child exists. It marks the stale run
interrupted and releases the local reservation; it is not a distributed lock recovery protocol.
- Each model completion uses a short-lived Python helper backed by LiteLLM. Structured input is
supplied over stdin, structured output alone is emitted on stdout, diagnostics use stderr, and
the helper can be terminated by cancellation.
- A completion request contains no more than ten targets. Requests run one at a time. The helper
performs at most one retry for a transient technical failure.
- Three consecutive model-request failures fail the run. A successful request resets that count.
Isolated exhausted failures may be logged and skipped, producing `completed_with_errors` if the
run later reaches its end.
- Every valid generated or non-generatable result is applied immediately to Generated Description.
Earlier writes are retained after cancellation, interruption, or later failure.
- A response must identify requested targets unambiguously and classify each returned result as
generated or non-generatable. Duplicate, unknown, missing, or malformed mappings cause a
technical request failure and no result from that ambiguous response is applied.
- The parser also tolerates one JSON object enclosed by one complete `json` code fence, because
some supported models add that formatting despite the prompt. Any prose outside the fence,
multiple payloads, or malformed/ambiguous mappings remain invalid.
- The application supplies localized standard non-generatable text. Provider wording is not used
as the standard value, and technical errors never write that value.
- A full-database run generates eligible Catalog Columns before Catalog Tables. Generate Missing
excludes targets whose Generated Description is already non-empty; all-generation may replace
existing generated proposals only after the initiating action makes that scope explicit.
- Model choices are declared under a metadata-generation section in installation setup YAML. Each
choice has a stable identifier, display label, LiteLLM provider/model settings, optional endpoint
settings, and an optional environment-secret reference for its API key. The reference may be
omitted only when an explicit endpoint is configured for unauthenticated access. One identifier
is the default.
- An explicit endpoint may set `disableThinking: true`; the helper translates it only to the
Qwen-compatible chat-template switch needed to keep the response within the strict JSON contract.
- Metadata-generation setup is separate from application settings for Pi and from workspace
`llm_policy`. Raw keys never enter setup YAML, the catalog database, API responses, process
arguments, or event text. Configuration reload is restart-only.
- If setup defines no usable model, the safe model-list response is empty and the UI disables
generation with an explanation. The backend still rejects direct generation attempts.
- Prompt construction treats schema names, comments, descriptions, and values as untrusted data.
It requests output in the workspace language and separates instructions from catalog content.
- A request may contain up to five real source rows and up to five representative distinct,
non-null values for relevant columns. Inputs are bounded before prompt construction and are not
persisted or logged.
- The UI discloses that real data can be sent to the selected provider. A future Sensitive Data
Policy will classify values and exclude or anonymize protected data; that policy is not silently
approximated in this slice.
- The generation UI reuses Database Management's table and column selections, model selector,
Actions control, run drawer conventions, SSE delivery, and polling fallback where practical.
Visual parity with Catalog Sync Run logs is not required.
- The consolidation action copies each selected, non-empty Generated Description into Description
in a catalog transaction, retains Generated Description, skips empty proposals, and reports
copied and skipped counts. It never writes to the external Workspace Database.
- Generation, cancellation, unlock, and consolidation require the existing database-management
permission and are validated by the backend independently of UI state.
- No user-facing generation CLI is added. The Python process is an internal completion adapter,
not an operator surface or a long-lived service.
## Testing Decisions
- Tests assert externally observable behavior rather than private loop structure, process timing,
or LiteLLM implementation details.
- The primary and highest test seam is the Fastify catalog API with a test PostgreSQL catalog and
an injected fake Model Completer. It verifies complete paths through authorization, run
persistence, sequential processing, event delivery, Generated Description updates, and final
status without contacting a real provider.
- API tests cover each generation scope, column-before-table order, the ten-target request bound,
model validation, one-active-run conflict, target-database exclusion, cancellation, startup
interruption, Unlock safeguards, Generate Missing, partial success, consecutive failure
handling, non-generatable localization, malformed responses, redacted events, and permissions.
- Catalog repository integration tests verify the migration, run and event ordering, active-run
constraint, immediate description writes, history queries, startup interruption, and bulk
consolidation behavior against PostgreSQL.
- The Python helper has a small black-box contract suite using a simulated LiteLLM adapter. It
verifies stdin/stdout framing, pristine stdout, stderr diagnostics, normalized success and
failure output, one transient retry, secret redaction, and termination behavior.
- Setup-validation tests cover duplicate model identifiers, missing or unknown defaults, malformed
provider settings, missing secret references, safe public model projection, and strict separation
from Pi and workspace model settings.
- Database Management tests use the existing browser-level component seam with MSW. They verify
model selection and default, selected/all/missing actions, disabled state without models, running
progress and logs, polling recovery, cancellation, Unlock visibility, terminal summaries,
generated-text refresh, and selected consolidation with copied/skipped counts.
- Existing Catalog Sync Run route, repository, SSE, and drawer tests are prior art for asynchronous
status and event behavior. Existing catalog table/column editing and Database Management tests
are prior art for optimistic catalog updates, permissions, selection, and action controls.
- One required manual acceptance gate, outside deterministic CI, uses the installation's configured
default model and a disposable PostgreSQL database containing only invented data. Its application
credentials are read-only. It generates Italian text for one Catalog Column and one Catalog
Table, verifies their Generated Description, inspects the safe activity log, confirms that no key
or sample value is exposed, and consolidates one selected result. If the configured secret is not
available, acceptance stops without exposing or requesting the key in conversation.
- Delivery includes a strict MkDocs build executed through repository-managed, reproducible
documentation dependencies rather than globally installed Python packages. A readable direct
dependency file is retained, a complete transitive lock is generated with `uv`, and one canonical
repository command performs the strict build from that lock.
- Successful real-provider acceptance is recorded in a short sanitized report under
`docs/testing/`. It identifies the model and checks performed but contains no credentials,
prompts, source samples, complete provider payloads, or generated database values.
- No tests are added for worker queues, parallel generation, distributed locking, multi-replica
recovery, automatic resume, cost accounting, or model fallback because those behaviors are out
of scope.
## Out of Scope
- Reusing Pi to execute Description Generation or changing Pi's model configuration.
- A shared Installation Model Registry, model gateway, long-lived Python sidecar, or provider
management platform.
- A user-facing generation CLI.
- Parallel model calls, worker queues, adaptive rate limiting, distributed locks, leases,
heartbeats, automatic resume, or multi-replica execution.
- Durable target jobs, invocation history, prompts, samples, token usage, cost accounting,
provenance chains, target snapshots, or advanced retention controls.
- Automatic retry or resume of individual targets beyond one technical helper retry and a new
Generate Missing run.
- Automatic fallback to a different model.
- Writing generated text into comments of the external Workspace Database.
- Generating logical relationships or other catalog metadata beyond Catalog Table and Catalog
Column descriptions.
- Implementing the Sensitive Data Policy. Its definition and exclusion/anonymization behavior are
a required follow-up improvement.
- Generalizing the log viewer across unrelated metadata operations. That broader concern remains
related to Gitea issue #2.
## Further Notes
- The design deliberately follows ThothAI's proven simple workflow while adapting it to ThothII's
asynchronous browser interaction, setup ownership, and existing Generated Description model.
- The source-sampling disclosure is a release requirement, not merely documentation for operators.
- `completed_with_errors` is reserved for a run that reaches the end after isolated technical
failures. Three consecutive failures end the run as `failed`.
- Successful values are their own recovery record: after interruption, Generate Missing naturally
skips them without needing replay state.
- The implementation is available without a feature flag once the catalog migration and valid
setup are present. With no configured model, the feature remains visibly unavailable rather than
partially initialized.
- The final manual gate is intentionally narrow: one real-provider run covers one Catalog Column
and one Catalog Table, generated Italian text, safe events, and one consolidation. Automated
tests remain the evidence for All, Missing, Stop, restart interruption, Unlock, and failure paths.
@@ -1,173 +0,0 @@
# AI catalog description generation
Status: simplified design, API, persistence, test seams, and delivery tickets accepted.
## Objective
Bring ThothAI's useful AI comment-generation workflow into the ThothII Metadata Catalog without
turning it into a general job platform. Administrators can generate editable descriptions for
catalog tables and columns, inspect progress, stop a run, recover a stale run, and explicitly copy
approved generated text into the curated Description field.
The implementation is UI/API only. There is no user-facing generation command.
## ThothAI behavior retained
- Generate descriptions for selected tables, selected columns, missing descriptions, or all
eligible targets.
- Generate columns before their containing table when running the full workflow, so table prompts
can benefit from the resulting column descriptions.
- Process bounded batches of at most ten targets, one model request at a time.
- Include schema context, existing catalog text, up to five real source rows, and up to five
representative non-null values when available.
- Keep generated text separate from the curated Description until an administrator consolidates
it.
- Use the existing table and column checkboxes plus the Actions selector to copy Generated
Description into Description for one or more selected records. The generated value is retained.
- Store a localized standard value such as `Non generabile` when a valid model response says that
a description cannot be inferred.
Unlike ThothAI, every generation action is asynchronous from the browser's perspective and exposes
a persistent, readable activity log.
## Minimal architecture
The Fastify backend owns the run lifecycle and sequential loop. It starts one short-lived Python
helper for each model completion. The helper uses LiteLLM, accepts structured input on stdin,
returns structured output on stdout, and writes diagnostics only to stderr.
This is preferred over reusing Pi. Pi remains the interactive NL-to-SQL orchestration surface,
whereas description generation is a bounded batch transformation with no conversational state or
human gate. A LiteLLM helper avoids inventing a Pi session protocol for a task that needs one
request and one structured response.
There is no Python daemon, model gateway, queue service, worker pool, or generation CLI. Python is
already a core implementation language in ThothII's harness and core image; this helper does not
introduce a new runtime family.
## Run lifecycle and exclusion
- At most one Description Generation Run may be queued or running in the installation.
- Start returns immediately after creating the run and scheduling the in-process backend loop.
- Requests are sequential; there is no parallel provider traffic.
- The target Workspace Database is reserved through the existing in-memory catalog-operation
coordinator. Synchronization, cleanup, consolidation, and direct catalog edits for that database
are rejected while generation is active.
- A second generation start is rejected with a conflict response.
- Stop terminates the current helper process, stops further targets, and marks the run cancelled.
- Backend startup marks any queued or running generation row interrupted. It does not resume work.
- Generate Missing is the normal manual continuation mechanism because successful values were
already saved.
- Unlock is available only when the backend has no live generation process; it marks a stale
recorded run interrupted and clears the local reservation.
This is intentionally a single-process policy. Multi-replica coordination is out of scope.
## Persistence
Persist only:
- a Description Generation Run with database, scope, selected model, language, status, counters,
timestamps, and an optional final error summary;
- ordered Description Generation Events containing timestamp, level, and human-readable text;
- each successful or non-generatable result directly in the target's Generated Description.
Do not add per-target job rows, invocation history, prompt or sample snapshots, provider cost
accounting, leases, heartbeats, registry revisions, or generated-description provenance. The event
log is operational evidence, not a replay mechanism.
## Model setup
Selectable models and their default belong to application setup YAML, not to a workspace. Each
entry supplies a stable display identifier, LiteLLM provider/model information, optional endpoint
settings, and—unless that explicit endpoint is unauthenticated—a reference to an installation
secret containing the API key. Keyless entries without an explicit endpoint are invalid. Raw keys must not be
stored in the YAML, database, frontend, events, or process arguments.
An explicit endpoint may opt into `disableThinking: true` when its Qwen-compatible chat template
would otherwise place reasoning text around the required JSON result.
This metadata-generation configuration is independent of the existing Pi provider/model settings
and workspace `llm_policy`. A setup change takes effect after application restart. If no model is
configured, generation controls are disabled with an explanatory message.
The browser receives only the selectable identifiers and labels. The selected value defaults to
the setup default and is validated again by the backend when a run starts.
## Prompt inputs and outputs
Targets are grouped in model requests of at most ten. Prompts distinguish instructions from
untrusted schema names, comments, descriptions, and sampled values. A response must map every
returned result to a requested target and classify it as generated or non-generatable. Missing,
duplicate, unknown, or malformed target results make that request a technical failure rather than
silently writing ambiguous text.
One complete `json` code fence around the object is tolerated for model compatibility; prose
outside it, multiple payloads, and ambiguous mappings are still rejected.
For a complete database run, eligible columns are processed before tables. A table request can use
the current Generated Description or Description of its columns. The output language is the
workspace language; the standard non-generatable text is localized by the application rather than
trusted to arbitrary model wording.
Up to five source rows and five representative examples may be sent to the provider and are never
persisted. Delivery must call out this disclosure. A follow-up Sensitive Data Policy will define
which values are excluded or anonymized.
## Errors, retry, and logs
The helper performs at most one retry for a transient technical provider failure. A final failed
request produces an error event and increments the consecutive-error count. The run stops as
failed after three consecutive technical failures; any successful request resets the count. There
is no automatic fallback to another model.
Valid non-generatable outcomes are results, not technical errors. Successful results from earlier
requests remain stored when a later request fails or the run is stopped.
The UI shows status, counters, selected model, start/end times, and a chronological text log. Live
delivery may reuse the existing SSE infrastructure with polling as fallback; exact visual parity
with synchronization logs is not required. Logs must not contain API keys, prompts, source sample
values, or full provider payloads.
## Explicitly deferred complexity
- shared model registry or cutover of Pi configuration;
- long-lived Python sidecar or internal HTTP model gateway;
- generic catalog-operation kernel;
- durable target items, invocation records, target snapshots, or provenance chains;
- distributed locks, leases, heartbeats, worker queues, automatic resume, or multi-replica support;
- parallel calls, adaptive rate limiting, cost estimation, advanced metrics, or model fallback;
- user-facing generation CLI;
- automatic writeback to comments in the external database;
- Sensitive Data Policy implementation, which remains a required improvement after this slice.
## Delivery tracking
The accepted specification is Gitea issue #4 and the implementation is split into issues #5–#11.
Each ticket is a bounded vertical slice with explicit Gitea dependencies. Implementation proceeds
from the unblocked frontier, using a fresh subagent context for each ticket; integration and final
verification remain centralized so later slices cannot silently reopen the deferred platform
features above.
Issue #4 remains open until delivery completes four final gates: the stale Compose service-set
contract is corrected in its own commit; documentation dependencies are repository-managed and a
strict MkDocs build passes; one narrow real-provider acceptance run succeeds against non-sensitive
test data; and a separate, non-blocking Sensitive Data Policy design ticket is linked as required
follow-up work.
The documentation toolchain retains a readable direct-dependency input, adds a complete lock
generated with `uv`, and exposes one canonical strict-build command. Real-provider acceptance uses
the installation's configured default model and a disposable PostgreSQL database seeded only with
invented values and accessed read-only by the application. A missing protected model secret stops
the gate without disclosing it. The successful gate is captured in a sanitized report under
`docs/testing/` without prompts, samples, full generated values, payloads, or credentials.
Delivery is organized as four reviewable commits: the stale Compose contract correction, the
reproducible documentation toolchain, the AI-description feature, and—only after acceptance—the
sanitized acceptance report. A failed real-provider gate does not invalidate already verified
commits, but issue #4 remains open and no acceptance report claims success. Application defects are
fixed and reverified; missing configuration or provider unavailability is recorded and retried.
After every gate passes, the existing `codex/db-management` branch is pushed to its configured
origin without introducing a new pull-request workflow, then issue #4 is closed with links to the
delivery evidence. The separate Sensitive Data Policy issue is created as non-blocking follow-up,
linked to #4, and labeled `enhancement` plus `ready-for-human` because its design requires a future
`grill-with-docs` before agent implementation.
@@ -1,248 +0,0 @@
# Installation Model Catalog
Status: implemented on 2026-09-02.
## Outcome
`thothii-installation.yaml` is the only operator-authored source for models used by interactive
sessions, metadata generation, and embedding. Runtime-specific files are deterministic projections,
not additional configuration sources. Workspace descriptors contain database and Evidence concerns
and no model, provider, allowlist, default, embedding, or vector-store configuration.
This design does not merge execution lifecycles. Pi continues to run interactive sessions, the
short-lived LiteLLM helper continues to perform metadata generation, and the internal Ollama service
continues to provide embeddings. They share model declaration, not execution machinery.
## Canonical installation shape
The following example covers all currently required cases: a Pi built-in model, an authenticated
custom endpoint, a keyless internal endpoint, metadata generation, and the single embedding model.
```yaml
schemaVersion: 2
profile: server
projectDirectory: /srv/thothii
envFile: /srv/thothii/operator.env
workspaceRepository:
remote: git@git.example.com:organization/workspaces.git
branch: main
access: ssh
modelCatalog:
defaults:
session: zai/glm-5.3
metadataGeneration: local-qwen/qwen3.6-35b-a3b
embedding:
id: ollama/qwen3-embedding:0.6b
dimensions: 1024
providers:
deepseek:
authentication:
mode: pi_auth
session:
mode: pi_builtin
models:
deepseek-v4-pro:
session: {}
deepseek-v4-flash:
session: {}
zai:
endpoint:
baseUrl: https://api.z.ai/api/coding/paas/v4
authentication:
mode: secret_env
apiKeyEnv: ZAI_API_KEY
session:
mode: openai_compatible
metadataGeneration:
litellmProvider: openai
models:
glm-5.3:
label: GLM-5.3
session:
reasoning: true
contextWindow: 200000
maxTokens: 131072
metadataGeneration: {}
local-qwen:
endpoint:
baseUrl: https://ml-aritmolab.policlinicosandonato.it/v1
authentication:
mode: none
session:
mode: openai_compatible
metadataGeneration:
litellmProvider: openai
models:
qwen3.6-35b-a3b:
label: Qwen3.6 35B A3B
session:
reasoning: false
contextWindow: 131072
maxTokens: 16384
compatibility:
supportsDeveloperRole: false
supportsReasoningEffort: false
supportsStore: false
maxTokensField: max_tokens
metadataGeneration:
disableThinking: true
authentication:
configDirectory: /srv/thothii/auth-canonical
runtimeProjection:
directory: /srv/thothii/auth-runtime
uid: 10001
gid: 10001
```
The catalog uses maps instead of repeated IDs. The canonical identity of a model is always derived
as `<provider-key>/<model-key>`. `upstreamModel` may be added to a model only when the endpoint uses
a different identifier. `label` is optional and falls back to the canonical identity.
Model eligibility is not repeated in an `usages` array. A `session` block makes the model eligible
for sessions; a `metadataGeneration` block makes it eligible for metadata generation. The embedding
is a single required installation value rather than a list plus default.
## Provider and authentication rules
A provider owns one endpoint, one authentication mode, and zero or one adapter for each runtime.
Model entries cannot override provider endpoint or credentials. If the same upstream service needs
different endpoints or credentials, the installation declares two provider identities.
Supported session modes are intentionally closed:
- `pi_builtin`: Pi already owns the model's technical descriptor; the model's `session` block is
empty and ThothII does not copy context-window or compatibility facts.
- `openai_compatible`: ThothII generates a Pi custom-provider descriptor; each session model supplies
the technical values required by Pi.
Metadata generation uses the provider-level `litellmProvider`. A model-level
`metadataGeneration.disableThinking: true` is permitted only for an explicit compatible endpoint.
There is no generic adapter or plugin abstraction in schema version 2.
Exactly one provider authentication mode is allowed:
- `secret_env` requires an approved API-key environment reference present in the protected secret
bundle. Secret values never enter YAML, generated files, logs, arguments, or API responses.
- `pi_auth` is valid only for session-only `pi_builtin` providers and resolves through Pi's protected
authentication projection.
- `none` is valid only for an explicit endpoint. Runtime projections may supply a fixed non-secret
compatibility placeholder when a client library requires a non-empty key.
## Defaults and selections
`defaults.session` and `embedding` are required. `defaults.metadataGeneration` is required exactly
when at least one model has a `metadataGeneration` block; metadata generation may otherwise be
absent and its UI controls are disabled.
`modelCatalog.defaults.session` is the only configured session-model default. `PI_PROVIDER`,
`PI_MODEL`, and provider/model fields in installation-default settings are removed. A user choice is
a Model Selection containing only the canonical model identity and runtime controls such as thinking
level. A session manifest pins the selected canonical identity.
Removing the currently selected model causes new-session selection to fall back to the catalog
default with an explicit administrative warning. An existing session is never silently moved to a
different model; resume fails with `model_unavailable` when its pinned identity can no longer be
resolved.
## Generated runtime projections
Before Compose starts, `tht` strictly validates schema version 2 and generates installation-local
artifacts below `deploy/<installation-id>/generated/`:
- a normalized catalog JSON consumed defensively by the backend;
- Pi `models.json` for custom providers;
- Pi `settings.json`, combining fixed product settings with the session-eligible canonical IDs;
- a Compose override that mounts the projections and supplies embedding identity and dimensions to
core, preprocessing, and `embedding-model-init`.
Generation is deterministic and published only after every candidate artifact validates. A failed
generation aborts start before Compose is invoked. `tht doctor` recomputes expected bytes and reports
differences; no digest manifest or separate apply command exists. When projection bytes change,
`tht start` recreates the affected services so they cannot continue with an older bind mount.
Pi-only restart, update, and rollback operations reject projection drift and direct the operator to
`tht start`, because applying only the core-facing files could leave embedding services stale.
Generated projections are not backed up. Restore validates the canonical installation descriptor,
regenerates every projection, and only then starts services. Base Compose files and `operator.env`
must contain no model identities, defaults, endpoints, or dimensions.
## Workspace schema v4
Workspace schema v4 removes both top-level `llm_policy` and `semantic_index`. The entire latter
block is redundant today: its engine and distance are product constants, its collection duplicates
the workspace ID, and its model and dimensions are installation facts.
The runtime derives:
- Qdrant collection identity from the workspace ID;
- engine and distance from the supported product contract;
- embedding identity and dimensions from the Installation Model Catalog.
The published index generation records the canonical embedding identity and dimensions that created
it. A mismatch makes the index explicitly incompatible and requires operator-triggered
preprocessing. No existing index is deleted or rebuilt automatically.
The v3-to-v4 workspace migration is deterministic: set `workspace.schema_version` to `4`, remove
`llm_policy`, and remove `semantic_index`. It does not alter database, Evidence, diagnostics, or
binding data.
## Installation migration
Legacy installation migration must inspect all three former sources:
1. `metadataGeneration` in `thothii-installation.yaml`;
2. `deploy/pi/models.json`;
3. `deploy/pi/settings.json`.
The migrator emits a version-2 candidate only when it can reconcile identities, endpoints,
credentials, and runtime-specific facts without guessing. Ambiguous aliases such as `glm-53`,
`zai/glm-5.3`, and `openai/glm-5.3` are not silently equated. A conflict produces a field-level
report and leaves every input unchanged for operator resolution.
After migration, the strict loader rejects `metadataGeneration`, workspace `llm_policy`, workspace
`semantic_index`, legacy Pi source files, unknown fields, duplicate YAML keys, invalid defaults, and
incompatible authentication/adapter combinations with an actionable `migration_required` or
validation error.
## Final simplicity audit
The accepted design removes every configuration duplication that can be removed without inference:
- one authored installation file instead of an installation block plus two Pi files;
- one canonical `provider/model` identity instead of display IDs and runtime IDs;
- per-use blocks instead of a duplicated usages list;
- one embedding entry instead of a selectable embedding catalog;
- one catalog session default instead of environment and settings defaults;
- no model or vector-store fields in workspace descriptors;
- provider-level credentials instead of per-model credentials;
- no generic runtime-plugin abstraction;
- no persisted digest, apply command, or backup of generated projections.
The remaining generated files are necessary boundary adapters, not configuration concepts. Making
the backend parse the authoring YAML independently would remove one file but restore two semantic
validators. Hard-coding embedding values in Compose would remove one projection but restore a model
source outside the catalog. Inferring authentication from missing fields would save one YAML key but
turn a safe explicit choice into ambiguity. These apparent simplifications are therefore rejected.
No further reduction was found that preserves one authority, strict validation, explicit security,
session determinism, and model-free workspaces.
## Implementation surface
Implementation must update the host `tht` installation loader, setup and lifecycle projection,
doctor, backup/restore, Compose mounts and embedding inputs, backend catalog/settings/session model
resolution, workspace schema and migration, runtime rendering and diagnostics, frontend workspace
drafts and model filtering, examples, fixtures, and documentation. Existing session manifests remain
readable and keep their pinned provider/model identity; only resume resolution changes to the new
catalog.
Implementation completed after explicit approval. The installation schema, deterministic runtime
projections, migration path, model-free workspace schema v4, backend consumers, operator UI,
fixtures, and documentation now enforce this contract.
+3 -3
View File
@@ -5,9 +5,9 @@ manuale con commit/push dell'operatore accettati per la release 0;
restanti semplificazioni confermate, con chiarimenti su impatto core e controllo Git;
E1, E2, E3 e il collegamento ai gate X1 implementati il 2026-09-09.
Il contratto X1 è in [Session corrections](../contracts/archive-repair.md). Risultati e limiti in
[E1 — validazione](2026-09-09-evidence-e1-validation.md) e
[E2 — validazione](2026-09-09-evidence-e2-validation.md) e
[E3 — validazione](2026-09-09-evidence-e3-validation.md).
[E1 — validazione](../reports/knowledge-archives-release.md) e
[E2 — validazione](../reports/knowledge-archives-release.md) e
[E3 — validazione](../reports/knowledge-archives-release.md).
La [revisione di semplicità](2026-09-08-memory-evidence-simplification-review.md)
registra il riesame di Q1–Q15. Il flusso principale è draft scritta dallo specialista
@@ -2,7 +2,7 @@
Data del piano: 2026-09-08. Aggiornamento 2026-09-09: M1–M3, E1–E3 e X1
implementati. Risultati e limiti della verifica finale sono raccolti nel
[rapporto X1](2026-09-09-archive-repair-x1-validation.md).
[rapporto X1](../reports/knowledge-archives-release.md).
La [revisione di semplicità](2026-09-08-memory-evidence-simplification-review.md)
riesamina tutte le decisioni Q1–Q15 alla luce degli ultimi chiarimenti del
+1 -1
View File
@@ -6,7 +6,7 @@ confini di test confermati dal proprietario il 2026-09-08.
Primo incremento del progetto Memory management. Attua le decisioni già approvate
nel piano del 2026-09-08 e nell'ADR 0018. Le scelte tecniche di dettaglio qui
proposte derivano dalla ricognizione del runtime. Gli esiti dell'implementazione
sono riportati nel [rapporto di verifica](2026-09-08-memory-m1-validation.md).
sono riportati nel [rapporto di verifica](../reports/knowledge-archives-release.md).
## Problem Statement
@@ -1,103 +0,0 @@
# M1 — Implementazione e verifica
Data: 2026-09-08. Implementazione locale della
[specifica approvata](2026-09-08-memory-m1-spec.md), associata all'
[issue 27](https://git.tylconsulting.it/mptyl/ThothII/issues/27).
## Risultato
La pagina **Memory management** è disponibile nell'Administration dopo Database
management. Gestisce le quattro famiglie di card, elenco completo, ricerca e filtri,
ordinamento, dettaglio, creazione, modifica, cancellazione, collegamenti e dipendenze.
Richiede un amministratore autenticato e una selezione esplicita del workspace;
non richiede una sessione o un database DWH configurato.
Il harness possiede l'archivio PostgreSQL `thoth_memory`. Card, collegamenti,
dipendenze e lavoro di propagazione sono salvati nella stessa transazione.
La pagina distingue salvataggio fallito e contenuto salvato con indice incompleto,
offrendo retry anche per le cancellazioni. Recall Memory ed exemplar verificano
esistenza, workspace e proiezione corrente nell'archivio prima di restituire contenuto.
Promozione, salvataggio singolo e finalizzazione corrente passano dal servizio
autorevole. Le ricevute della sorgente impediscono duplicati e ricreazione di card
cancellate. Reindicizzazione e preprocessing non importano vecchi payload o sessioni.
L'errore Memory non annulla una sessione già finalizzata; il gate segnala anche
una promozione salvata con indicizzazione incompleta.
Le migrazioni sono versionate, controllate tramite checksum e incluse nel wheel
e nell'immagine core. Il servizio di preparazione `catalog-migrate` le esegue dopo
quelle del Catalog. Il runtime assume il ruolo limitato `thoth_memory_runtime`,
con isolamento del workspace tramite RLS e senza privilegi DDL.
## Verifiche eseguite
| Confine | Esito |
| --- | --- |
| Harness, test senza L0/L2 | 1.134 passati; i 9 test dei percorsi portabili sono stati eseguiti separatamente e sono passati. |
| Servizio Memory, PostgreSQL e Qdrant reali | 17 passati, inclusi CLI, migrazioni, ruolo runtime, isolamento, transazioni, outage, retry, cancellazioni, cambio famiglia e rebuild. |
| Gate Pi | 190 passati, inclusi identità UUID e avviso dopo salvataggio con indice incompleto. |
| Backend | 1.345 passati nella suite completa, 40 esclusi dalle condizioni previste dai test; un test di autenticazione ha superato il timeout sotto carico. Il relativo file è stato rieseguito isolato: tutti i 17 test passati. |
| Frontend | 632 passati, inclusi ingresso dall'AppShell, form, filtri, collegamenti, dipendenze e retry delle cancellazioni. |
| Browser integrato | Passato: autenticazione amministratore, creazione, modifica, riavvio del backend, rilettura, cancellazione e assenza nel recall. |
| Build e tipi | Build backend e frontend, typecheck TypeScript e build documentale strict superati. |
| Lint e diff | Ruff sui file Python modificati e `git diff --check` superati. Il lint globale segnala tre rilievi in file non modificati, elencati sotto. |
Il browser utilizza autenticamente frontend, login locale, Fastify, ThtRunner,
CLI Python, PostgreSQL e Qdrant. Gli embedding sono deterministici e le attività
Pi/sessione estranee al percorso Memory usano le fixture esistenti. Non sono state
intercettate le API Memory. Sono stati usati container temporanei PostgreSQL 16 e
Qdrant 1.18.2, senza accesso a un DWH remoto o a un modello generativo.
Il test browser ha consentito di correggere etichette accessibili instabili nei
campi compilati e la sovrapposizione del pannello di recupero ai comandi del dettaglio.
La selezione del workspace e l'uscita dalla pagina sono bloccate durante le operazioni.
Il lint globale preesistente riguarda soltanto:
- ordinamento import in `harness/tests/test_effective_relationships.py`;
- ordinamento import in `harness/tests/test_p3_dwh_binding.py`;
- uso di `datetime.UTC` in `harness/tht/mschema/catalog_snapshot.py`.
## Riproduzione
Usare Node 24 e le dipendenze installate dei tre layer. Per eseguire il harness
in un ambiente con home non scrivibile si può impostare `THT_HOME` su una directory
di prova. I test dei percorsi portabili devono essere eseguiti senza questo override,
perché verificano deliberatamente la risoluzione dell'home e di `THT_DATA_ROOT`.
```sh
cd harness
THT_HOME=/private/tmp/thothii-m1-test-home .venv/bin/pytest -m 'not l0 and not l2' --ignore=tests/test_portable_paths.py -q
.venv/bin/pytest tests/test_portable_paths.py -q
.venv/bin/pytest tests/memory/test_administration.py -q
npm test
```
```sh
cd backend
npx vitest run
npx tsc --noEmit -p .
npm run build
```
```sh
cd frontend
npx vitest run
npx tsc -b
npm run build
THT_MEMORY_BROWSER_E2E=1 npx playwright test e2e/memory-real.spec.ts
```
Il percorso browser richiede Docker, Python del harness, Go per il bridge di
autenticazione e Chromium di Playwright. Avvia risorse isolate e le rimuove alla
fine. Su macOS il browser deve poter avviare i processi Chromium fuori dalle
restrizioni della sandbox. La build documentale si esegue dalla radice con
`./scripts/build-docs.sh`.
## Stato della consegna
Le modifiche sono nel worktree locale. Nessuno stack già attivo è stato aggiornato
e nessun dato esistente è stato migrato o eliminato. Prima di usare M1 su
un'installazione occorrono il nuovo core e la preparazione `catalog-migrate`.
M2 (retrieval ibrido ed espansione dei collegamenti), M3 (integrazione estesa nel
workflow) ed Evidence management restano incrementi successivi.
+1 -1
View File
@@ -2,7 +2,7 @@
Data del piano: 2026-09-08. Aggiornamento 2026-09-09: M1–M3 implementati,
con integrazione X1 per le correzioni persistenti dei conflitti. Risultati e limiti
sono raccolti nel [rapporto X1](2026-09-09-archive-repair-x1-validation.md).
sono raccolti nel [rapporto X1](../reports/knowledge-archives-release.md).
La [revisione di semplicità](2026-09-08-memory-evidence-simplification-review.md)
mantiene il perimetro Memory e precisa un salvataggio sequenziale e una pulizia
@@ -1,9 +1,31 @@
# PRD — Security hardening per Docker personale e server multiutente
## Ripresa del lavoro e limiti di autorizzazione
Questo PRD resta una bozza, non una specifica di implementazione approvata. Il precedente
prompt di ripresa è stato consolidato qui il 15 settembre 2026. Riconfermare i rilievi
SEC-01–SEC-12 contro codice e dipendenze correnti, separando fatti, ipotesi, rischi del
profilo locale/server e problemi già risolti. Le vecchie survey non provano lo stato del server.
Usare inizialmente controlli in sola lettura e dati sintetici; accesso a server, IdP, DWH
o provider e relative mutazioni richiedono target e operazioni concordati.
Prima di implementare, il proprietario deve confermare profili di fiducia, isolamento
del runtime Pi, trattamento dei valori sensibili, revoca, retention, limiti di risorse,
priorità e criteri misurabili. I modelli visibili sul server sono un problema separato.
Registrare le decisioni in questo PRD; pubblicare spec e ticket Gitea soltanto dopo la
conferma del perimetro e della granularità. La bonifica documentale non autorizza il
codice di sicurezza, né il rollout di tutti i punti SEC.
L'implementazione futura richiede un worktree dedicato, test positivi/negativi ai confini
concordati, typecheck e revisione contro standard e specifica. Conservare gate manuali,
evidenze e rollback; un ticket completato non chiude automaticamente il PRD. Non copiare
segreti o dati operativi nei worktree. Usare le skill effettivamente disponibili per
chiarimento, diagnosi e revisione, senza assumere che i vecchi nomi dei comandi esistano.
**Stato:** bozza da validare con `grill-with-docs`; implementazione rinviata.
**Data:** 8 settembre 2026.
**Owner delle decisioni:** il maintainer di ThothII.
**Ripresa:** [prompt per la prossima sessione](2026-09-08-security-hardening-resume-prompt.md).
**Ripresa:** seguire la sezione «Ripresa del lavoro e limiti di autorizzazione» sopra.
Questo documento conserva la survey di sicurezza discussa con il maintainer e propone requisiti,
priorità e criteri di accettazione. Non è una spec approvata, un penetration test, una certificazione
@@ -1,100 +0,0 @@
# Riprendere il PRD di sicurezza con le skill di Pocock
**Stato:** prompt conservato per uso futuro; nessuna esecuzione programmata.
**PRD:** [Security hardening per Docker personale e server multiutente](2026-09-08-security-hardening-prd.md).
Apri una sessione nella codebase ThothII e incolla il blocco seguente. Il nome corretto della
skill è `grill-with-docs`, che combina `grilling` e `domain-modeling`. Il prompt apre la fase di
chiarimento; il passaggio a issue, worktree e implementazione resta soggetto alle conferme indicate.
```text
Riprendiamo il lavoro di sicurezza rinviato l'8 settembre 2026.
Leggi docs/plans/2026-09-08-security-hardening-prd.md. È una bozza di PRD ricavata da una
survey storica, non una spec approvata né una prova della configurazione del server remoto.
Voglio preparare interventi proporzionati per Docker su Mac/PC personale e per un server
multiutente con autenticazione built-in oppure OIDC. Il problema separato dei modelli
visibili sul server è fuori perimetro.
Usa realmente le skill di Matt Pocock: leggi le istruzioni installate, dichiarando quali
applichi. Parti da ask-matt per verificare il percorso e da grill-with-docs per il lavoro
di design; quest'ultima richiede grilling e domain-modeling. Se una skill non è disponibile,
segnalalo e concorda il fallback, senza installarla o fingere di averla eseguita.
FASE 1 — Riconferma delle evidenze, senza modificare il runtime
1. Leggi AGENTS.md, PROJECT_STATE.md, CONTEXT.md, le istruzioni docs/agents/ su dominio,
issue tracker e label, gli ADR pertinenti e il PRD. Controlla HEAD, stato del worktree
e differenze dalla baseline della survey. Preserva tutte le modifiche preesistenti.
2. Riconferma i rilievi SEC-01…SEC-12 nel codice corrente. Separa fatti verificati,
ipotesi, rischi condizionati al profilo e problemi già risolti. Non trattare i vecchi
conteggi delle dipendenze come una scansione aggiornata.
3. Usa controlli locali read-only e dati sintetici. Per un difetto da riprodurre, usa
diagnosing-bugs con un segnale ripetibile sul comportamento effettivo; una diagnosi
non autorizza ancora il fix. Confronta fatti di librerie e advisory con fonti primarie
correnti quando necessario, senza inviare codice privato o segreti ai servizi di ricerca.
4. Non accedere o intervenire su server, IdP, DWH o provider reali senza aver concordato
target e operazioni. Non mostrare API key, cookie, password o campioni di dati reali.
Esito della fase: una matrice aggiornata che conserva gli ID dei rilievi, con evidenza,
profilo interessato e stato. Un fatto ancora non verificabile resta esplicitamente aperto.
FASE 2 — grill-with-docs, con me presente
5. Costruisci l'albero delle decisioni. Parti da profili di deploy, fiducia fra utenti e
condivisione dei dati; poi affronta isolamento di Pi, policy dei valori sensibili,
revoca, retention e limiti seguendo le dipendenze effettive.
6. A ogni round presenta soltanto le domande attualmente sbloccate, numerate, con la tua
raccomandazione e i trade-off. Attendi le mie risposte prima di assumere le decisioni
successive. Cerca autonomamente i fatti ricavabili dal repository; usa agenti di
ricerca mirati quando previsto dalla skill, senza delegare a loro le mie decisioni.
7. Aggiorna il PRD distinguendo proposte e decisioni confermate. Aggiorna CONTEXT.md solo
per termini realmente risolti. Proponi ADR soltanto per scelte difficili da invertire,
sorprendenti senza contesto e fondate su alternative reali: basta il formato minimo.
8. Concorda requisiti, priorità, rischi accettati e criteri misurabili, inclusi i tempi
di revoca e i limiti di risorse. Conferma con me i confini pubblici dei test prima
di scriverli. Mantieni espliciti i gate manuali già presenti in PROJECT_STATE.md.
Esito della fase: nessuna decisione bloccante lasciata implicitamente all'agente;
riepilogo e mia conferma della comprensione condivisa. Fino a quella conferma rimani
su analisi e documentazione: nessun cambiamento applicativo o di deployment.
FASE 3 — Spec e ticket, soltanto dopo mia conferma
9. Usa to-spec per sintetizzare le decisioni già prese, senza riaprire arbitrariamente
l'intervista. Chiedimi conferma della pubblicazione prima di creare la spec nel
tracker canonico Gitea indicato in docs/agents/issue-tracker.md, non nel mirror GitHub.
Collega la spec canonica dal PRD e rendi chiaro quale documento è la fonte aggiornata.
10. Usa to-tickets per proporre fette verticali verificabili autonomamente, dimensionate
per un contesto fresco. Collega ogni ticket ai requisiti e ai rilievi pertinenti,
indica i veri blocker e includi criteri positivi e negativi. Fai approvare granularità
e dipendenze prima di pubblicare. Solo i ticket approvati e completi ricevono
ready-for-agent; non rimetterli in triage e non chiudere automaticamente la spec padre.
11. Se una decisione richiede una prova eseguibile, proponi un prototype limitato a quella
domanda prima di fissare la spec. Usa wayfinder solo se il lavoro risulta realmente
troppo ampio e incerto per essere chiarito con grill-with-docs.
Esito della fase: spec approvata e ticket autosufficienti con dipendenze risolte o esplicite.
Chiedimi se autorizzo il primo ticket: l'approvazione del design non avvia da sola il codice.
FASE 4 — Implementazione futura autorizzata
12. Prima di modificare codice, concorda e crea un worktree dedicato, verificando percorso,
branch e commit base. Non riusare una directory occupata, non alterare il worktree
originario e non copiare automaticamente segreti o dati operativi. Assicurati che
PRD e prompt siano disponibili nel worktree attraverso un passaggio esplicito.
13. Esegui implement su un ticket sbloccato per volta, in un contesto fresco. Segui tdd
ai confini concordati: un test rosso sul comportamento, implementazione minima,
test verde. Esegui typecheck e test mirati durante il lavoro e le suite pertinenti
al termine; usa fixture locali per IdP, DWH e provider.
14. Esegui code-review sui due assi Standards e Spec, usando i due agenti previsti dalla
skill e una base Git fissata. Assicurati che il diff esaminato includa tutto il lavoro
del ticket, anche se ancora non committato; un diff vuoto non è una review superata.
Risolvi i rilievi e verifica di nuovo. Commit soltanto del lavoro pertinente nel
worktree autorizzato; push, merge e deploy richiedono un'autorizzazione distinta.
15. Consegna evidenze dei test, istruzioni di adozione e rollback, gate manuali pendenti
e rischi residui. Non dichiarare chiuso il PRD intero se è concluso soltanto un ticket
o se resta un'accettazione dell'owner.
Inizia dalla Fase 1, poi proponimi il primo round di grill-with-docs.
```
@@ -1,112 +0,0 @@
# X1 — validation of session archive corrections
Date: 2026-09-09. The joint Memory/Evidence repair increment is implemented.
The authoritative contract is [Session archive corrections](../contracts/archive-repair.md).
## Delivered behavior
The session gate shows complete before/after content for specific alternatives targeting
Memory or Evidence. The reviewer chooses one correction or rejects all proposals as
inadequate and requests reformulation. The resulting receipt survives interruption;
saved content and index activation are reported separately. Pending activation offers
retry of the same chosen operation. A subsequent curator change blocks replay.
Application requires an administrator in the harness and the responding browser
principal's archive-management permission. Cross-principal runtime responses cannot
misattribute the correction. A non-administrator can decline or continue the current
question without modifying shared archives. The gate does not advance a workflow phase.
Memory and Evidence remain separate domains. The integration coordinator reuses their
canonical persistence and activation operations. A session Evidence correction requires
a consolidated archive, preserves source lineage, and cannot publish unrelated external
edits. Existing administration, source import, dependency cleanup and final Memory review
remain available. No automatic Git commit or push was added.
## Verification
- Harness regression: **1,264 passed**, one skipped, five deselected. All **nine**
portable-path checks passed separately without `THT_HOME`. Python lint passed on changed modules.
- Backend: **1,366 passed**, 40 skipped. Tests include actual session-response routes
for both target archives, unauthorized response, malformed choice and runtime ownership.
- Frontend: complete suite **645 passed**; the final display adjustment passed all four
focused widget tests. Backend and frontend TypeScript checks passed.
- Pi extension: **199 passed**, including closed human choices, rejection, failure/retry,
forged selections and the updated public tool schema. The modular skill projection is
byte-identical to its updated approved template.
- PostgreSQL/Qdrant integration traverses the actual Python CLI for preparation,
application and recovery inspection. Corrected Memory and Evidence are retrieved from
real indexes, and Evidence activation preserves the Memory card. Session loading is a
controlled fixture and embeddings are deterministic; this is not an LLM quality test.
- Failure tests cover both targets, index outage, replay, later edits, workspace/session
isolation, changed session context, rejection, non-admin writes, and interruption after
the Evidence file write but before its saved receipt.
- Playwright desktop/mobile: **one passed**. The real widget renders both alternatives,
accepts an Evidence choice, displays pending activation and allows retry to active.
No page errors or mobile horizontal overflow. Screenshots are
`/private/tmp/thothii-x1-repair-desktop.png` and `/private/tmp/thothii-x1-repair-mobile.png`.
This browser fixture controls operation outcomes; persistent behavior is tested above.
- Strict MkDocs build and `git diff --check` passed.
## Local installation and reviewer acceptance
Core and frontend images were rebuilt from this worktree using the existing local
preview launcher. Migration `004_archive_repairs.sql` was applied to the existing
installation catalog. The new gate is available to session workflows; it is not an
always-visible administration panel. Existing PSD archive content was not changed by
the synthetic validation cases.
All five local services are healthy at `http://127.0.0.1:8080/`.
The technical increments and their planned checks are complete. The end-user acceptance
check remains a real session containing a meaningful domain conflict, with the reviewer
evaluating the proposed correction. Automated browser validation uses temporary accounts
and data, not the user's authenticated PSD session. Source import retains its E3
validation boundaries; no broader model-quality benchmark was added.
## Follow-up acceptance: configured model
The opt-in `test_real_model_proposes_a_reviewable_persistent_archive_correction`
passed with the installation's **zai/glm-5.3** model. Synthetic Memory asserted an
order-ID-only join; synthetic Evidence required the financial year too. The model
returned two schema-valid, specific alternatives with complete content and the exact
target revisions. The test reviewer selected Memory, persisted the correction through
the real coordinator and PostgreSQL, and retrieved the updated rule. Evidence stayed
unchanged. This test uses deterministic vectors and the configured completion helper;
it does not claim a full autonomous Pi session or human acceptance of PSD semantics.
The run log is `/private/tmp/x1-acceptance-model.log`. Reproduce with
`THT_MEMORY_L2_INSTALLATION=<installation.yaml>` and `THT_MEMORY_L2_CORE=<core-container>`
using `pytest -q -s -m l2 tests/memory/test_administration.py -k real_model_proposes`.
Credentials are resolved inside core and are not returned to the test runner.
## Follow-up acceptance: both administration pages
The opt-in `frontend/e2e/memory-real.spec.ts` passed through real authentication,
Fastify, ThtRunner, Python, isolated PostgreSQL and Qdrant. It verifies:
- Database management, Memory management and Evidence management appear as peers in
that order, with no active core session or DWH binding required.
- Memory creation, editing, persistence across backend restart, deletion and absence
from subsequent recall.
- Canonical Evidence remains intact after the Memory deletion. Its full rule is read
through the real Evidence administration worker; content filtering finds it and an
unmatched filter produces the empty state.
- Requests for an unregistered workspace return 404 for both archives.
- Desktop and mobile Evidence views render without horizontal document overflow.
On phones, both archive pages have at least 380px of usable width at a 390px viewport.
Navigation opens in the shared accessible dialog, closes with Escape or archive selection,
and returns focus to the trigger after Escape.
The temporary PostgreSQL readiness probe now waits for TCP, avoiding the image's
socket-only initialization server. The browser waits for Memory refresh to finish
before leaving its page, matching the existing navigation guard. Visual inspection
also exposed a real mobile layout issue: the fixed sidebar left only 134px for the
Evidence page. `ArchiveNavigation` now moves that sidebar into the shared dialog below
768px on Memory/Evidence pages. Desktop behavior is unchanged. The frontend image
was rebuilt for the local preview.
Run log: `/private/tmp/x1-acceptance-browser7.log` (**one passed**).
Screenshots: `/private/tmp/thothii-acceptance-evidence-desktop.png` and
`/private/tmp/thothii-acceptance-evidence-mobile.png`. Reproduce with
`THT_MEMORY_BROWSER_E2E=1 npx playwright test e2e/memory-real.spec.ts` from `frontend/`.
The fixture removes its temporary containers, accounts and checkout on completion.
The TypeScript check, Python lint, strict documentation build and diff check also pass.
@@ -1,67 +0,0 @@
# Evidence E1 — validation
Date: 2026-09-09. Scope: editable Curated Evidence v4 and the persistent local archive.
## Implemented behavior
- Parser, renderer, authoring output and normalization share the existing typed payloads.
Visible Markdown edits determine content for all eight kinds. Legacy v1–v3 conversion
is explicit and lossless, with errors for content that cannot be represented exactly.
- Manual declarations record the curator. A correction preserves the original document
as lineage, separately from the current declaration. No source hash is needed to
create a manual file.
- The local archive records baselines, immutable candidates, active revisions and
deletion/source suppression metadata. Unresolved review items and invalid edits block
consolidation. Missing archive directories are availability failures, not deletions.
- Activation failures preserve the previous active revision. Interrupted normalization
replays only unchanged input bytes; later operator edits survive recovery.
- Revision-checked correction methods reject stale workflow updates. Legacy preparation
and resolution cannot overwrite an initialized local archive; explicit import/refresh
integration is deferred to E3.
## Verification
The final harness suite excluding opt-in L0/L2 and portable-layout cases passed with
**1,180 tests** (58 deselected). All **9 portable-layout tests** passed separately with
`THT_HOME` unset. The dedicated real-Qdrant integration test passed, including the
optional 35-unit PSD probe. Ruff passed on the changed Evidence implementation and
tests, and the strict documentation build succeeded. No frontend or backend TypeScript
changes are part of E1.
The integration test uses an isolated Qdrant 1.18.2 container, the actual corpus
pipeline, semantic chunking, vector adapter and active Evidence searcher. Deterministic
three-dimensional embeddings isolate file/content correctness from model behavior.
It verifies that raw edits do not change recall, consolidation updates recalled content
and curator identity, a blocked candidate preserves prior recall, and deletions remove
recall. Existing schema and Memory records survive each operation.
All **35 PSD units** were copied from the owner's workspace into
`/private/tmp/thothii-e1-psd.bsW4cp`. Deterministic conversion preserved every ID, payload,
scope, provenance and review item. There were no unresolved review items. The optional
integration probe then indexed all 35 converted units and compared their complete ID
set to the original. It uses PSD's actual `max_chunk_chars: 5000`; a preliminary probe
at 4000 correctly blocked an oversized atomic unit.
Reproduce the isolated real-corpus probe after creating a converted workspace copy:
```sh
cd harness
THT_E1_PSD_COPY=/absolute/path/to/converted-copy \
.venv/bin/pytest -q -s tests/test_evidence_editable_integration.py
```
The environment variable is optional. Ordinary CI uses only synthetic Evidence. No
source refresh, external document download, DWH call or model request is involved.
## Delivery boundary
E1 is a core/library increment. E2 must add the installed manual consolidation command,
connect runtime source selection to the active local snapshot, and build administrative
list/filter/detail with real persistent host paths and manual Git instructions. E3
adds source acquisition and explicit refresh/conflict handling. X1 later wires deliberate
joint Memory/Evidence corrections into review gates.
The actual PSD Evidence checkout was not converted. The live Docker preview at
`http://127.0.0.1:8080` remains the previously deployed M3 stack, with no new Evidence
administration page. The corpus conversion and reindexing described here used copies
and disposable test resources.
@@ -1,86 +0,0 @@
# Evidence E2 — validation
Date: 2026-09-09. E2 is implemented locally and installed on the existing Docker preview.
E3 source imports/refresh and X1 deliberate Memory/Evidence workflow corrections remain open.
## Delivered behavior
- Independent **Administration → Evidence management**, after Memory, protected by
`evidence.manage`: complete typed content, provenance and original excerpts, review items,
pagination, search, kind/purpose/status and scope/source filters, sort, and refresh.
- Working-file states distinguish active, modified, new, removed, invalid, legacy and review
required. Detail shows the actual configured host path with copy controls. Instructions
cover external editing, all eight Markdown templates, consolidation and manual Git.
- Installed `tht workspace evidence consolidate --workspace <id> [--json]` uses a closed
maintenance envelope. First use converts legacy units. Validation, immutable candidates,
activation and retry run through the existing corpus pipeline without a DWH scan or Git.
- Runtime and ordinary preprocessing consume the active local snapshot. Unconsolidated
edits remain excluded. Catalog/Schema readiness is not advanced by this operation.
Clear preserves curated files, archive metadata and Memory; full preprocessing must
recreate the missing Reference/Schema derivations afterward.
- Immutable runtime lease filenames now identify rendered bytes as well as logical input
identity. This fixes upgrades colliding with old runtime files without changing Catalog
fingerprints or removing the checks against tampered files.
## Automated checks
The complete backend suite passed: **1,355 tests**, 40 skipped. The complete frontend
suite passed: **639 tests**. Both TypeScript checks passed. Native Go workspace operation
and CLI tests passed, including rejection of arbitrary consolidation flags. Ruff passed
for changed Python implementation and test files.
The harness run passed **1,248 tests**, with one skipped and five deselected. Its three
portable-path tests failed because that run deliberately set `THT_HOME` to the test
runtime; rerunning the portable tests with `THT_HOME` unset passed. The final focused
administration/path suite passed all 18 tests, including actionable migration errors and invalid
consolidation combinations rejected before cleanup or indexing.
The real-Qdrant integration test exercised the actual harness consolidation CLI with
deterministic embeddings: all 35 PSD units converted and indexed with stable identities;
active-only source selection; separate Schema and Memory canaries; Clear and rebuild
from the retained snapshot. Unit tests cover validation, saved-but-unindexed failure,
retry, browsing/filtering, no automatic Git, and no Catalog mutation from consolidation.
A temporary Git repository and bare local remote exercise the documented manual sequence:
edit, add and remove files, consolidate, inspect, stage the complete Evidence tree,
commit, push and clone. The clone retains changed content, additions, deletions, managed
metadata and an accessible active snapshot. No remote user repository was pushed.
## Installed preview
The existing Compose project is `thothii-18998cca7b0a`, at `http://127.0.0.1:8080`.
The persistent editable checkout is:
```text
/Users/mp/projects/ThothII/deploy/psd/evidence-registry/repo/psd-clinical/evidence
```
The original registry checkout was copied from its retained Docker volume. The original
author repository was not changed. Core and maintenance share a nested host bind for
`repo`; registry state/snapshots and all other existing data volumes were retained.
The installation descriptor includes the existing workspace bindings and the new
`evidence-host.yaml` override. The previous descriptor and native binary are backed up
at `/private/tmp/thothii-installation-before-e2.yaml` and `/private/tmp/tht-before-e2`.
The real installed command succeeded with **35 documents, 35 chunks, 35 changed, zero
removed**, using the configured embedding service and Qdrant. A second run succeeded
with **35 unchanged, zero changed**. Reading the actual archive from core returned
35 active units and no file errors. All five long-running services are healthy.
Only the Evidence stage ran. The strict documentation build and `git diff --check`
also passed. The stack launcher is `bash /private/tmp/thothii-memory-preview.sh`; keep its
worktree image-build override until this branch is integrated into the main checkout.
Browser verification reached the local login page. The saved administrator password
does not match the current account hash, so the authenticated visual check remains
manual. No account or password was modified. React interaction tests cover navigation,
detail, host paths, templates, filtering, pending activation and invalid files.
## Boundaries
There is no web content editor, watcher, automatic commit/push, or implicit source refresh.
The API exposes administration reads and consolidation; the archive's revision-checked
save/remove operations remain available for the later explicit workflow corrections.
These gates are not claimed as implemented by E2. Initialized local archives retain
structural/review checks but bypass the legacy fixed retrieval-evaluation fixture so
its old expected IDs cannot veto deliberate deletions. A general retrieval benchmark
is outside the agreed scope.
@@ -1,100 +0,0 @@
# Evidence E3 — validation
Date: 2026-09-09. Explicit source import/refresh and decisions are implemented. X1,
the integration of deliberate Memory/Evidence corrections into workflow gates, remains next.
## Delivered behavior
The independent Evidence page now offers **Sources and imports**. An operator copies
a specialist's draft into `evidence/incoming/`, then explicitly imports/refreshes.
Original local Markdown and configured HTTP/S3 sources use existing read-only adapters.
Acquisition retains raw bytes, source identity and versioned normalized documents.
The existing Pi authoring refiner prepares typed, editable v4 proposals.
Unchanged hashes skip refinement. All source acquisitions/refinements must succeed
before saving a new set of comparisons. Missing sources are recorded as unavailable,
never interpreted as permission to delete. No runtime lookup, ordinary consolidation
or preprocessing triggers remote refresh once the local archive is initialized.
The administrator sees current and proposed units, scope, content, excerpts, review
items and explicit retirement IDs. **Keep local Evidence** records the retained wording
as a manual declaration with original lineage. **Use proposed Evidence** adopts the
proposal and its source version. Both save and activate through the existing archive
and corpus pipeline; review items block adoption. Comparisons use optimistic checks
on affected file bytes. Interrupted decisions have a durable replay journal and retry
without reacquisition, while intervening external edits are preserved and reported.
Deleted IDs remain reserved. New model-generated identities from sources with curated
deletions are also conservatively suppressed; surviving IDs can still receive reviewed
updates. Deliberate new knowledge can be authored as a manual file. This mechanical
protection does not depend on the model detecting semantic duplication or contradictions.
Installed commands are `workspace evidence refresh` and `workspace evidence decide`,
alongside E2 consolidation. Decision envelopes carry a source identity, comparison
revision and keep/replace choice. Extra URLs, arbitrary paths, forged actors and unknown
fields are rejected at the public API/CLI boundary. HTTP requests bind the authenticated
curator. Source operations do not mutate Catalog readiness or run DWH/schema stages.
The Python source worker is internal; the workflow CLI's visible surface is preserved.
## Checks
- Complete backend suite: **1,359 passed**, 40 skipped. Complete frontend suite:
**641 passed**. Both TypeScript checks passed; native Go CLI/workspace tests passed.
- Harness regression run: **1,256 passed**, one skipped and five deselected, with
portable-path tests run separately without `THT_HOME`. All **24 focused import,
CLI-surface and portable-path checks** passed. These
cover import, unchanged refresh, access failure, missing source, manual correction,
keep/replace, deletion suppression, stale comparisons, failure/retry and interrupted
journal writes. Ruff passed on the changed Python implementation and tests.
- The real-Qdrant test traverses the actual harness source CLI with deterministic
refinement/embedding boundaries: import is absent from recall before a decision,
accepted content becomes searchable, refreshed proposals preserve active manual
corrections, replacement removes the former text, deletion remains absent after
another refresh, and unrelated Schema/Memory canaries survive.
- Source contract fixtures cover controlled HTTP and S3 identities, exact acquired
bytes, and acquisition call counts. Existing adapter tests retain transport/egress
coverage. The test does not claim to exercise a live S3 account.
- React interaction tests verify explicit refresh, comparison content, exact decisions,
saved-decision retry and failure feedback. Route tests cover admin authorization,
workspace isolation, strict inputs and principal attribution. Service tests verify
the trusted config file descriptor and absence of Catalog mutation.
## Local preview
Core/frontend were rebuilt for the existing `thothii-18998cca7b0a` stack. Its persistent
archive and data volumes are retained. The native `/usr/local/bin/tht` was updated;
the previous executable is at `/private/tmp/tht-before-e3`.
The installed refresh command ran against `psd-clinical` successfully: **35 unchanged
sources, zero changed, zero pending comparisons**. All 35 source hashes matched their
existing units, so this probe required no refinement and changed no active Evidence.
Source registry metadata was saved locally; no Git commit or push was performed.
A separate synthetic draft was passed to the configured Pi/model inside core. It
produced one domain proposal with one review item, which was not activated. The probe
exposed a deployment issue: Python wheel modules and Pi skills live in different
directories. The refiner now resolves resources through `THT_HARNESS_DIR`, with the
source-tree location as its development fallback; a regression test covers this layout.
The corrected installed worker was then exercised end to end in a temporary workspace
inside core, using the real configured Pi/model and a synthetic `incoming/orders.md`.
It returned success, one changed source, one comparison and one proposal with a review
item. The active snapshot remained absent. The temporary directory was removed on exit;
the probe did not open the DWH or activate an index. Strict docs build and
`git diff --check` also passed.
The in-app browser still showed the login page with the prior credential error.
Authenticated visual verification remains manual; no credentials were reset or retried.
## Operational instructions and boundaries
See [Import drafts and refresh sources](../contracts/curated-evidence-v4.md#import-drafts-and-refresh-sources)
for commands, local paths, review, retry and backup/Git requirements. Preserve the
complete Evidence tree, including source comparisons, journals and acquired versions.
Local activation and transfer to the remote Git repository remain separate operator steps.
No web content editor, automatic Git, background watcher, new job queue, general
retrieval benchmark or automatic source merge was introduced. Review/refinement is
sequential and bounded by per-source limits plus 200 documents/100 MiB per refresh.
New changed-source decisions and failure scenarios use isolated test data; live PSD
curated content was kept unchanged. X1 is not included in this increment.
@@ -1,96 +0,0 @@
# M2 — Ricerca ibrida e collegamenti
Data: 2026-09-09. Implementazione locale dell'incremento M2 del
[piano Memory approvato](2026-09-08-memory-management.md#piano-esecutivo).
## Risultato
La ricerca Memory ed exemplar usa embedding dense e BM25 in Qdrant. I filtri sono
applicati in entrambi i rami prima della selezione dei candidati. Il core espande
i collegamenti uscenti, deduplica, limita cicli e visite, riordina insieme i risultati
e restituisce il contenuto corrente verificato in PostgreSQL. Non usa un database
a grafo né una chiamata LLM per il riordinamento.
Il contesto fisico distingue database, schema, tabella e colonna e richiede che
corrispondano alla stessa dipendenza strutturata. Le card senza dipendenze valgono
per il workspace; un riferimento a un antenato fisico si applica ai suoi discendenti.
Ambito descrittivo e concetti possono restringere ulteriormente la ricerca tramite
`--filters`. La CLI impedisce di sostituire database/schema del runtime. Il dettaglio
dei limiti e della formula di ranking è nel [contratto operativo](../gestione-memory.md#hybrid-recall-and-links).
L'incremento conserva i confini dei gate correnti: F2 riceve chiarimenti di dominio,
gli exemplar restano consultativi. Un collegamento non autorizza a consumare una
famiglia diversa o una card fuori ambito. Il riepilogo finale, i nuovi gate e la
pulizia delle dipendenze dopo sincronizzazione fisica appartengono a M3.
## Transizione e recupero
La migrazione versionata `002_hybrid_projection.sql` aggiunge il formato delle
proiezioni. Le card M1 restano autorevoli e consultabili; le loro proiezioni dense
risultano pendenti e non possono alimentare il recall. Un retry esplicito o
`tht memory index -c <runtime.yaml>` costruisce dense e BM25 dal contenuto corrente.
Il formato della proiezione e la revisione della card sono verificati prima dell'uso.
Il salvataggio può aggiungere il vettore sparse mancante nella sola collezione
Memory. Non sostituisce configurazioni incompatibili e non modifica Reference.
Il rebuild esplicito ricrea anche una collezione Memory assente; il test elimina
la collezione, ricostruisce da PostgreSQL e verifica il ritorno della sola card conservata.
Fallimenti lasciano il lavoro di propagazione persistito e recuperabile. Nessuna
importazione da JSONL, sessioni storiche o vecchi payload Qdrant.
## Verifiche
| Controllo | Esito |
| --- | --- |
| Suite harness senza L0/L2, escluso il file dei percorsi portabili | 1.152 test passati nell'esecuzione finale. |
| Suite mirata Memory, adapter e CLI, con embedding reale | 78 test passati. |
| Verifica aggiuntiva del rebuild con collezione assente e adapter | 54 passati; il solo test del modello reale era escluso in questa riesecuzione. |
| API Fastify Memory | 13 test passati, compresa propagazione della lingua del workspace. |
| Browser amministrativo integrato | Passato: creazione, modifica, riavvio, rilettura, cancellazione e verifica del recall. |
| Wheel e casi CLI | 10 test passati; il wheel include entrambe le migrazioni Memory. |
| Build e controlli statici | Build/typecheck backend, Ruff sui file Python interessati e build documentale strict passati. |
- Test deterministici: collegamenti necessari, contenuto corrente, duplicati,
cicli, profondità e limiti, card mancanti o pendenti, rifiuti già registrati,
famiglie e ambiti esclusi, dipendenze omonime e filtri CLI vincolati al runtime.
- Adapter: stesso filtro nei prefetch dense/BM25 per Memory ed exemplar; restano
coperti i contratti Evidence esistenti.
- PostgreSQL/Qdrant: salvataggio, modifica, cambio famiglia, cancellazione,
ricostruzione, riferimento separato, isolamento, RLS, errori, retry e transizione
delle proiezioni M1 al formato ibrido.
- Recupero effettivo: client Ollama di produzione con il modello configurato
`qwen3-embedding:0.6b`, dimensione 1024, e Qdrant dell'immagine fissata in Compose.
La domanda sulla chiave commessa fra esercizi recupera la regola attesa e la
granularità collegata, escludendo un altro database, un altro ambito e dipendenze
che corrispondono soltanto combinando riferimenti distinti. Verifica separatamente
dense, BM25 e fusione, poi recall, cancellazione e rebuild.
Il test effettivo avvia un processo Ollama separato, montando il volume del modello
installato in sola lettura. PostgreSQL e Qdrant sono container temporanei dedicati,
eliminati a fine test. Non usa ID di risultati predisposti. Il browser amministrativo
usa invece embedding deterministici: verifica il collegamento fra UI, autenticazione,
Fastify, ThtRunner e persistenza, senza essere una misura di qualità del recupero.
Non è una valutazione generale della qualità semantica su un corpus di produzione;
verifica i casi di recupero richiesti da M2, con il percorso reale configurato.
## Riproduzione
Dalla directory `harness`, con Docker disponibile:
```sh
THT_HOME=/private/tmp/thothii-m2-test-home \
THT_MEMORY_TEST_OLLAMA_VOLUME=<volume-modelli-installazione> \
THT_MEMORY_TEST_MODEL=qwen3-embedding:0.6b \
THT_MEMORY_TEST_DIMENSIONS=1024 \
.venv/bin/pytest -q tests/memory/test_administration.py \
tests/memory/test_retrieval.py tests/memory/test_recall.py \
tests/test_qdrant_vector_store.py tests/test_solved_search_cli.py
```
Senza il volume esplicito, il solo test con modello reale viene escluso; gli altri
test restano eseguibili. Il volume deve contenere il modello indicato. Le immagini
Qdrant e Ollama del test sono lette da `compose.yaml`.
Consegna locale: non sono stati eseguiti deploy, migrazioni delle installazioni
attive o aggiornamenti remoti dell'issue tracker.
@@ -1,78 +0,0 @@
# Memory M3 — validation
Implemented on 2026-09-09 in the rapid-harbor worktree.
## Delivered behavior
- F8 presents an editable summary of proposed additions, explicit updates and links.
Only selected content is saved, including the optional solved-question exemplar.
- Proposals reference effective approved decisions. Exact existing content is reused.
Concurrent edits invalidate an update; manual identities and origins are preserved.
- Selected cards and links commit atomically. Durable receipts recover repeat delivery
and the gap before the session review marker. Finalization does not add Memory.
- F4/F6/F7 retrieve SQL rules and explained errors for the existing approval gates.
Retrieval is consultative and does not write an approval decision.
- Successful Catalog physical sync deletes cards with matching removed dependencies.
The same Catalog transaction marks pending Memory cleanup. Retry keeps the original
removals and does not rescan; deletion receipts and projection tombstones survive restarts.
- Migration 003 adds minimal review and physical-cleanup receipts.
## Executed checks
| Check | Result |
| --- | --- |
| Harness deterministic suite, excluding portable-path environment cases | 1,152 passed |
| Portable-path suite without the temporary THT_HOME override | 9 passed |
| Memory service/retrieval with isolated PostgreSQL and Qdrant | 41 passed, 1 optional real-embedding case skipped, 1 L2 case excluded |
| Pi gate suite | 195 passed |
| Backend full suite | 1,346 passed; one auth timing test exceeded 5 seconds under concurrent load |
| Isolated auth and Catalog route rerun | All 40 passed, including the timed-out case |
| Catalog PostgreSQL integration after adding atomic cleanup-marker coverage | All 5 passed |
| Frontend full suite | 635 passed |
| Chromium summary review, desktop and 390px mobile | Passed; no page errors or horizontal overflow |
| Configured real GLM 5.3 generation | Passed on synthetic PostgreSQL data |
| Backend/frontend production builds, modified Python lint, strict docs build | Passed |
The existing local Docker preview was rebuilt from this worktree, migration 003
was applied, and core/frontend were recreated with the existing persistent volumes.
The preview remains at `http://127.0.0.1:8080`.
The browser check uses the production widget in an isolated Vite fixture. It edits
the rule, declines the exemplar, submits only the selected card and checks responsive
layout. Gate tests separately verify request ordering through the production Pi
composition root; service and Catalog tests use real PostgreSQL. This is not a claim
of an automated complete live Pi conversation.
The L2 case retrieves an approved SQL rule, excludes a card bound to another database,
and asks the configured GLM 5.3 model to generate a query. Order IDs repeat between
financial years; the correct composite join returns 120 on the synthetic fixture.
The generated SQL is validated and executed in a read-only PostgreSQL transaction.
No real DWH rows are sent. Embeddings in this case are deterministic; the real
embedding/hybrid retrieval evidence remains documented in M2.
## Reproduction
From the harness:
```sh
THT_HOME=/private/tmp/thothii-m1-harness-home .venv/bin/pytest -q -m 'not l0 and not l2' --ignore=tests/test_portable_paths.py
.venv/bin/pytest -q tests/test_portable_paths.py
THT_HOME=/private/tmp/thothii-m1-harness-home .venv/bin/pytest -q tests/memory/test_administration.py tests/memory/test_retrieval.py -m 'not l2'
npm test
```
The optional generation case requires an installation YAML path and its running
core container. It resolves the model credential inside core without printing it:
```sh
THT_MEMORY_L2_INSTALLATION=<installation.yaml> THT_MEMORY_L2_CORE=<core-container> \
.venv/bin/pytest -q -s -m l2 tests/memory/test_administration.py -k real_model
```
From frontend: `npx playwright test e2e/memory-review.spec.ts`.
Screenshots are written to `/private/tmp/thothii-m3-summary-desktop.png` and
`/private/tmp/thothii-m3-summary-mobile.png`.
Evidence authoring and the joint X1 persistent Memory/Evidence conflict repair remain
outside M3. This increment does not infer knowledge from unexplained failures or
promise general improvements in SQL-generation accuracy.
@@ -0,0 +1,53 @@
# Knowledge archives: consolidated implementation evidence
Consolidated on 15 September 2026 from the M1–M3, E1–E3 and X1 records of 8–9 September.
This is historical verification evidence, not a claim that these test counts describe today's
tree or that every installation has passed acceptance. Original commands, detailed outcomes and
local deployment details remain under `docs/plans/` in Git commit
`5f3a7f5975b96fae1e1cdd0da08b4d60d41064cc`.
| Increment | Delivered boundary | Recorded verification |
| --- | --- | --- |
| M1 | PostgreSQL Memory authority, administration, durable indexing retry | 1,134 harness + 9 portable cases; 17 real PostgreSQL/Qdrant cases; 190 Pi gates; 632 frontend. One backend auth timeout passed its isolated rerun. |
| M2 | Dense/BM25 recall, scoped links and explicit projection rebuild | 1,152 harness; 78 targeted cases with real embedding; 13 Fastify API cases; 10 wheel/CLI cases. |
| M3 | Human Memory promotion, receipts and physical-schema dependency cleanup | 1,152 harness + 9 portable; 41 Memory integration, 1 optional embedding skipped, 1 L2 excluded; 195 Pi gates; 635 frontend; 5 Catalog integration. Backend timing case passed isolated rerun. |
| E1 | Editable file authority and explicit consolidation | 1,180 harness + 9 portable; real Qdrant including optional 35-unit PSD probe. No actual PSD authoring checkout conversion in E1. |
| E2 | Evidence administration, host CLI and pending activation recovery | 1,355 backend, 639 frontend, 1,248 harness; portable-path rerun passed with THT_HOME unset. Initial authenticated visual acceptance remained manual. |
| E3 | Draft import, explicit source refresh and protected manual corrections | 1,359 backend, 641 frontend, 1,256 harness; 24 focused checks. Synthetic filesystem/HTTP/S3 behavior, not a live S3-account certification. |
| X1 | Human-reviewed Memory/Evidence conflict correction and durable recovery | 1,264 harness + 9 portable; 1,366 backend, 645 frontend, 199 Pi gates; desktop/mobile widget probe and later real authenticated administration probe. |
X1's configured `zai/glm-5.3` probe used synthetic conflicting knowledge. It was not a full
autonomous Pi-session certification or acceptance of PSD semantics. Later administration
checks exercised real authentication and a disposable synthetic workspace, including persistence
across backend restart. Existing PSD archive content was not rewritten by those probes.
## Current authorities and repeatable checks
- [Memory implementation and storage](../gestione-memory.md), ADR 0018.
- [Editable Evidence](../contracts/curated-evidence-v4.md), ADR 0019.
- [Session corrections](../contracts/archive-repair.md).
- [Evidence lifecycle acceptance](../testing/evidence-lifecycle-test-plan.md).
- Harness tests under `tests/memory/`, Evidence integration tests, backend route/service tests,
Pi gate tests and frontend tests are the executable regression boundaries. Run portable-path
tests without a THT_HOME override; optional L2/provider tests require separate authorization.
Do not equate passing synthetic tests with real-provider quality, live S3 coverage, PSD-domain
acceptance or permission to migrate another installation. Those remain explicit operator gates.
## Catalog/model design records consolidated at the same time
The old metadata-catalog exploration and description-generation plan/spec are no longer
current-state authorities. PostgreSQL/Fastify ownership, installation bindings, description
generation and source-sampling constraints now live in ADRs 0001–0016,
[architecture](../architecture/overview.md), [Database Management](../operations/database-management.md)
and the [description acceptance checklist](../testing/2026-08-29-ai-catalog-description-generation-acceptance.md).
The installation-model plan is replaced by ADR 0013 and the
[current model catalog guide](../general/pi-configuration.md).
Preserve these outstanding questions rather than infer acceptance from implementation:
real provider/DWH acceptance recorded by the owner, licensing review of shipped optional models,
future dialect/multi-schema support, and any desired semantic aliases/value descriptions.
The former proposed catalog-to-core cutover and separate database permissions are already
represented by current ADR 0016 and `database.manage`; do not reopen them as unfinished steps.
The original description-generation plan referred to issue #4 for final delivery gates;
this consolidation neither changes nor closes its tracker state.
@@ -1,420 +0,0 @@
# Inventario delle funzionalità legacy di gestione metadati di ThothAI
Data dell'inventario: 2026-08-23
Ultima verifica rispetto a ThothII: 2026-08-31
Stato: **inventario storico verificato; non è una specifica dello stato corrente**.
Issue originaria: [mptyl/ThothII#6](https://git.tylconsulting.it/mptyl/ThothII/issues/6)
Fonte primaria: repository legacy annidato `Thoth/ThothAI`, commit
`55855de0f18e5cb4bc72f0a2ab0a7317995186dd`.
Le citazioni che iniziano con `/Thoth/ThothAI/` sono relative alla radice di quella copia legacy
fissata al commit indicato. Alla verifica del 2026-08-31 tutti i 68 riferimenti univoci puntavano a
file esistenti e a intervalli di righe validi.
> Questo documento conserva l'inventario e le evidenze degli anti-pattern di ThothAI. Le frasi di
> requisito nelle sezioni successive descrivono la baseline proposta il 2026-08-23; non prevalgono
> su ADR, contratti, codice o `PROJECT_STATE.md` correnti.
## Stato rispetto al codice corrente
### Fonti autorevoli correnti
Per capire cosa esiste oggi, usare nell'ordine:
- `PROJECT_STATE.md`, per lo snapshot operativo aggiornato;
- `CONTEXT.md`, per il modello di dominio corrente;
- il [piano accettato del Metadata Catalog](../plans/2026-08-26-metadata-catalog-from-thothai.md)
e il [contratto dello schema snapshot](../contracts/catalog-schema-snapshot.md);
- gli ADR del catalogo, in particolare
[0001](../adr/0001-postgres-metadata-catalog.md),
[0004](../adr/0004-fastify-kysely-metadata-catalog.md),
[0006](../adr/0006-separate-physical-and-logical-relationships.md),
[0007](../adr/0007-durable-authoritative-schema-synchronization.md),
[0008](../adr/0008-allow-manual-catalog-metadata-cleanup.md),
[0009](../adr/0009-use-one-sequential-description-generation-run.md),
[0010](../adr/0010-allow-bounded-real-source-samples-for-description-generation.md) e
[0011](../adr/0011-gate-source-samples-with-a-sensitive-data-flag.md);
- l'implementazione, soprattutto `backend/src/catalog/types.ts`,
`backend/src/routes/catalog-databases.ts`, `backend/src/routes/catalog-schema.ts`,
`backend/src/routes/catalog-description-generation.ts`,
`backend/src/routes/catalog-description-consolidation.ts` e
`frontend/src/shell/DatabaseManagementPage.tsx`.
### Disposizione della baseline storica
| Capacità inventariata | Disposizione al 2026-08-31 | Evidenza corrente o nota |
|---|---|---|
| Configurazione per workspace, secret separati e test connessione | **Adottata e implementata** | PostgreSQL interno, binding `postgres_direct`, `rest_api` o `ssh_tunnel`, secret write-only; ADR [0001](../adr/0001-postgres-metadata-catalog.md)–[0004](../adr/0004-fastify-kysely-metadata-catalog.md) e `backend/src/routes/catalog-databases.ts`. |
| Inventario fisico di tabelle, colonne, PK e FK | **Adottato e implementato** | La struttura osservata è distinta dai contenuti curati; `backend/src/catalog/types.ts`, `backend/src/routes/catalog-schema.ts` e ADR [0006](../adr/0006-separate-physical-and-logical-relationships.md). |
| Riconciliazione completa e osservabile dello schema | **Implementata; semantica storica parzialmente superata** | I durable Catalog Sync Runs sono autoritativi, fail-closed, atomici e richiedono conferma per diff distruttivi. Non esiste il lifecycle `drift/removed` proposto qui: la sincronizzazione riconcilia la membership e ADR [0008](../adr/0008-allow-manual-catalog-metadata-cleanup.md) consente anche cleanup manuale esplicito, superando ADR-0005. |
| Descrizione curata e Generated Description separate | **Adottata e implementata** | Tabelle e colonne espongono entrambi i campi in `backend/src/catalog/types.ts`; la copia selettiva AI → curato è in `backend/src/routes/catalog-description-consolidation.ts`. |
| Generazione AI selettiva con run, stato e log | **Adottata e implementata** | Un run asincrono installazione-wide, sequenziale, con eventi persistiti e senza resume automatico; ADR [0009](../adr/0009-use-one-sequential-description-generation-run.md) e `backend/src/routes/catalog-description-generation.ts`. I thread daemon legacy sono **esclusi**. |
| Campioni reali per la generazione e protezione dei campi sensibili | **Implementata come estensione correttiva** | ADR [0010](../adr/0010-allow-bounded-real-source-samples-for-description-generation.md) e [0011](../adr/0011-gate-source-samples-with-a-sensitive-data-flag.md): campioni bounded per colonne non sensibili e valori sintetici deterministici per quelle sensibili. |
| Proposte persistenti, diff e versioni dei testi AI | **Escluse** | Resta un solo Generated Description modificabile. La cronologia dei run è operativa: non conserva prompt, output grezzo, proposta per colonna o audit della decisione umana. |
| Relazioni fisiche | **Adottate e implementate** | Sono constraint immutabili con coppie ordinate di colonne; ADR [0006](../adr/0006-separate-physical-and-logical-relationships.md). |
| Relazioni logiche curate o inferite | **Differite/aperto** | ADR-0006 riserva un modello e un lifecycle separati; non sono ancora parte del catalogo corrente. |
| Alias semantici, descrizioni dei valori, sinonimi e concetti | **Differiti/aperti** | `PROJECT_STATE.md` li assegna a slice dedicate; non vanno dedotti dai campi fisici già implementati. |
| Scope AI, ERD Mermaid e documentazione aggregata | **Differiti/aperti** | Restano capacità legacy inventariate, non una feature corrente del Metadata Catalog. Un eventuale lavoro dovrà avere contratto e gate propri. |
| Export CSV e script SQL dei commenti | **Differiti/aperti; varianti insicure escluse** | Non risultano nella API corrente. Export di segreti, CSV incoerenti e SQL ricostruito da tipi incompleti restano vietati dagli anti-pattern sotto. |
| Inferenza euristica e validazione di relazioni candidate | **Differita/aperta** | Non va confusa con la sincronizzazione delle FK fisiche; dipende dal futuro lifecycle delle relazioni logiche. |
| Pubblicazione del catalogo al core/schema-linking/Qdrant | **Differita e richiesta come design gate successivo** | Il runtime NL→SQL continua a usare configurazione e annotations del workspace; il cutover è esplicitamente rinviato in `PROJECT_STATE.md`. |
| Sette motori database del legacy | **Baseline superata** | La prima versione corrente supporta PostgreSQL; l'aggiunta di altri dialetti è una decisione futura, non parità automatica. |
| GDPR e import da installazioni ThothAI | **Fuori dalla baseline iniziale; import differito** | GDPR resta escluso. Un eventuale import richiede una iniziativa idempotente e un cutover separati, non il riuso degli ID Django. |
| Django Admin, modifica manuale della struttura sorgente, password nel catalogo, duplicazione opaca dei database | **Esclusi** | La UI e le API correnti amministrano il catalogo e non eseguono DDL sul DWH esterno; binding e segreti hanno ownership separata. |
## Sintesi storica
La baseline di parità proposta il 2026-08-23 comprende un catalogo dei database, l'inventario
completo di tabelle, colonne e relazioni fisiche, metadati descrittivi modificabili, relazioni
logiche, introspezione dello schema, generazione AI di descrizioni, scope, ERD Mermaid,
documentazione ed esportazioni operative. La UI Django Admin è soltanto l'interfaccia legacy:
non è un requisito architetturale da riprodurre.
Il flusso AI per le descrizioni individuato come baseline è volutamente semplice:
1. l'AI scrive nel campo `generated_comment` della tabella o colonna;
2. l'utente seleziona gli elementi desiderati;
3. un'azione copia il testo generato nel campo descrittivo canonico, sovrascrivendolo.
Nel codice esaminato **non esiste un'azione inversa che copi la descrizione canonica in
`generated_comment`**. Le due azioni inverse presenti sulle colonne copiano invece il nome
originale nel nome espanso e viceversa. Non risultano proposal, versioni, diff o workflow di
approvazione: aggiungerli non sarebbe parità con ThothAI.
La parità deve essere di capacità e comportamento utile, non dei difetti del legacy. In
particolare non vanno replicate esportazioni di password, SQL costruito con identificatori non
quotati, job in thread daemon, incoerenze CSV e azioni admin non funzionanti.
## 1. Modello dati legacy
### Database
`SqlDb` contiene:
- identità e connessione (`name`, host, tipo motore, nome database, porta, schema, utente e
password);
- configurazione SSH e ambiente (`dev`, `test`, `prod`);
- lingua per database, scope testuale e JSON, ERD Mermaid, direttive e report GDPR;
- stato e log separati per generazione commenti tabella/colonna, introspezione, scope, ERD,
documentazione e GDPR.
Evidenze: `/Thoth/ThothAI/backend/thoth_core/models.py:298-350`,
`/Thoth/ThothAI/backend/thoth_core/models.py:355-439`.
I motori dichiarati sono sette: Informix, MariaDB, MySQL, Oracle, PostgreSQL, SQL Server e
SQLite (`/Thoth/ThothAI/backend/thoth_core/models.py:116-123`).
Il modello `Workspace` collega un solo `SqlDb`; contiene inoltre configurazioni degli agenti e
duplica parte degli stati operativi (`/Thoth/ThothAI/backend/thoth_core/models.py:731-754`,
`/Thoth/ThothAI/backend/thoth_core/models.py:847-877`).
### Tabelle e colonne
`SqlTable` contiene nome, descrizione canonica, commento generato e riferimento al database
(`/Thoth/ThothAI/backend/thoth_core/models.py:454-465`).
`SqlColumn` contiene nome originale, nome espanso, formato dati normalizzato, descrizione
canonica, commento generato, descrizione dei valori e indicazioni PK/FK testuali
(`/Thoth/ThothAI/backend/thoth_core/models.py:468-485`).
La cancellazione usa le cascade Django: database → tabelle → colonne e relazioni. Nei modelli
non sono definite unicità composte per database/tabella/colonna. Ogni database ha un solo
campo `schema`; tabelle e colonne non portano una propria identità di schema. Questi limiti
non devono diventare vincoli del nuovo catalogo.
### Relazioni
`Relationship` collega tabella e colonna sorgente a tabella e colonna destinazione
(`/Thoth/ThothAI/backend/thoth_core/models.py:488-503`). Il metodo
`update_pk_fk_fields` ricostruisce stringhe descrittive PK/FK sulle colonne coinvolte
(`/Thoth/ThothAI/backend/thoth_core/models.py:505-526`).
Il form admin verifica che le due tabelle appartengano allo stesso database e che ogni colonna
appartenga alla tabella selezionata
(`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_relationship.py:19-165`).
Per ThothII occorre distinguere:
- struttura fisica importata dal database, da trattare come inventario non modificabile;
- descrizioni e metadati semantici, modificabili con CRUD completo;
- relazioni logiche aggiunte dall'utente, modificabili con CRUD completo.
Questa separazione conserva le capacità utili del legacy senza permettere che la UI alteri o
falsifichi accidentalmente lo schema fisico osservato.
## 2. Capacità esposte dal Django Admin
La registrazione degli admin fornisce il CRUD Django standard per database, tabelle, colonne e
relazioni (`/Thoth/ThothAI/backend/thoth_core/admin.py:13-24`). Oltre al CRUD, le azioni
specifiche sono le seguenti.
### Database
L'admin di `SqlDb` espone:
- export/import CSV;
- scansione delle tabelle, creazione delle relazioni e introspezione completa;
- validazione FK e inferenza di relazioni candidate;
- test della connessione;
- duplicazione della configurazione;
- generazione commenti AI per tabelle e colonne;
- generazione sincrona o asincrona di scope, ERD e documentazione;
- report GDPR;
- export della struttura e script SQL dei commenti.
Evidenza: `/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:237-274`.
I campi di configurazione, contenuto e stato sono esposti in fieldset distinti
(`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:275-462`). In ThothII i segreti di
connessione non devono essere campi ordinari del catalogo né essere restituiti dalle API di
gestione metadati.
### Tabelle
L'admin delle tabelle permette export/import CSV, introspezione colonne, validazione e pulizia
PK/FK, copia del commento AI nella descrizione, generazione commenti sincrona/asincrona e
download dello script SQL (`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqltable.py:133-150`).
L'azione di applicazione AI opera solo sulle righe selezionate, ignora commenti generati vuoti
e sovrascrive direttamente `description`
(`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqltable.py:165-190`).
### Colonne
L'admin delle colonne permette export/import CSV, copia del commento AI nella descrizione,
copia bidirezionale fra nome originale e nome espanso, validazione FK e generazione AI sulle
colonne selezionate (`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqlcolumn.py:167-196`).
L'applicazione AI sovrascrive `column_description` soltanto per le colonne selezionate con
`generated_comment` valorizzato
(`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqlcolumn.py:219-245`). Le azioni inverse riguardano
esclusivamente i due campi nome
(`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqlcolumn.py:247-261`).
### Relazioni
L'admin delle relazioni offre il CRUD standard, filtri e ricerca, export/import e dichiara
un'azione per aggiornare i campi PK/FK
(`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_relationship.py:219-245`). L'ultima azione è però un
difetto: `RelationshipAdmin` non implementa il metodo dichiarato; esiste soltanto il metodo
statico sul model. La capacità da conservare è la ricostruzione coerente degli indicatori, non
l'azione admin rotta.
## 3. Introspezione e riconciliazione dello schema
Il factory del database manager associa i sette motori ai rispettivi adapter e gestisce anche
il tunnel SSH (`/Thoth/ThothAI/backend/thoth_core/dbmanagement.py:77-227`).
L'introspezione legacy:
- riduce i tipi nativi a un insieme generico limitato, con fallback `VARCHAR`
(`/Thoth/ThothAI/backend/thoth_core/dbmanagement.py:237-266`);
- legge e crea le colonne mancanti, ma non aggiorna tipo o commento di quelle già note
(`/Thoth/ThothAI/backend/thoth_core/dbmanagement.py:269-351`);
- crea le tabelle mancanti e aggiorna la descrizione di quelle esistenti
(`/Thoth/ThothAI/backend/thoth_core/dbmanagement.py:431-481`);
- legge le foreign key fisiche, crea al bisogno colonne mancanti e crea relazioni assenti
(`/Thoth/ThothAI/backend/thoth_core/dbmanagement.py:484-641`);
- esegue l'intero processo nell'ordine tabelle → colonne → relazioni
(`/Thoth/ThothAI/backend/thoth_core/dbmanagement.py:718-891`).
Il comportamento è additivo: gli oggetti scomparsi dal database sorgente non sono eliminati o
marcati come obsoleti. Per ThothII la capacità richiesta è una scansione completa e
riconciliabile, con provenienza, data dell'osservazione e stato degli oggetti rimossi o cambiati;
non la semantica incompleta del refresh legacy.
L'inferenza di relazioni candidate combina convenzioni sui nomi, individuazione euristica delle
PK e campionamento dei valori, con soglia di corrispondenza del 70%
(`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:605-801`). Deve rimanere una proposta
esplicita da convalidare, non essere confusa con una foreign key fisica.
## 4. Generazione AI delle descrizioni
### Tabelle
La generazione procede in batch da dieci, utilizza lingua e contesto del database, schema
osservato, descrizioni disponibili e fino a cinque righe di esempio; il risultato JSON aggiorna
soltanto `table.generated_comment`
(`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/create_table_comments.py:181-267`,
`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/create_table_comments.py:269-482`).
### Colonne
La generazione usa contesto della tabella, nomi delle colonne selezionate ed esempi reali, e
scrive soltanto `column.generated_comment`
(`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/create_column_comments.py:185-280`,
`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/create_column_comments.py:283-487`).
Provider e modello AI provengono dalla configurazione globale del backend; la lingua è invece
per database (`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/comment_generation_utils.py:156-219`).
### Comportamento di parità proposto il 2026-08-23
Il contratto funzionale minimo è esattamente questo:
- due campi per elemento: descrizione canonica e commento generato;
- comando AI su elementi selezionati o su un insieme più ampio esplicitamente scelto;
- scrittura/sovrascrittura del solo commento generato;
- azione separata, su selezione dell'utente, che copia il generato nel canonico;
- nessuna proposta persistente aggiuntiva, diff obbligatorio, approvazione multilivello o
versionamento del testo.
Non è stata trovata una copia `descrizione → commento generato`. Se si desiderasse in futuro,
sarebbe una nuova capacità, non parità legacy.
## 5. Scope, Mermaid e documentazione
### Scope
La generazione dello scope raccoglie nomi e descrizioni di tabelle e colonne, nome e lingua del
database, richiede JSON al modello e persiste sia il JSON sia una resa Markdown. Se il JSON non
è valido conserva il testo grezzo e svuota `scope_json`
(`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/create_db_scope.py:25-163`).
### ERD Mermaid
L'ERD è prodotto dall'AI usando tabelle, colonne, PK/FK e relazioni. Il codice estrae un blocco
Mermaid oppure usa la risposta grezza e la salva in `SqlDb.erd`, senza una validazione
sintattica o semantica preventiva
(`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/generate_db_erd.py:18-145`).
Il rendering attivo usa un servizio Mermaid locale: health check, POST a `/svg` o `/png`,
timeout e file temporanei
(`/Thoth/ThothAI/backend/thoth_ai_backend/mermaid_utils.py:29-72`,
`/Thoth/ThothAI/backend/thoth_ai_backend/mermaid_utils.py:281-464`). Il servizio Express espone
`/health`, `/svg` e `/png` ed applica un limite al body
(`/Thoth/ThothAI/docker/mermaid-service/src/server.js:13-23`,
`/Thoth/ThothAI/docker/mermaid-service/src/server.js:70-115`). È distribuito come servizio
Compose autonomo con health check
(`/Thoth/ThothAI/docker-compose.yml:184-201`).
La vista ERD renderizza il Mermaid memorizzato in SVG tramite tale servizio e poi elimina il
file temporaneo (`/Thoth/ThothAI/backend/thoth_ai_backend/views.py:227-288`).
### Documento del database
La documentazione HTML combina scope, relazioni, tabelle e colonne. Per le descrizioni usa il
campo canonico e, quando vuoto, il commento generato
(`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/generate_db_documentation.py:35-188`).
Il processo di generazione chiede all'AI il diagramma Mermaid, lo salva nell'ERD e costruisce
deterministicamente l'HTML corrente; l'HTML generato non incorpora il diagramma
(`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/generate_db_documentation.py:767-922`). La UI espone il
documento del database del workspace corrente e le route per HTML/PDF ed ERD/PDF
(`/Thoth/ThothAI/backend/thoth_ai_backend/views.py:86-179`,
`/Thoth/ThothAI/backend/thoth_ai_backend/urls.py:179-188`).
Per la parità ThothII servono quindi contenuto documentale strutturato, sorgente Mermaid
persistita e rendering locale. La validazione Mermaid prima della pubblicazione è una
correzione necessaria, non un cambiamento di scopo.
## 6. Export e comandi operativi
Le capacità utili rilevate sono:
- CSV di tabelle e colonne selezionate
(`/Thoth/ThothAI/backend/thoth_core/utilities/utils.py:150-195`);
- export CSV generico dei modelli e della struttura completa
(`/Thoth/ThothAI/backend/thoth_core/utilities/utils.py:198-261`,
`/Thoth/ThothAI/backend/thoth_core/utilities/utils.py:399-569`);
- script SQL dei commenti per PostgreSQL, SQL Server, MySQL e MariaDB
(`/Thoth/ThothAI/backend/thoth_core/admin_utils/sql_comment_script.py:25-316`);
- comandi `generate_scope` e `generate_documentation`, indirizzabili per workspace, database o
tutti i database (`/Thoth/ThothAI/backend/thoth_core/management/commands/generate_scope.py:12-89`,
`/Thoth/ThothAI/backend/thoth_core/management/commands/generate_documentation.py:12-89`);
- export globale e per workspace
(`/Thoth/ThothAI/backend/thoth_core/management/commands/export_models.py:19-64`,
`/Thoth/ThothAI/backend/thoth_core/management/commands/export_workspace.py:19-96`);
- aggiornamento delle descrizioni colonna da CSV per workspace
(`/Thoth/ThothAI/backend/thoth_ai_backend/management/commands/update_column_descriptions.py:24-92`).
I comandi di importazione dalla produzione legacy sono deliberatamente esclusi da questo
inventario di parità: costituiscono un progetto CLI `tht` separato, con mapping, validazione e
cutover propri.
## 7. Esecuzioni asincrone e stato
Scope, ERD, documentazione e commenti possono essere lanciati in background. Il legacy crea
thread daemon e memorizza task id, stato e log sui modelli
(`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/async_scope.py:21-62`,
`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/async_erd.py:21-30`,
`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/async_documentation.py:21-30`,
`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/async_table_comments.py:39-87`).
La capacità da mantenere è: operazione lunga non bloccante, esclusione di esecuzioni
confliggenti, stato osservabile e log. Il meccanismo daemon non è durevole, non permette
recovery o cancellazione e non deve essere replicato letteralmente.
## 8. Difetti e comportamenti da non replicare
| Area | Evidenza legacy | Decisione di parità |
|---|---|---|
| Segreti negli export | L'export struttura include `password` in chiaro (`/Thoth/ThothAI/backend/thoth_core/utilities/utils.py:516-559`); l'export generico serializza tutti i campi (`/Thoth/ThothAI/backend/thoth_core/management/commands/export_models.py:19-64`). | Escludere sempre credenziali e segreti. |
| SQL euristico | Il campionamento per inferire FK interpola identificatori senza quoting e usa query costruite come stringhe (`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:697-750`). | Usare adapter sicuri, quoting per dialetto e query read-only con limiti. |
| Refresh incompleto | L'introspezione aggiunge oggetti ma non riconcilia rimozioni e non aggiorna tutte le proprietà (`/Thoth/ThothAI/backend/thoth_core/dbmanagement.py:318-351`, `/Thoth/ThothAI/backend/thoth_core/dbmanagement.py:459-481`). | Scansioni versionate e stato drift/removed. |
| Relazioni denormalizzate | L'aggiornamento ricostruisce stringhe PK/FK solo sulle colonne partecipanti e non ripulisce esplicitamente valori obsoleti (`/Thoth/ThothAI/backend/thoth_core/models.py:505-526`). | Derivare indicatori dalle relazioni correnti. |
| Azione relazione rotta | L'admin dichiara `update_pk_fk_fields` ma non lo implementa (`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_relationship.py:219-245`). | Esporre un comando applicativo testato. |
| CSV colonne incoerente | L'header ha sei colonne, le righe ne emettono otto incluse PK/FK (`/Thoth/ThothAI/backend/thoth_core/utilities/utils.py:165-195`). | Contratto CSV versionato e testato. |
| Comando export rotto | `export_single_model` usa l'app label `toth_be` e un attributo non definito (`/Thoth/ThothAI/backend/thoth_core/management/commands/export_single_model.py:19-67`). | Non portare il comando; sostituirlo con export catalogo coerente. |
| Batch AI colonne | Un batch usa la prima tabella selezionata come contesto per tutte le colonne (`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/create_column_comments.py:230-268`). | Raggruppare sempre per database e tabella. |
| Contesto AI tabelle | Il fallback al commento generato è calcolato ma non incluso nel dataframe finale (`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/create_table_comments.py:485-525`). | Costruire il contesto dal valore effettivamente visibile. |
| Duplicazione DB | Duplica configurazione e vector DB, azzerando soltanto una data di aggiornamento (`/Thoth/ThothAI/backend/thoth_core/admin_models/admin_sqldb.py:514-569`). | Se mantenuta, duplicare soltanto metadati esplicitamente scelti, mai segreti o stati. |
| Scope non JSON | Il generatore intercetta internamente il JSON non valido, mentre l'azione può comunque mostrare successo (`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/create_db_scope.py:92-192`). | Stato `failed` o `needs_review`, senza falso successo. |
| Mermaid non validato | La risposta AI viene salvata dopo la sola estrazione del blocco (`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/generate_db_erd.py:88-121`). | Validare prima di pubblicare e conservare l'errore di rendering. |
| HTML non escapato | I valori descrittivi sono interpolati direttamente nell'HTML (`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/generate_db_documentation.py:35-188`). | Escape/sanitizzazione obbligatori. |
| Job daemon | I task sono thread in-process (`/Thoth/ThothAI/backend/thoth_core/thoth_ai/thoth_workflow/async_scope.py:21-62`). | Esecuzione durevole o almeno recuperabile, con idempotenza. |
| Script MySQL | I commenti colonna usano `ALTER ... MODIFY COLUMN` basandosi sul tipo normalizzato (`/Thoth/ThothAI/backend/thoth_core/admin_utils/sql_comment_script.py:244-316`). | Generare solo con tipo nativo completo oppure bloccare l'export rischioso. |
Il modulo contiene anche un helper verso `mermaid.ink`, ma non risultano call site nel tree
legacy; il percorso attivo è il servizio locale. Non va quindi considerata una dipendenza
funzionale da conservare.
## 9. Baseline di parità proposta il 2026-08-23
### Necessario
- database gestiti derivati dalla configurazione dei workspace;
- inventario completo del database, anche oltre il sottoinsieme usato dal workflow core;
- scansione di tabelle, colonne, chiavi e relazioni fisiche con drift osservabile;
- descrizioni canoniche e commenti AI separati per tabelle e colonne;
- generazione AI selettiva e copia selettiva AI → descrizione, senza workflow aggiuntivo;
- CRUD dei metadati semantici e delle relazioni logiche;
- scope strutturato, documento generale strutturato ed ERD Mermaid;
- servizio locale per validazione/rendering Mermaid in SVG/PNG/PDF;
- test connessione, inferenza/validazione delle relazioni e operazioni lunghe con stato/log;
- export CSV sicuro e script SQL commenti solo quando il dialetto può essere ricostruito senza
perdita;
- API e UI dedicate, separate dal workflow NL→SQL;
- pubblicazione esplicita e controllata del sottoinsieme utile al core/Qdrant.
### Estensione, non requisito di parità iniziale
- analisi e report GDPR;
- importazione dei dati da installazioni ThothAI in produzione, da progettare come iniziativa
CLI `tht` separata.
### Esplicitamente escluso
- replica del Django Admin o del suo modello di permessi implicito;
- modifica manuale della struttura fisica osservata;
- memorizzazione o export di password nel catalogo;
- dipendenza del workflow SQL dalla disponibilità della nuova UI;
- proposal/versioning/diff per i commenti AI;
- copia descrizione → commento AI, non presente nelle fonti;
- difetti tecnici e comportamenti insicuri elencati sopra.
## Conclusione
ThothAI fornisce già il perimetro funzionale di un catalogo metadati, ma lo realizza mescolando
configurazione di connessione, inventario fisico, contenuti semantici, job e UI nel modello
Django. ThothII può raggiungere la parità separando questi aspetti e mantenendo invariato il
workflow SQL: il catalogo gestisce l'intero database, mentre il core continua a consumare
soltanto il sottoinsieme dichiarato dal workspace e pubblicato nel proprio indice Qdrant.
La regola di Q6 è semplice e verificata nelle fonti: generazione nel campo AI, selezione
esplicita dell'utente, copia nel campo canonico. Ogni meccanismo ulteriore sarebbe una nuova
funzionalità e non è necessario per la parità.
@@ -1,206 +0,0 @@
# PostgreSQL deployment and failure-isolation constraints for the Metadata Catalog
Original research: 2026-08-23
Last verified against the repository: 2026-08-31
Issue: [#8 — Assess PostgreSQL deployment and failure-isolation constraints](https://git.tylconsulting.it/mptyl/ThothII/issues/8)
> **Status: partially superseded by ADR-0004.** This is historical research, not the current
> architecture contract. The recommendation to run a separate `catalog-api` process was rejected
> by [ADR-0004](../adr/0004-fastify-kysely-metadata-catalog.md). Operational constraints that do
> not depend on that process boundary remain useful, but the status table below is authoritative
> for what the 2026-08-31 code actually adopts, rejects, or leaves pending.
## Question
How can a Metadata Catalog PostgreSQL service be deployed, backed up, diagnosed, and made
non-blocking for the NL→SQL workflow across local and server installations?
## Current decision and implementation
[ADR-0001](../adr/0001-postgres-metadata-catalog.md) selects PostgreSQL as the catalog authority.
[ADR-0003](../adr/0003-installation-local-database-bindings.md) keeps database bindings
installation-local. [ADR-0004](../adr/0004-fastify-kysely-metadata-catalog.md) then places the
catalog in the existing Fastify backend as an isolated Kysely module rather than a microservice.
The implemented dependency graph is therefore:
```mermaid
flowchart LR
UI[Frontend]
Core[Core: Fastify workflow and catalog modules]
Pi[Pi and tht workflow]
PG[(catalog-db PostgreSQL)]
Semantic[Qdrant and embedding]
UI --> Core
Core --> Pi
Core --> PG
Core --> Semantic
```
The catalog module is isolated behind a repository interface, but it is not process-isolated.
`core` owns the catalog pool and Compose waits for `catalog-db` health before starting `core`
(`compose.yaml`, `backend/src/app.ts`, `backend/src/catalog/repository.ts`). Database-management records still do
not feed the NL→SQL handoff, so a running workflow does not read catalog rows; that cutover remains
future work (`PROJECT_STATE.md`).
## Verification result
| Area | Status on 2026-08-31 | Evidence and consequence |
| --- | --- | --- |
| PostgreSQL as canonical catalog store | **Adopted** | ADR-0001 is implemented by the PostgreSQL-backed Kysely repository and the internal `catalog-db` service. |
| One Workspace Database per workspace and installation-local bindings | **Adopted** | ADR-0003 and the catalog migrations enforce the model; workspace identity remains in the workspace registry. |
| Separate `catalog-api` process | **Superseded/rejected** | ADR-0004 explicitly chooses an isolated module inside the existing Fastify process. There is no catalog microservice or separate catalog liveness endpoint. |
| No catalog pool, credential, or Compose dependency in `core` | **Superseded/rejected** | `core` owns the runtime pool, receives the runtime password secret, and declares `depends_on: catalog-db: service_healthy`. The old process-level isolation acceptance criterion is not current architecture. |
| Private PostgreSQL service and durable volume | **Adopted** | `catalog-db` uses a version-and-digest-pinned PostgreSQL 17.6 image, the private `thothii` network, no published host port, a `pg_isready`-based healthcheck, and `catalog-data`. Compose contract tests assert this topology (`scripts/test-default-compose.sh`). |
| Different local and server database topology | **Superseded/rejected** | Both current profiles inherit the same internal `catalog-db` and named volume. `deploy/compose.server.yaml` does not replace it with an operator-provided endpoint. |
| Runtime and migrator role separation | **Adopted** | `core` receives only `thothii_catalog_runtime`; the profile-gated `catalog-migrate` job receives only `thothii_catalog_migrate`. Bootstrap grants runtime DML/sequence privileges without DDL (`docker/catalog-db-init.sql`). |
| Dedicated backup role | **Pending** | There is no `catalog_backup` role or backup secret. |
| Explicit one-shot migrations | **Adopted** | `backend/src/catalog/migrate.ts` registers ordered Kysely migrations and uses a pool of one. `scripts/run-stack.sh` starts PostgreSQL and runs `catalog-migrate` before local startup; migrations are not hidden in backend startup. |
| Checksums, drift/pending refusal, and migration readiness | **Pending** | No repository-owned checksum policy, drift report, or readiness gate exists for catalog migrations. The backend can start without checking the Kysely migration head when launched outside the local helper. |
| Bounded runtime connection pool | **Adopted** | `backend/src/catalog/repository.ts` sets `max: 5` and `connectionTimeoutMillis: 3000`, and closes Kysely with the Fastify lifecycle. |
| `lock_timeout`, `statement_timeout`, and catalog TLS policy | **Pending** | The runtime pool does not set query or lock timeouts. Catalog connection configuration has no explicit CA/hostname-verification contract; the server profile still uses the private Compose network. |
| Process liveness independent of catalog queries | **Adopted** | `GET /health` returns `{status: "ok"}` without probing PostgreSQL, and `backend/test/health.test.ts` preserves that behavior. `/catalog/status` performs the catalog-specific availability check. |
| Stack startup independent of catalog availability | **Superseded/rejected** | Compose blocks `core` on healthy `catalog-db`. The host service health fold does not list `catalog-db`, but it cannot make `core` start while its Compose dependency is unhealthy. |
| Uniform catalog-outage response (`503` plus `Retry-After`) | **Pending** | Routes map the domain `CatalogUnavailableError` to a sanitized `503`, and an omitted catalog configuration uses an unavailable repository. PostgreSQL driver failures are not uniformly translated to that domain error, and no `Retry-After` contract is implemented. |
| Catalog-specific readiness and `tht doctor` checks | **Pending** | There is no `/health/ready` for the catalog and no doctor section for connection, migration head, pool saturation, backup age, or publication lag (`tools/tht/internal/doctor/report.go`). |
| Logical catalog backup and restore | **Pending** | Current backup archives omit `catalog-data` and do not run `pg_dump`; server archives include only Qdrant and embedding volumes. A backup can therefore succeed without preserving the Metadata Catalog (`tools/tht/internal/backup/create.go`). |
| Last-good Publication and catalog-to-Qdrant cutover | **Pending** | The current database-management slice does not change the NL→SQL runtime or publish catalog metadata to Qdrant. |
## Current operational contract
### Deployment and credentials
- Local and server Compose profiles currently use the same installation-owned `catalog-db`
container and `catalog-data` named volume. PostgreSQL is private to the Compose network.
- The database bootstrap login is the migrator. The init script creates the separate runtime login
from a Docker secret and grants only runtime DML and sequence access.
- Runtime and migrator passwords are separate protected host files exposed as separate Docker
secrets. Neither value belongs in tracked environment files
(`deploy/env/local.env.example`, `deploy/env/server.env.example`).
- The Fastify catalog repository uses a five-connection pool with a three-second connection
timeout. Fastify closes the repository pool during shutdown.
### Migrations
The compiled `catalog-migrate` entry point owns schema changes and uses the migrator credential.
The current ordered series is under
`backend/src/catalog/migrations/`. The local launcher runs
it before the normal stack, while production rollout must invoke the profile-gated service
explicitly.
This is weaker than the original recommendation in two ways: there is no catalog readiness check
for pending or unknown migrations, and the repository does not maintain content checksums for
migration drift. Until those checks exist, “the migrator completed” is the available deployment
gate; the application itself does not prove migration compatibility.
### Failure behavior actually provided
| Failure | Current catalog behavior | Current workflow behavior |
| --- | --- | --- |
| Catalog configuration omitted when launching the backend directly | Fastify uses `UnavailableCatalogRepository`; `/catalog/status` reports unavailable and domain-mapped catalog operations return sanitized `503` | `/health`, sessions, and SSE remain available |
| `catalog-db` unhealthy before Compose startup | `core` is not started because its dependency is not healthy | Workflow startup is blocked |
| PostgreSQL becomes unavailable after startup | `/health` remains process-only and `/catalog/status` reports unavailable; route errors are sanitized, but a uniform `503`/`Retry-After` mapping is not guaranteed | Existing session code does not read catalog rows, but both surfaces still share one Fastify process |
| Migrations are pending or incompatible | No dedicated readiness refusal exists; affected catalog operations fail | No catalog-to-workflow handoff exists yet, but local deployment correctness depends on running `catalog-migrate` first |
| Catalog backup is requested through current `tht backup` | No PostgreSQL dump is added and `catalog-data` is omitted | The archive may succeed while being unable to restore catalog state |
The shared process means resource exhaustion, fatal process errors, and startup hooks remain a
common failure domain even though repository calls are separated. Conversely, placing the module
inside Fastify does not require workflow code to consume catalog rows: preserving that data-flow
boundary is still the useful part of the original isolation recommendation.
## Historical recommendations that remain valid backlog
The following constraints survive ADR-0004 because they can be implemented inside the current
Fastify deployment:
1. **Bound every database operation.** Keep the adopted pool and connection timeout; add explicit
request query and lock timeouts. Long model calls and source sampling must not hold catalog
transactions.
2. **Make failures component-specific.** Translate connection, timeout, and pool-exhaustion errors
into one sanitized catalog-unavailable response, add bounded retry guidance, and keep `/health`
process-only.
3. **Make migration compatibility observable.** Report applied, pending, unknown, and drifted
migrations through a catalog readiness check and `tht doctor`; do not put migrator credentials
in `core`.
4. **Define a server TLS topology before externalizing PostgreSQL.** If the server profile moves to
an operator-provided endpoint, use a dedicated database and roles, protected CA material, and
hostname verification. Sharing a cluster leaves connection, maintenance, WAL, and storage blast
radius even when schemas are separate. PostgreSQL documents the relevant connection and TLS
parameters in its
[connection parameter reference](https://www.postgresql.org/docs/current/libpq-connect.html).
5. **Keep derived semantic data non-canonical.** When catalog publication is implemented, activate
complete immutable revisions and retain the last successfully activated revision rather than
exposing mutable catalog rows to the workflow.
## Backup and restore target
The original logical-backup recommendation is still valid and is now a confirmed implementation
gap.
### Backup
Extend `tht backup create` with a catalog step that runs `pg_dump --format=custom` through a
one-shot helper and adds the dump plus checksum to the installation manifest. Do not treat a tar of
a live data volume as a PostgreSQL consistency contract. PostgreSQL documents that `pg_dump`
creates a consistent export while the database remains in use and that custom format supports
selective restore ([`pg_dump`](https://www.postgresql.org/docs/current/app-pgdump.html)).
Use a dedicated least-privilege backup role, never write dumps into a workspace repository, and
record at least the server version, migration head, checksum, creation time, and catalog revision
identifiers. A filesystem/Qdrant archive with a failed or absent catalog dump must be reported as
incomplete.
### Restore
Restore only through an explicit maintenance operation into an empty or deliberately cleaned
target. Validate the archive checksum, run `pg_restore --exit-on-error --single-transaction`, then
verify migration compatibility, referential integrity, bounded entity counts, and an authenticated
catalog smoke test. The controls are documented by PostgreSQL
([`pg_restore`](https://www.postgresql.org/docs/current/app-pgrestore.html)). Keep the old database
until validation passes; rollback should switch the endpoint or retained volume, not dual-write.
## Diagnostics target
- Keep the shared `GET /health` endpoint process-only.
- Treat `/catalog/status` as the current minimal availability surface; add a bounded catalog
readiness check covering connection and migration compatibility before using it as a rollout
gate.
- Add read-only `tht doctor` checks for secret-file presence and permissions, PostgreSQL
reachability, authentication, migration state, pool saturation, and last successful catalog
backup. Sanitize all DSNs and errors.
- Use `pg_isready` only for server transport readiness. Its result does not prove schema or runtime
authorization correctness
([`pg_isready`](https://www.postgresql.org/docs/current/app-pg-isready.html)).
- Add structured metrics for connection acquisition failures, pool use, query/lock timeout,
migration head, background-job backlog, and backup age. Do not log connection strings, source
samples, prompts, or generated descriptions at info level.
## Recommended closure criteria
1. Decide whether ADR-0004's statement that catalog unavailability does not make sessions or SSE
unavailable must also hold at Compose startup. If yes, remove or soften the hard `core` →
`catalog-db` startup dependency without reintroducing a microservice.
2. Prove a configured PostgreSQL outage produces the same sanitized catalog response across every
catalog route while `/health`, session creation, and SSE continue to work.
3. Add catalog migration compatibility to readiness and `tht doctor`, including pending, unknown,
and drifted states.
4. Add a real `pg_dump`/`pg_restore` round-trip to local and server backup tests, and fail backup
publication when the catalog dump is absent or fails.
5. Before enabling a remote server catalog, define TLS verification, credential files, connection
limits, and the accepted cluster-level blast radius.
6. Before the NL→SQL cutover, define and test an immutable last-good Publication boundary; do not
dual-read mutable PostgreSQL rows and legacy metadata as competing authorities.
## Decision summary
PostgreSQL, installation-local bindings, private deployment, role separation, one-shot migrations,
bounded pooling, and process-only liveness are implemented. The separate `catalog-api` process and
the claim that `core` has no catalog dependency are not current design: ADR-0004 chose the existing
Fastify process, and Compose currently blocks `core` startup on `catalog-db` health. Logical
backup/restore, catalog migration readiness and drift detection, consistent outage mapping,
catalog-specific doctor checks, server TLS/external topology, and last-good publication remain
pending. Those open constraints should be treated as backlog, not as capabilities already provided
by the repository.
@@ -1,301 +0,0 @@
# ThothII metadata publication and Qdrant revision seams
**Research question:** Which existing workspace, snapshot, preprocessing, Qdrant,
session-pinning, and runtime read-only contracts constrain metadata publication without changing
the NL→SQL workflow?
**Last verified:** 2026-08-31
**Validity:** Active architectural research; the catalog-to-core integration described below is
still **deferred**, not implemented.
**Current authorities:** `PROJECT_STATE.md`,
[`workspace-evidence-v3.md`](../contracts/workspace-evidence-v3.md),
[`workspace-preprocessing-cli.md`](../contracts/workspace-preprocessing-cli.md),
[`tht-dwh.md`](../contracts/tht-dwh.md),
[`ADR-0001`](../adr/0001-postgres-metadata-catalog.md), and
[`ADR-0004`](../adr/0004-fastify-kysely-metadata-catalog.md). These sources and current code
override this research note if they diverge.
## Revalidation against the current implementation
| Finding | Status on 2026-08-31 | Current evidence and consequence |
| --- | --- | --- |
| PostgreSQL metadata authority in the existing Fastify backend | **Adopted/current** | ADR-0001 and ADR-0004 are implemented; the catalog is the management-plane authority. |
| Git workspace revision, immutable registry snapshot, and revision-pinned runtime | **Adopted/current** | Registry activation and the Workspace Evidence v3 contract still provide the publication boundary for runtime-owned files. |
| Qdrant read isolation for schema and Evidence by `workspace_id` + `workspace_revision` | **Adopted/current** | `QdrantVectorStore.search()` applies `_revision_filter()` to schema/Evidence; Memory and solved questions intentionally remain workspace-wide (`harness/tht/adapters/vector/qdrant.py`). |
| Physical schema as a file in the Git publication | **Superseded clarification** | `physical.yaml` is owned by the immutable `.tht-dwh` generation selected by `ACTIVE`, not by the Git workspace revision ([DWH contract](../contracts/tht-dwh.md)). Git currently supplies the revision-pinned curated `schema/annotations.yaml`. |
| Catalog-to-core publisher | **Open/deferred** | No current route or service projects catalog records into core artifacts. `PROJECT_STATE.md` explicitly defers the schema-linking integration (`PROJECT_STATE.md:160-171`). |
| Explicit Core Schema Selection | **Open/deferred** | The workspace descriptor selects one database and one physical schema, but has no table/column allowlist (`backend/src/workspaces/schema.ts`); no catalog selection is handed to `tht`. |
| Cross-revision hash lookup used by schema synchronization | **Open defect** | Reads are revision-filtered, but `existing_hashes()` is not. A same-key/same-content point from an older revision can suppress the required upsert into the new revision (`harness/tht/adapters/vector/qdrant.py`, `harness/tht/cli/vector_cmd.py`). |
| Deletion/GC | **Current for Evidence; open for schema** | Evidence has generation inventory, retention, compensation, and exact-generation deletion. `sync_canonical_records()` never deletes schema records absent from the new canonical set, and no revision-retention GC exists for schema points. |
| Annotation consumption | **Current but incomplete** | M-Schema rendering and schema embeddings consume `Annotations`; `tht schema columns` still returns only physical comments, so F4 does not display annotation descriptions (`harness/tht/cli/schema_cmd.py`, `harness/.pi/extensions/tht-gate.js`). |
## Conclusion
The lowest-impact **proposed** publication seam is not a new writer inside the NL→SQL workflow and
is not a direct CRUD-to-Qdrant path. The existing core consumes an introspected `PhysicalSchema`
from the active immutable DWH generation and a curated, Git-revision-pinned `Annotations`
document. A future explicit publication operation should project the approved catalog subset into
a new Core Schema Selection contract and the curated annotations, activate a new immutable Git
revision, and then invoke the existing `workspace index-schema` preprocessing operation. Qdrant
remains a derived, rebuildable projection. None of this catalog-to-core handoff exists yet;
`PROJECT_STATE.md:160-164` deliberately defers it to the next design
gate.
The proposed flow would preserve the existing runtime path:
```text
approved catalog data
-> explicit Core Schema Selection + curated annotations projection (future)
-> workspace Git revision (annotations and selection contract; physical.yaml stays DWH-owned)
-> immutable registry snapshot
-> revision-bound runtime configuration
-> existing tht vector index-schema
-> Qdrant records filtered by workspace_id + workspace_revision
-> existing retrieval_pack / schema render / F4 review
```
The complete database inventory remains in the Metadata Catalog. Only an explicit Core Schema
Selection and approved semantic fields should be projected into the artifacts used by the SQL
workflow. ThothII does not currently model or publish that table/column-level selection, so both
the projection contract and its operational publisher are new work.
## 1. Current authority and publication boundary
The workspace descriptor identifies one PostgreSQL database and one physical schema, plus one
workspace-owned Qdrant collection; it has no table or column allowlist
(`backend/src/workspaces/schema.ts`).
Consequently, a full-database metadata catalog and the subset eligible for the SQL core cannot be
represented as the same current descriptor object.
The existing public contract makes the Git workspace repository curator-owned. Changes occur in a
separate authoring clone followed by installation pull, and the API does not write workspace,
schema, or Evidence paths
([Workspace Evidence v3 contract](../contracts/workspace-evidence-v3.md)).
Therefore a browser CRUD service cannot silently make its PostgreSQL state authoritative for the
core without either:
1. exporting/committing a deterministic workspace projection through the existing curator flow;
or
2. deliberately replacing this Git-authority contract.
The first option preserves current architecture and session reproducibility.
Registry activation validates every descriptor at one exact commit, validates and synchronizes
the commit's `schema/annotations.yaml`, and rejects duplicate ownership of a Qdrant collection
(`backend/src/workspaces/registry.ts`,
`backend/src/workspaces/git-repository.ts`,
`backend/src/workspaces/annotations-sync.ts`).
It writes the candidate snapshot into a staging directory, records file digests in
`snapshot.json`, atomically renames the directory, and only then moves active state
(`backend/src/workspaces/registry.ts`).
That is the existing atomic publication boundary to reuse.
## 2. Canonical schema inputs already consumed by the core
The harness separates source facts from curated semantics:
- `PhysicalSchema` contains database/schema identity and tables; table facts include comments,
columns, physical foreign keys, and indexes; columns contain type, nullability, primary-key,
default, source comment, examples, and eligibility
(`harness/tht/mschema/models.py`).
- `Annotations` contains curated table descriptions, concepts and notes, column descriptions,
synonyms, concepts, evidence, notes and eligibility overrides, plus logical foreign keys
(`harness/tht/mschema/models.py`).
Rendering already gives annotations precedence over source comments and merges physical and
logical foreign keys. It also applies column eligibility before producing M-Schema context
(`harness/tht/mschema/render.py`). This makes
`Annotations` the natural narrow projection target for approved descriptions, synonyms, logical
relationships, and eligibility from the new catalog.
There are two current compatibility gaps:
- `schema_records()` embeds table descriptions/concepts and column descriptions/synonyms/examples,
but does not include physical or logical foreign keys in vector record content
(`harness/tht/vectorstore/records.py`).
Relationships still reach the model through deterministic schema rendering, not through schema
candidate embeddings.
- The F4 widget loads columns with `tht schema columns`
(`harness/.pi/extensions/tht-gate.js`), but that
command still returns only `PhysicalSchema.comment` values and does not merge
`Annotations`
(`harness/tht/cli/schema_cmd.py`).
A publisher that writes only annotations would improve vector search and rendered M-Schema but
not the table/column descriptions displayed by this existing reviewer widget. Fixing the command
to use the existing merged description helpers would preserve the workflow shape while closing
the inconsistency.
## 3. Existing preprocessing seam
`workspace index-schema` is already the supported operator seam. It creates a revision-bound
runtime, checks collection compatibility, and runs the harness command
`vector index-schema --json`
(`backend/src/workspaces/preprocessing-service.ts`,
[`workspace-preprocessing-cli.md`](../contracts/workspace-preprocessing-cli.md)).
The full preprocessing operation performs DWH preparation, FK suggestion/review, schema indexing,
and optional Evidence preprocessing as separate resumable stages
(`backend/src/workspaces/preprocessing-service.ts`).
Metadata-only publication should normally use the narrow `index-schema` operation after its
workspace artifacts are valid, rather than coupling catalog CRUD to the full pipeline.
The harness indexer reads the active immutable physical schema and revision-specific annotations,
constructs schema records, embeds only changed content, and writes them through the configured
vector adapter
(`harness/tht/cli/vector_cmd.py`). Its
machine result carries the physical/annotation artifact digests, workspace revision, collection,
and counts
(`harness/tht/cli/vector_cmd.py`). This is
the right place to retain publication evidence and audit linkage.
Preprocessing state is already revision- and binding-aware. A resumed job must match operation,
workspace revision, descriptor/catalog blobs, runtime config and binding digests or it fails with
`preprocessing_resume_mismatch`
(`backend/src/workspaces/preprocessing-state.ts`).
There is an important operational gate: every preprocessing operation calls
`assertSessionInventoryCompatible`; a non-finalized, non-archived session pinned to another
revision blocks preprocessing
(`backend/src/workspaces/preprocessing-service.ts`,
`backend/src/workspaces/preprocessing-state.ts`).
A catalog publication UX must expose this as a pending/blocking condition rather than report a
generic indexing failure.
## 4. Qdrant identity, payload and collection contracts
The collection contract is fixed at the descriptor's dimensions/distance and eight keyword payload
indexes: `content_hash`, `document_id`, `kind`, `record_key`, `record_kind`,
`vector_generation`, `workspace_id`, and `workspace_revision`
(`backend/src/workspaces/qdrant-collection.ts`).
`self_heal` may create a missing compatible collection or indexes; `require_existing` only validates
and refuses an incompatible collection
(`backend/src/workspaces/qdrant-collection.ts`).
The publication path must use this shared manager instead of inventing collection setup.
Schema payloads include both the workspace and workspace revision, record identity, semantic kind,
content and content hash
(`harness/tht/vectorstore/records.py`).
Qdrant point IDs for schema and Evidence also include the revision, and upserts use the same
revision in the payload
(`harness/tht/adapters/vector/qdrant.py`).
Reads always filter by `workspace_id`; schema and Evidence reads additionally filter by the bound
`workspace_revision`, while memory and solved-question records intentionally remain
workspace-wide
(`harness/tht/adapters/vector/qdrant.py:125-145`).
This means an approved semantic change needs a new workspace revision if old sessions must retain
their previous view. Directly overwriting points under the same Git revision would mutate the
meaning of that supposedly immutable revision; adding an independent catalog-publication version
would require changing the current runtime filter contract.
### Qdrant synchronization defect to resolve before catalog publication
The current incremental schema synchronizer compares canonical records by content hash and upserts
changed records, but it has no deletion step
(`harness/tht/cli/vector_cmd.py:77-99`). More
importantly, `QdrantVectorStore.existing_hashes()` filters by workspace and record kind but does not
apply `_revision_filter()`
(`harness/tht/adapters/vector/qdrant.py:266-286`),
even though point IDs and reads are revision-scoped. Therefore a same-key/same-content record from
an older revision may be classified as unchanged and never written under the new revision. This is
an implementation defect/risk inferred directly from the two code paths, and it should be fixed
and regression-tested before the catalog relies on `index-schema` for multi-revision publication.
Deletion/GC must be stated per record family. Evidence cleanup is implemented: the corpus pipeline
retains the configured number of published generations, protects active/job-referenced
generations, and calls exact-generation deletion for evicted or compensated generations
(`harness/tht/evidence/corpus/pipeline.py`,
`harness/tht/adapters/vector/qdrant.py:350-390`). Schema cleanup is
not implemented: `delete_kinds()` exists as a workspace-scoped adapter primitive, but the schema
synchronizer never calls it, it is not revision-scoped, and there is no retention policy for old
schema revisions. Publication therefore still needs exact current-revision deletion semantics and
separate safe GC for unleased historical schema revisions.
## 5. Session pinning and why the SQL workflow can remain unchanged
Normal session creation acquires an immutable registry revision, passes its snapshot path,
workspace ID and commit to `tht session new`, and only releases the retention lease after the
manifest has been persisted
(`backend/src/routes/sessions.ts`). The
manifest stores `workspace_id` and `workspace_revision` next to database/schema identity
(`harness/tht/session/store.py`). Resume and
saved-SQL paths reopen that exact retained snapshot rather than the current installation default
(`backend/src/routes/sessions.ts`,
`backend/src/routes/sql.ts`).
The runtime renderer places the same workspace revision in `runtime_identity`, points the harness
at the descriptor-owned Qdrant collection, and supplies the internal embedding service
(`backend/src/workspaces/runtime-renderer.ts`).
The vector adapter is constructed directly from that configuration, including the revision
(`harness/tht/adapters/factory.py`).
At session bootstrap the backend invokes the existing `search pack` command
(`backend/src/routes/sessions.ts`). That
command queries schema records with the existing schema kinds, ranks tables and persists the
candidate list
(`harness/tht/cli/search_cmd.py`). The Pi
extension reads the persisted retrieval pack through the CLI
(`harness/.pi/extensions/tht-gate.js`),
and F4 starts from those candidates while loading full table/column context through existing schema
commands. Thus catalog publication can improve the inputs without changing phases, gate semantics,
or persisted session artifacts.
## 6. DWH reuse across metadata-only revisions
DWH preparation uses immutable generations selected by an `ACTIVE` pointer
([DWH contract](../contracts/tht-dwh.md)). The effective-configuration
identity deliberately excludes `runtime_identity`, so a content-only Git revision does not force a
database re-introspection
([DWH contract](../contracts/tht-dwh.md)). This is the key enabling property for future metadata
publication: a new Git revision can carry updated curated annotations and a future Core Schema
Selection contract, reuse the compatible DWH-owned `physical.yaml`, and rebuild only the
revision-scoped schema projection. Today only the curated annotations part of that statement exists.
## 7. Constraints for the Metadata Catalog design
The following remain proposed requirements for the deferred catalog-to-core design gate; they are
not claims about current implementation:
1. **Separate full inventory from core projection.** PostgreSQL may hold the complete database
catalog, drafts and AI-generated text. Only an explicit Core Schema Selection and approved
semantic fields are exported to core artifacts/Qdrant.
2. **Publish; do not live-link.** CRUD changes are not visible to the SQL workflow until an explicit,
audited publication succeeds. A failed projection, Git activation or Qdrant index operation
leaves the previous revision active.
3. **Use a new Git revision as the publication identity.** This preserves current snapshot,
retention, session resume and Qdrant filtering semantics. A separate mutable catalog revision
cannot be safely introduced without changing runtime reads.
4. **Respect artifact ownership.** Keep introspected physical facts in the immutable DWH generation;
project the future Core Schema Selection through an explicit contract and approved semantic
edits into `Annotations`. Keep Mermaid and long-form database documentation outside Qdrant
unless a separate record kind and retrieval policy is designed.
5. **Reuse the operator boundary.** Trigger `workspace index-schema`, observe its schema-versioned
result and persist its artifact identities. Do not call Qdrant from browser CRUD handlers.
6. **Keep runtime read-only.** The session/Pi process continues to read the pinned snapshot,
retrieval pack and Qdrant projection. AI generation and catalog writes belong to the separate
management control plane.
7. **Surface publication gates.** UI status must distinguish Git activation, incompatible/missing
collection, resumable-session revision conflict, embedding failure and completed publication.
8. **Repair and test cross-revision schema synchronization first.** Scope `existing_hashes()` to
the bound revision, delete records removed from the current revision's canonical schema, and
define safe schema-revision GC. Reuse rather than duplicate the already implemented Evidence
generation GC.
9. **Close the annotation display gap without changing the workflow.** Make `schema columns` read
the same merged descriptions used by M-Schema/vector rendering, so the current F4 widget sees
the approved catalog text.
## Proposed decision summary for the Wayfinder map
- Keep the Metadata Catalog as a separate management subsystem and source of editable metadata.
- Keep Qdrant derived and revision-scoped; it is not the catalog database or source of truth.
- Publish through immutable workspace revisions plus the existing preprocessing service.
- Preserve the current NL→SQL workflow, Pi extension, phase model and session artifacts.
- Add a new, explicit Core Schema Selection contract because no table/column selection exists in
the current descriptor.
- Treat direct same-revision Qdrant writes, implicit CRUD publication, and bypassing the Git
snapshot boundary as rejected integration paths.
This summary remains design input. The authoritative current state is that catalog-to-core
integration and Sensitive Data Policy enforcement in schema-linking are deferred in
`PROJECT_STATE.md:160-171`.
@@ -114,7 +114,7 @@ La review dei join durante una sessione è distinta dalla curatela globale: il r
| Retrieval | Le relationship entrano nel render M-Schema ma non nei record vettoriali | Va deciso se e come influenzano selezione tabelle, ranking e descrizioni, oltre al rendering finale |
| Drift | Sync fisica è autoritativa; annotazioni sono una revisione separata | Servono semantiche per endpoint rinominati/eliminati, candidate stale, orphan e riapprovazione |
Le analisi già presenti nel repository trattano l'integrazione catalogo→core, la selezione pubblicabile e la riparazione degli indici come questioni ancora aperte; le loro proposte non sono decisioni implementate (`docs/research/2026-08-23-thothii-metadata-publication-qdrant-seams.md:20-43`; `docs/research/2026-08-23-thothii-metadata-publication-qdrant-seams.md:239-301`). Anche il piano di migrazione rinvia il lifecycle/admin delle relationship logiche (`docs/plans/2026-08-26-metadata-catalog-from-thothai.md:681-691`).
Nota storica: questa analisi precede il passaggio PostgreSQL catalog-to-core. Per il contratto implementato usare [architettura corrente](../architecture/overview.md) e ADR 0016; le vecchie ipotesi di pubblicazione non sono istruzioni operative.
## Direzione confermata nel grill
@@ -1,122 +0,0 @@
# Google Antigravity e Gemini 3.8 Flash: convenienza costo/qualità
Verifica effettuata il **7 settembre 2026**, privilegiando documentazione e annunci
ufficiali Google. Le prove indipendenti su Gemini 3.8 Flash sono ancora limitate perché il
modello è stato pubblicato il 2 settembre 2026.
## Giudizio sintetico
- **Registrazione gratuita: decisamente conveniente.** Il piano Individual da $0 include
Gemini 3.8 Flash, l'ultima versione Flash, oltre alla CLI e alle funzioni principali di
Antigravity. È difficile ottenere un rapporto costo/qualità migliore di un accesso gratuito
a un modello di questa fascia.
- **Google AI Pro da circa $20/mese: probabilmente conveniente per uso regolare**, ma solo dopo
aver misurato il consumo sul piano gratuito. Google non pubblica un numero fisso di prompt o
token inclusi, quindi non è possibile calcolare un break-even affidabile contro l'API.
- **Ultra da $100 o $200: non lo comprerei per il solo Gemini Flash** senza aver già dimostrato
di saturare Pro. Il piano da $100 offre 5 volte la quota Pro; quello da $200 ne offre 20 volte.
- Per il server dietro CyberArk, Antigravity ha un vantaggio concreto: la CLI ufficiale `agy`
funziona direttamente in Linux e prevede esplicitamente un flusso OAuth remoto tramite URL e
codice, senza tunnel né port forwarding.
## Accesso e prezzi
La [pagina ufficiale dei piani Antigravity](https://www.antigravity.google/docs/plans/) e la
[tabella dei modelli](https://www.antigravity.google/docs/models/) indicano che Gemini 3.8 Flash
è disponibile su Individual gratuito, AI Plus, AI Pro, AI Ultra ed Enterprise. Il piano gratuito
include anche completamenti Tab illimitati e tutte le funzioni del prodotto, inclusa la CLI, ma
ha un limite settimanale di base.
Google AI Pro costa normalmente **$19,99/mese** nella pagina internazionale di
[Google One](https://one.google.com/about/plans) e offre quote Antigravity superiori, con rinnovo
ogni cinque ore finché non viene raggiunto il limite settimanale. Google AI Ultra è offerto a
[$100/mese con quota 5× Pro oppure $200/mese con quota 20× Pro](https://blog.google/products-and-platforms/products/google-one/google-ai-subscriptions/).
Il problema è la misurabilità: Google dichiara che i limiti dipendono dalla capacità disponibile
e dalla quantità di lavoro compiuta dall'agente, possono cambiare e non corrispondono a un numero
pubblico fisso di richieste o token. Pro e Ultra possono acquistare crediti per continuare oltre
la quota base, con consumo ai prezzi della piattaforma Gemini.
Usando direttamente la Gemini API, Gemini 3.8 Flash costa fino al 31 dicembre 2026:
- **$0,75 per milione di token di input**;
- **$3,75 per milione di token di output**, inclusi i token di ragionamento;
- la metà in modalità Batch o Flex.
Dal 1º gennaio 2027 questi prezzi raddoppieranno a $1,50/$7,50. Esiste anche un free tier API,
con limiti, nel quale input e output sono gratuiti ma i contenuti possono essere usati per
migliorare i prodotti Google. Fonte: [pricing ufficiale Gemini API](https://ai.google.dev/gemini-api/docs/pricing).
## Qualità del modello
[Gemini 3.8 Flash](https://ai.google.dev/gemini-api/docs/models/gemini-3.8-flash) è GA, offre un
contesto da 1.048.576 token, output fino a 65.536 token, livelli di ragionamento low/medium/high e
strumenti per code execution, computer use, file search, function calling e search grounding.
I risultati pubblicati da Google lo collocano molto vicino ai modelli flagship su alcuni test di
coding, ma non su tutti:
| Benchmark | Gemini 3.8 Flash | Claude Opus 5 | GPT-5.6 Sol | Lettura onesta |
|---|---:|---:|---:|---|
| DeepSWE v1.1 | 73,7% | 74,0% | 72,7% | Prestazione quasi flagship sul software engineering end-to-end |
| Terminal-Bench 2.1 | 89,4% | 89,1% | 88,8% | Eccellente nel terminale sul benchmark più maturo |
| Terminal-Bench 4.0 | 19,1% | 51,8% | 37,3% | Forte calo sul test nuovo e più difficile: non è universalmente al livello dei flagship |
La [metodologia ufficiale Google](https://deepmind.google/models/evals-methodology/gemini-3-8-flash/)
precisa che diversi punteggi Gemini sono calcolati internamente e che i concorrenti provengono
anche da risultati auto-dichiarati; inoltre DeepSWE usa mini-swe, mentre Terminal-Bench usa un
harness diverso. I numeri vanno quindi letti come indicazione, non come garanzia. Una
[ricostruzione della tabella e dei confronti](https://www.vellum.ai/blog/gemini-3-8-flash-benchmarks-explained)
mostra lo stesso andamento: molto competitivo sui compiti di coding già ben rappresentati, più
debole su alcune prove nuove e aperte.
La conclusione qualitativa è: **ottimo implementatore quotidiano e subagente veloce**, con qualità
da modello molto più costoso in diversi task; per architettura difficile, debugging ambiguo o
lavori ad alto rischio è ancora sensato affiancargli un modello più forte come pianificatore o
revisore.
## Perché Antigravity è particolarmente adatto al server
La [CLI ufficiale Antigravity](https://antigravity.google/docs/cli/overview/) è una TUI interattiva
con editing multi-file, cronologia, tool calling, sandbox e subagenti. La
[guida d'installazione e autenticazione](https://antigravity.google/docs/cli/install/) conferma:
- esecuzione nativa su Linux, macOS e Windows;
- binario `agy` installato in `~/.local/bin` su Linux/macOS;
- quando rileva SSH, stampa un URL da aprire sul Mac e richiede di incollare nel terminale il
codice ottenuto;
- nessuna porta in ascolto e nessun port forwarding sono necessari per questo login.
Questo risolve meglio di ZCode il vincolo CyberArk, purché il server possa effettuare connessioni
HTTPS in uscita e sia consentita l'installazione del binario.
Per consumare la quota della registrazione Antigravity bisogna usare il client ufficiale. I
[termini Antigravity](https://antigravity.google/terms) vietano di riutilizzare il login/OAuth con
Pi, OMP, Claude Code, OpenCode o altri client. Con questi harness si può invece usare una normale
chiave Gemini API, pagando o consumando la quota API separata.
## Privacy e codice aziendale
Con un account personale Google registra le interazioni e può usarle per valutare e migliorare
prodotti e modelli; l'utente può disattivare l'uso dalle impostazioni. I termini Enterprise sono
diversi e la documentazione dichiara che codice, prompt e trascrizioni delle organizzazioni non
sono usati per addestrare i modelli Google. Fonti:
[termini Antigravity](https://antigravity.google/terms) e
[integrazioni Enterprise](https://antigravity.google/docs/ide/extensions/).
Su un server aziendale protetto da CyberArk userei quindi una registrazione personale solo dopo
aver verificato la policy interna; in caso contrario sceglierei l'accesso Antigravity Enterprise
tramite il progetto Google Cloud dell'organizzazione.
## Raccomandazione finale
1. Creare l'account gratuito e usare `agy` sul server per una settimana con task reali.
2. Tenere disabilitato l'uso automatico dei crediti extra e osservare i due indicatori di quota.
3. Passare a Pro soltanto se il limite gratuito interferisce con il lavoro.
4. Non acquistare Ultra finché Pro non viene saturato con regolarità.
5. Usare Gemini 3.8 Flash come modello principale per esplorazione, implementazione e test; per le
decisioni più difficili, mantenere Codex/Sol/Opus o un altro modello forte come revisore.
In breve: **sì, oggi Antigravity gratuito o Pro offre uno dei migliori rapporti costo/qualità per
Gemini 3.8 Flash**, e nel caso specifico la CLI ufficiale senza tunnel aumenta ulteriormente il
valore. Il limite commerciale da accettare è la quota non numerica e modificabile da Google.
@@ -1,154 +0,0 @@
# DeepSeek Harness su un server raggiunto tramite CyberArk
Data della verifica: 7 settembre 2026.
## Risposta breve
Sì. Il progetto che ha attirato molta attenzione è **DeepSeek Harness**, comando
`dsh`, pubblicato da DeepSeek nel repository ufficiale
[`deepseek-ai/deepseek-harness`](https://github.com/deepseek-ai/deepseek-harness).
Può lavorare direttamente sul server senza SSH port forwarding usando il profilo
ufficiale **headless**:
```sh
export DEEPSEEK_API_KEY='...'
npx @deepseek-ai/dsh --profile headless \
"Esamina questo repository e correggi i test che falliscono"
```
Questo profilo esegue un incarico, stampa la risposta e termina. Non avvia GUI,
browser o server HTTP e, soprattutto, **non apre alcuna porta**. Lo documentano sia
il [README del profilo headless](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/bundle/headless/README.md)
sia il [riferimento della CLI](https://github.com/deepseek-ai/deepseek-harness/blob/master/apps/cli/reference/README.md).
Pertanto il port forwarding non serve: basta la shell che CyberArk già consente e
connettività HTTPS in uscita.
La limitazione importante è che non si tratta, per ora, di un'interfaccia terminale
interattiva come Pi, OMP, Claude Code o Codex: il profilo ufficiale headless accetta
**un solo task per invocazione e non permette follow-up interattivi**.
## Che cos'è, e cosa non è
DeepSeek lo presenta come un harness open source in *developer preview*, basato su
un'architettura in cui modelli, strumenti, skill, sessioni, sandbox, storage,
subagenti e UI sono plugin componibili. La pagina ufficiale descrive inoltre
modalità Standard, Code, Minimal e Creator e la registrazione append-only delle
esecuzioni. Non è semplicemente il modello DeepSeek e non è uno dei numerosi wrapper
creati dalla comunità. Fonti: [pagina ufficiale DeepSeek Harness](https://www.deepseek.com/harness/en/)
e [README ufficiale](https://github.com/deepseek-ai/deepseek-harness#readme).
L'identità del pacchetto è verificabile anche nel
[`package.json` della CLI](https://github.com/deepseek-ai/deepseek-harness/blob/master/apps/cli/package.json):
il pacchetto pubblico è `@deepseek-ai/dsh` e installa l'eseguibile `dsh`.
Il [record ufficiale del repository](https://api.github.com/repos/deepseek-ai/deepseek-harness)
ne data la creazione al 13 agosto 2026; alla data di questa verifica la release
più recente è la prerelease
[`dsh-v0.1.3-alpha.2`](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.3-alpha.2),
pubblicata il 7 settembre 2026. Questo conferma quanto il progetto sia giovane e
rafforza l'avvertenza sulla stabilità delle interfacce.
## Le tre modalità rilevanti nel tuo scenario
| Modalità | Porta/tunnel | Interazione | Utilità con CyberArk |
|---|---:|---|---|
| `dsh --profile headless "task"` | Nessuna | One-shot, non interattiva | **Sì, è la soluzione semplice** |
| `dsh web` | HTTP locale su `127.0.0.1:3080` | Web interattiva | No dal Mac senza forwarding, reverse proxy autorizzato o browser sul server |
| `dsh --profile acp` | Nessuna porta; JSON-RPC su stdin/stdout | Persistente, pilotata da un client ACP | Possibile, ma l'integrazione attraverso CyberArk va provata |
La Web UI ufficiale ascolta per default su `127.0.0.1:3080`; il README afferma
esplicitamente che, durante un lancio SSH, il forwarding è responsabilità del client
SSH o dell'editor. È quindi inadatta al vincolo descritto, a meno di cambiare
l'architettura di accesso con l'approvazione dell'amministrazione
([README ufficiale](https://github.com/deepseek-ai/deepseek-harness#run)).
Il profilo ACP ufficiale è invece un server di automazione persistente su
**JSON-RPC stdio**, senza UI. Un client ACP avvia `dsh --profile acp`, crea una
sessione indicando una directory di lavoro assoluta e scambia richieste e risposte
su standard input/output
([README ACP ufficiale](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/bundle/acp-app/README.md)).
Da ciò segue una possibilità tecnica: un client locale potrebbe usare come comando
qualcosa di equivalente a `ssh <server> dsh --profile acp`, trasportando lo stdio
senza alcun port forwarding. Questa è però un'**inferenza architetturale**, non una
configurazione dichiarata compatibile con CyberArk da DeepSeek. Banner di login,
MFA interattivo, testo aggiunto su stdout, divieto di `ssh host command`, timeout o
riscrittura dei flussi da parte del proxy CyberArk possono corrompere JSON-RPC o
impedire del tutto l'avvio. Se CyberArk offre soltanto una console web/interattiva,
ACP non collega automaticamente Zed sul Mac al processo remoto.
## Requisiti di rete e di sistema
- Il repository richiede Node.js `^22.19.0` oppure `>=24.0.0`, come specificato
nel [`package.json` ufficiale](https://github.com/deepseek-ai/deepseek-harness/blob/master/package.json).
- Il primo `npx` deve poter scaricare `@deepseek-ai/dsh` dal registry npm. In un
ambiente bloccato occorre un mirror aziendale o un'installazione preventiva
autorizzata.
- Per usare il provider predefinito occorrono `DEEPSEEK_API_KEY` e traffico HTTPS
in uscita verso `https://api.deepseek.com`. `DEEPSEEK_BASE_URL` può sostituire
l'endpoint, per esempio con un proxy OpenAI-compatible
([adapter DeepSeek ufficiale](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/llm/llm-deepseek/README.md)).
- Se la rete aziendale impone un proxy HTTP, il riferimento della CLI indica
`NODE_USE_ENV_PROXY=1` affinché una versione Node compatibile rispetti
`HTTP_PROXY` e `HTTPS_PROXY`
([riferimento della CLI, sezione Source execution](https://github.com/deepseek-ai/deepseek-harness/blob/master/apps/cli/reference/README.md#source-execution)).
- Le operazioni richieste dall'agente possono necessitare altri host in uscita
(`git`, registry dei pacchetti, documentazione). Non sono necessari per il
trasporto DSH in sé, ma possono esserlo per il task affidato.
In altre parole, serve **HTTPS outbound**, non una connessione in ingresso verso il
server e non un tunnel dal Mac.
## Limiti pratici specifici di CyberArk
1. **Accesso alla shell:** se CyberArk consente di aprire una normale sessione shell
e di eseguire Node, `headless` funziona concettualmente come qualunque altro
comando. Se applica allowlist ai binari, servirà l'autorizzazione per `node`,
`npx`/`dsh` e per gli strumenti che l'agente vuole eseguire.
2. **Egress:** firewall e proxy devono consentire almeno l'endpoint del modello;
CyberArk non sostituisce questa autorizzazione di rete.
3. **Durata della sessione:** un timeout o la chiusura della sessione privilegiata
può terminare il task. `headless` non lascia un demone dietro di sé, ma incarichi
lunghi vanno confrontati con i limiti della sessione CyberArk.
4. **Credenziali e registrazione:** evitare di digitare o stampare la chiave API in
una sessione registrata. Conviene usare il meccanismo aziendale approvato per
iniettare il secret e verificare cosa CyberArk registra.
5. **Approvals:** l'headless ufficiale non ha un interlocutore umano integrato. Le
richieste di escalation senza un *answerer* vengono negate in modo fail-closed;
le normali scritture consentite nella workspace restano possibili
([contratto delle approvazioni](https://github.com/deepseek-ai/deepseek-harness/blob/master/packages/interaction/user-approval/README.md)).
6. **Sicurezza:** DeepSeek dichiara il progetto non sottoposto a security audit e
non pronto per produzione. Può eseguire codice generato dal modello e accedere
a file, processi, rete e credenziali disponibili al processo. Il progetto stesso
raccomanda privilegi minimi e un ambiente dedicato o usa-e-getta
([Safety notice ufficiale](https://github.com/deepseek-ai/deepseek-harness/blob/master/SAFETY.md)).
Un dettaglio importante in un server aziendale: la sandbox corrente descritta da
DeepSeek governa gli effetti sul filesystem, mentre rete e visibilità dei processi
sono fuori dal suo vocabolario di enforcement. Non considerarla quindi un sostituto
di firewall, container, account dedicato e policy CyberArk
([documentazione sandbox ufficiale](https://github.com/deepseek-ai/deepseek-harness/blob/master/docs/subsystems/sandbox.md)).
## TUI interattiva: stato reale
Il prodotto ufficiale offre oggi Web UI, headless one-shot e interfacce di
automazione SDK/ACP. Una TUI a schermo intero chiamata `dsh-tui` è stata presentata
nell'area community del repository, ma il pacchetto e il repository appartengono a
terzi, non a DeepSeek
([discussione nella community DSH](https://github.com/deepseek-ai/deepseek-harness/discussions/3715)).
Non va confusa con la CLI ufficiale e, dato che DSH avverte di possibili cambiamenti
incompatibili durante la developer preview, non la considererei la prima scelta su
un server aziendale protetto.
## Giudizio
Per il tuo vincolo concreto la risposta è **sì, ma in modalità one-shot**:
`dsh --profile headless` è utilizzabile dalla normale shell CyberArk, non apre porte
e non richiede tunnel. È una soluzione tecnicamente più adatta della Web UI, ma
meno comoda di Pi/OMP o Codex CLI per un dialogo iterativo.
La proverei inizialmente su una copia non sensibile del repository, con
`workspace-write`, egress ristretto e secret iniettato secondo le regole aziendali.
Se vuoi un'esperienza persistente dal Mac, ACP su stdio merita un piccolo test di
compatibilità con il gateway CyberArk; non lo darei per funzionante finché non si
verifica che il gateway permetta un comando remoto non interattivo e mantenga
stdin/stdout completamente puliti.
-127
View File
@@ -1,127 +0,0 @@
# OMP con GPT-5.6 Sol, Codex ufficiale e ZCode CLI
_Verifica effettuata il 7 settembre 2026 su documentazione e codice/fonti primarie correnti._
## Risposta breve
Se l'obiettivo è usare **GPT-5.6 Sol attraverso la quota inclusa nel piano ChatGPT/Codex**, la
scelta consigliata è **Codex ufficiale**: CLI per il lavoro da terminale, app desktop per più task,
worktree e revisione visuale. OMP è un harness molto capace e può essere preferibile per ACP/Zed,
multi-provider, LSP/DAP e orchestrazione dei subagenti, ma il suo accesso “OpenAI Codex OAuth” non
è una superficie che OpenAI documenta ufficialmente come client supportato.
Se invece OMP usa una **chiave API OpenAI**, l'integrazione è tecnicamente normale e GPT-5.6 Sol è
disponibile tramite Responses API con function calling, structured outputs e diversi tool. In quel
caso, però, il consumo è fatturato come API e non attinge alla quota inclusa nel piano ChatGPT.
Per **ZCode di Z.ai**, la documentazione ufficiale corrente presenta un'app desktop/ADE, non una
CLI pubblica autonoma. Esiste `zcode-app-cli`, ma è un progetto comunitario che si dichiara non
ufficiale ed estrae il runtime distribuito con l'app. Non esiste una garanzia ufficiale che riceva
un presunto bonus di quota del 50%; la documentazione corrente non promette neppure un diritto
fisso “150%” per tutti gli utenti ZCode.
## 1. OMP + GPT-5.6 Sol oppure Codex?
### Il modello è solo una parte del risultato
Usare lo stesso modello non rende equivalenti due agenti. L'harness decide prompt di sistema,
selezione e schema dei tool, raccolta del contesto, compaction, gestione degli errori, permessi,
parallelismo e verifica. Non risultano benchmark first-party che confrontino direttamente
GPT-5.6 Sol dentro OMP contro lo stesso modello dentro Codex: un vincitore assoluto non è quindi
dimostrabile.
GPT-5.6 Sol è il modello flagship general-purpose della famiglia 5.6 e supporta Responses API,
function calling, structured outputs, hosted shell, apply patch, skills, MCP e tool search.
[Scheda ufficiale GPT-5.6 Sol](https://developers.openai.com/api/docs/models/gpt-5.6-sol).
### Accesso tramite abbonamento
OMP dichiara un provider **OpenAI Codex OAuth** e consente il login dal proprio harness.
[README OMP](https://github.com/can1357/oh-my-pi/blob/daf07999c2fee9b22edc7bf8fea1fb6272e0df5e/README.md).
La documentazione OpenAI, però, indica il login ChatGPT per l'accesso in abbonamento soltanto per
l'app desktop ChatGPT/Codex, Codex CLI e l'estensione IDE. Non include OMP tra i client supportati.
Questo non prova che OMP non funzioni, ma significa che compatibilità, continuità dell'accesso e
interpretazione della quota non sono garantite da OpenAI per quel percorso.
[Autenticazione ufficiale Codex](https://learn.chatgpt.com/docs/auth).
Con una chiave API la situazione è diversa: l'accesso al modello è ufficiale, ma è fatturato ai
prezzi API. OpenAI specifica inoltre che l'autenticazione API usa il pricing API invece dei crediti
inclusi nel piano ChatGPT.
[Pricing Codex](https://learn.chatgpt.com/docs/pricing),
[pricing del modello](https://developers.openai.com/api/docs/models/gpt-5.6-sol).
### Confronto operativo
| Priorità | Scelta migliore | Motivo |
|---|---|---|
| GPT-5.6 Sol con quota ChatGPT e supporto prevedibile | Codex ufficiale | Login, quota e aggiornamenti sono first-party |
| Esperienza terminale, scripting e CI | Codex CLI | Loop locale, `codex exec`, review e handoff cloud |
| Task paralleli, worktree e review visuale | App desktop Codex | Gestione visuale dei task e isolamento mediante Git worktree |
| ACP/Zed, molti provider, LSP/DAP e Agent Hub | OMP | Harness più estensibile e ricco di strumenti |
| OMP con stabilità contrattuale dell'accesso | OMP + API key | API ufficiale, ma costo separato dal piano ChatGPT |
La CLI ufficiale è consigliata per terminale, automazione e CI.
[Codex CLI](https://learn.chatgpt.com/docs/codex/cli). L'app desktop è più comoda per task
concorrenti isolati, diff visuali e passaggio fra checkout locale e worktree.
[Worktree Codex](https://learn.chatgpt.com/docs/environments/git-worktrees).
CLI e app non danno due quote separate: quando si accede con ChatGPT, fanno parte dello stesso
ecosistema Codex/ChatGPT e consumano la stessa quota del piano. Il consumo concreto dipende da
modello, lunghezza del contesto e complessità del task.
[Pricing e limiti](https://learn.chatgpt.com/docs/pricing).
### Giudizio
Per un flusso principale basato su GPT-5.6 Sol sceglierei **Codex CLI**; affiancherei l'app quando
servono task paralleli, worktree e review visuale. Sceglierei OMP come harness principale soltanto
se le sue capacità specifiche — soprattutto Zed/ACP, routing multi-provider o Agent Hub — valgono
più del supporto first-party. Se OMP deve essere affidabile nel tempo, userei una API key anziché
fondare il workflow sul login OAuth non documentato da OpenAI per client terzi.
## 2. Il “150%” di Codex non è quota aggiuntiva
Nella documentazione OpenAI, **1,5× indica la velocità del Fast mode**, non un aumento della quota.
Con GPT-5.6, Fast mode consuma crediti a **2,5×** il tasso Standard. È disponibile nei client
Codex ufficiali quando si accede con ChatGPT.
[Fast mode](https://learn.chatgpt.com/docs/agent-configuration/speed).
## 3. Esiste una CLI ufficiale ZCode?
La guida ufficiale ZCode offre download desktop per macOS, Windows e Linux e descrive ZCode come
ADE con terminale integrato. Non documenta un comando autonomo ufficiale equivalente a `codex`.
La presenza di directory chiamate `~/.zcode/cli/` riguarda il runtime/config interno e non equivale
alla pubblicazione di una CLI supportata.
[Installazione ZCode](https://zcode.z.ai/en/docs/install),
[FAQ ufficiale](https://zcode.z.ai/en/docs/qa).
Esiste il progetto comunitario
[`zcode-app-cli`](https://github.com/kingsword09/zcode-cli), installabile con npm. Il progetto si
definisce esplicitamente non affiliato né approvato da Z.ai e dichiara di estrarre il runtime
dall'app desktop. Va quindi considerato non ufficiale e soggetto a possibili rotture e problemi di
compatibilità o licenza.
### Ha il 150% della quota?
Non c'è una conferma ufficiale corrente. Le pagine ZCode attuali descrivono quote Coding Plan su
finestre di cinque ore e settimanali, quota MCP mensile, crediti e reset card promozionali/dinamiche;
non dichiarano un moltiplicatore fisso 150% applicabile alla CLI.
[Statistiche e quota ZCode](https://zcode.z.ai/en/docs/usage-stats),
[connessione al Coding Plan](https://zcode.z.ai/en/docs/configuration).
Alcuni benefici sono esplicitamente legati all'app e al login ZCode: per esempio le reset card
richiedono di essere connessi a ZCode, mentre gli idle-time task gratuiti sono una funzione
dell'app in rollout. Questo non autorizza a concludere che un client comunitario riceva gli stessi
benefici.
[ZCode Usage Stats](https://zcode.z.ai/en/docs/usage-stats),
[ZCode overview](https://zcode.z.ai/en/docs/welcome).
Inoltre, i termini del GLM Coding Plan avvertono che l'uso tramite strumenti non autorizzati o non
supportati può comportare restrizioni di alcuni benefici. Di conseguenza non userei una CLI
comunitaria con l'obiettivo specifico di ottenere quota extra.
[Termini del GLM Coding Plan](https://docs.z.ai/legal-agreement/subscription-terms).
La scelta prudente è usare l'app ZCode ufficiale — incluso il suo terminale integrato o Remote
Development — e considerare valido soltanto il saldo mostrato in tempo reale nell'app. Se Z.ai
pubblicherà una CLI ufficiale o una regola “+50%”, servirà una dichiarazione esplicita applicabile
alla versione e al piano usati.
@@ -1,148 +0,0 @@
# Pi + `pi-config` di Amos vs Oh My Pi
_Ricerca aggiornata al 7 settembre 2026. Fonti: esclusivamente repository, documentazione, sorgenti, issue tracker e release ufficiali dei progetti._
## Risposta breve
**Per la maggior parte degli sviluppatori che vuole un agente completo e pronto all'uso, sceglierei Oh My Pi (OMP), ma non con le impostazioni di sicurezza predefinite.** OMP integra provider, routing per ruolo, LSP/DAP, subagent, web, browser, sessioni, memoria, marketplace e una UX terminale molto più ampia. È un prodotto coerente, installabile e aggiornabile come tale.
**Sceglierei invece Pi con pezzi selezionati di `pi-config` se volessi un nucleo piccolo, leggibile e fortemente personalizzabile**, accettando di assemblare, verificare e mantenere personalmente ogni componente. Il vantaggio non è avere più funzioni: è sapere con precisione quali funzioni si stanno aggiungendo.
La prima distinzione è fondamentale:
- [`amosblomqvist/pi-config`](https://github.com/amosblomqvist/pi-config) **non è una distribuzione alternativa di Pi**. È la configurazione personale di Amos Blomqvist: una raccolta di estensioni e skill da copiare selettivamente sopra il [Pi ufficiale](https://github.com/earendil-works/pi). Il README invita esplicitamente a non installarla come un unico pacchetto e a non clonarla sopra la propria configurazione.
- Per “Oh My Pi” qui si intende [`can1357/oh-my-pi`](https://github.com/can1357/oh-my-pi), il fork integrato di Pi che si presenta come agente “batteries included”. Non è un semplice tema o dotfile pack.
Di conseguenza il confronto corretto è **Pi ufficiale + componenti scelti da `pi-config` e dai repository companion** contro **OMP come fork/prodotto integrato**.
## Confronto spalla a spalla
| Area | Pi + `pi-config` | Oh My Pi | Valutazione |
|---|---|---|---|
| Installazione | Prima si installa [Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/README.md), poi si copiano singole estensioni/skill in `~/.pi/agent/`; alcuni componenti richiedono `npm install`, Chromium, Python o tool di sistema. Il [README di `pi-config`](https://github.com/amosblomqvist/pi-config#installation) raccomanda la selezione manuale. | Installer shell/PowerShell, Homebrew, Bun, Nix e `mise`, più binari multipiattaforma nelle [release](https://github.com/can1357/oh-my-pi/releases). | **OMP**: onboarding e aggiornamento più coerenti. |
| Filosofia | Pi è un harness terminale minimale, esteso tramite TypeScript, skill, prompt template, temi e pacchetti; evita intenzionalmente alcune funzionalità integrate, inclusi subagent e plan mode, per lasciarle alle estensioni ([README Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/README.md)). `pi-config` porta questa filosofia all'estremo: si prendono solo i pezzi voluti. | Fork “batteries included”: molte capacità sono native o integrate e configurabili da una superficie comune ([README OMP](https://github.com/can1357/oh-my-pi)). | **Dipende**: controllo e semplicità a Pi; completezza a OMP. |
| Provider e modelli | `pi-config` non aggiunge provider. Eredita da Pi login per Anthropic/OpenAI/Copilot, numerosi provider API, servizi cloud, OpenRouter e modelli locali/custom ([provider Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/README.md#providers-and-models)). | Dichiara oltre 60 provider e modelli locali/remoti; assegna modelli distinti ai ruoli `default`, `smol`, `slow`, `plan`, `commit`, `vision`, `task`, `advisor` e `tiny`, con fallback e credenziali multiple ([model routing OMP](https://github.com/can1357/oh-my-pi#sixty-plus-providers-a-thousand-models-one-model-away)). | **OMP** per ampiezza e routing; Pi resta già provider-agnostic. |
| Prompt e istruzioni | Pi supporta `AGENTS.md`, `SYSTEM.md`, `APPEND_SYSTEM.md` e prompt template. `prompt-snippets` aggiunge piccoli frammenti attivabili per singolo messaggio, poi azzerati ([sorgente/README](https://github.com/amosblomqvist/pi-config/tree/main/extensions/prompt-snippets)). | Stessi concetti di personalizzazione del prompt, con override globali/progetto/modello ([documentazione](https://github.com/can1357/oh-my-pi/blob/main/docs/system-prompt-customization.md)); aggiunge ruoli modello, advisor e agent personalizzati. | **OMP** per orchestrazione; **pi-config** ha la migliore micro-UX per regole effimere per messaggio. |
| Subagent | Non nativi nel core. Il companion [`pi-interactive-subagents`](https://github.com/amosblomqvist/pi-interactive-subagents) avvia agent asincroni in pannelli tmux, persistenti e pilotabili; include `scout`, `researcher` e `worker`, loadout a allowlist e nesting esplicito. | Subagent di prima classe con batch, modalità sincrona/asincrona, output strutturato, Agent Hub, steering/revive/kill e ricorsione controllata. L'isolamento del workspace esiste, ma è **opt-in**, non una proprietà automatica di ogni spawn ([task](https://github.com/can1357/oh-my-pi/blob/main/docs/tools/task.md)). | **OMP** per orchestrazione complessiva; **Amos** per pannelli tmux e minimo privilegio più semplice da verificare. |
| Tool di coding | Il core Pi espone un set volutamente piccolo; `pi-config` aggiunge soprattutto browser, fetch/search, guard e UI di domande. | Lettura/scrittura/editing e AST, grep/glob, shell ed evaluator persistenti, LSP, DAP, code review, security scan, checkpoint/rewind e altri tool elencati nel [README](https://github.com/can1357/oh-my-pi#thirty-one-first-class-tools). Alcuni sono disattivati inizialmente. | **OMP**, nettamente, per intelligence sul codice e debug. |
| Web e browser | [`browser`](https://github.com/amosblomqvist/pi-config/tree/main/extensions/browser) usa Playwright/Chromium headless ed è spento di default; è una singola pagina senza download/upload. `web-search` usa Google Custom Search e richiede API key/CSE ([sorgente](https://github.com/amosblomqvist/pi-config/blob/main/extensions/web-search/index.ts)); c'è anche `web-fetch`. | Browser/computer integrati e ricerca con numerosi backend, inclusi servizi a pagamento, locali/pubblici e motori specializzati ([README](https://github.com/can1357/oh-my-pi#web-search)). | **OMP** per copertura; `pi-config` è più piccolo e comprensibile. |
| Skill e plugin | Skill file-based native di Pi più quattro skill incluse: analisi sessioni, PDF, web debug e trascrizione YouTube ([inventario](https://github.com/amosblomqvist/pi-config#skills)). `learn`, dictation, memoria e subagent sono repository separati. | Skill caricate progressivamente tramite metadata e URI `skill://` ([skill docs](https://github.com/can1357/oh-my-pi/blob/main/docs/skills.md)); marketplace per plugin Git/local/catalogo, con skill, comandi, agent, hook, tool, MCP e LSP ([marketplace](https://github.com/can1357/oh-my-pi/blob/main/docs/marketplace.md)). | **OMP** per distribuzione e composizione. |
| MCP e interoperabilità | Dipende dalle capacità/estensioni del Pi base; `pi-config` non offre un livello MCP proprio. | Configurazione MCP utente/progetto e discovery di configurazioni provenienti anche da altri editor/agenti ([MCP docs](https://github.com/can1357/oh-my-pi/blob/main/docs/mcp-config.md)). | **OMP**. |
| UX/TUI | TUI Pi pulita con editor, fuzzy file search, immagini, shell, steering/follow-up e alberi di sessione. `ask-user-question` aggiunge un dialogo strutturato; snippets e pannelli tmux sono distintivi. [`pi-dictate`](https://github.com/amosblomqvist/pi-dictate) aggiunge dettatura Deepgram. | TUI più ricca con card dei tool, preview/accettazione edit, picker, Agent Hub e time-travel; include sia sintesi vocale sia STT tramite scorciatoia `Alt+H` ([README OMP](https://github.com/can1357/oh-my-pi)). | **OMP** in generale; la semplicità di Pi può essere un pregio. La voce non è esclusiva della configurazione Amos. |
| Sessioni | Pi salva JSONL ad albero, consente resume/fork/clone/tree e compaction conservando lo storico ([sessioni Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/README.md#sessions)). | JSONL append-only, struttura ad albero, blob esterni e ricostruzione versionata ([formato](https://github.com/can1357/oh-my-pi/blob/main/docs/session.md)); dump/export/share/fork/resume sono documentati come operazioni native ([operazioni](https://github.com/can1357/oh-my-pi/blob/main/docs/session-operations-export-share-fork-resume.md)). | **OMP**, di poco, per operazioni integrate; i due condividono una base concettuale simile. |
| Memoria | [`pi-observational-memory`](https://github.com/amosblomqvist/pi-observational-memory) è opzionale e spento di default: observer LLM paralleli distillano i turni, una compaction deterministica crea un ledger e un consolidatore produce file Markdown per sessione. È auditabile, ma aggiunge costo e complessità. | Memoria spenta di default con backend `local`, Hindsight, Mnemopi e Sharpshooter; sommari/lezioni possono attraversare sessioni e alimentare skill, con esplicita avvertenza che la memoria può essere obsoleta ([memory docs](https://github.com/can1357/oh-my-pi/blob/main/docs/memory.md)). | **OMP** per scelta e integrazione; **Amos** per un modello per-sessione semplice da ispezionare. |
| Sicurezza applicativa | Pi dichiara di non avere un permission system integrato per filesystem, processi, rete o credenziali e consiglia container/microVM ([security Pi](https://github.com/earendil-works/pi#permissions--containerization)). [`bash-guard`](https://github.com/amosblomqvist/pi-config/tree/main/extensions/bash-guard) intercetta euristicamente solo chiamate al tool `bash`: non protegge `write`, `edit` né i comandi `!`. | Ha policy per tool e tre approval mode, ma il default è **`yolo`**; in tale modalità gli override di comandi bash critici non forzano il prompt. Anche quando si approva un comando non c'è contenimento di filesystem, rete o subprocessi ([approval docs](https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md)). L'offuscamento dei segreti esiste ma è spento di default ([secrets docs](https://github.com/can1357/oh-my-pi/blob/main/docs/secrets.md)). | **Nessun vincitore sicuro di default**. OMP offre controlli migliori, ma sceglie un default molto permissivo. |
| Isolamento | Nessun sandbox OS o worktree per-agent documentato; l'allowlist del loadout limita i tool, non il filesystem raggiungibile dai tool concessi. | Gli spawn normali condividono la `cwd` del parent. Workspace separato e merge patch/branch richiedono `task.isolation.enabled` **e** `isolated: true` sul task; l'opzione non è disponibile in plan mode ([task](https://github.com/can1357/oh-my-pi/blob/main/docs/tools/task.md#inputs)). Neppure questo è un sandbox OS. | **OMP** per isolamento anti-collisione opt-in; **parità negativa** come confine di sicurezza. |
| Portabilità | I componenti sono piccoli file TypeScript/Markdown copiabili, quindi il lock-in concettuale è basso. Però molte estensioni Amos importano ancora il vecchio scope `@mariozechner/*`; il Pi attuale usa `@earendil-works/*`. L'[advisory ufficiale](https://github.com/earendil-works/pi/security/advisories/GHSA-r95r-rj6r-c39x) depreca il vecchio pacchetto, quindi oggi serve una verifica/possibile migrazione degli import. | Binari e setup multipiattaforma; importa varie convenzioni esterne. Tuttavia si è allontanato dal Pi upstream: scope `@oh-my-pi`, runtime/test Bun, moduli nativi, auth e API proprie sono differenze dichiarate nella [guida di porting](https://github.com/can1357/oh-my-pi/blob/main/docs/porting-from-pi-mono.md). | **Pi + selezione manuale** per lock-in ridotto; **OMP** per portabilità operativa immediata. |
| Migrazione delle estensioni | È l'ambiente nativo della raccolta Amos, salvo la transizione di package scope appena citata. | Non tratta `.pi/extensions` come root nativa. Può leggere dichiarazioni `pi.extensions` nei manifest, ma il caricamento e le API non rendono la migrazione automaticamente compatibile ([extension loading](https://github.com/can1357/oh-my-pi/blob/main/docs/extension-loading.md)). | **Non è drop-in in nessuna direzione**; verificare ogni estensione. |
| Aggiornamenti e manutenzione | `pi-config` è un piccolo snapshot personale, senza release versionate; il [registro commit](https://github.com/amosblomqvist/pi-config/commits/main/) mostra pochissimi cambiamenti e l'integrazione è responsabilità dell'utente. I companion hanno cicli propri. | Distribuzione versionata con release frequenti e asset per piattaforma; al 7 settembre 2026 la release più recente è [`v18.1.13`](https://github.com/can1357/oh-my-pi/releases/tag/v18.1.13). | **OMP** per manutenzione di prodotto; le release molto rapide aumentano anche il rischio di churn. |
| Maturità pratica | Il Pi sottostante è un progetto attivo e maturo, ma `pi-config` non è testato o pubblicato come distribuzione unitaria. L'autore di `pi-dictate`, per esempio, lo presenta esplicitamente come tool personale mantenuto per il proprio uso ([README](https://github.com/amosblomqvist/pi-dictate)). | Repository ampio, migliaia di commit e cadenza di release elevata ([storia](https://github.com/can1357/oh-my-pi/commits/main/), [release](https://github.com/can1357/oh-my-pi/releases)). Più integrazione e utenti implicano più validazione reale, ma anche superficie di bug e regressioni maggiore. | **OMP** come prodotto; nessuna garanzia che “più grande” significhi “più stabile”. |
## Approfondimento: gestione dei subagenti
**Sì: OMP ha una gestione dei subagenti paragonabile e, come orchestratore automatico, più completa.** La proposta Amos non è però semplicemente inferiore: privilegia un diverso modello operativo, nel quale ogni agente vive in un vero pannello tmux che l'utente può osservare e usare direttamente, con un loadout strettamente autorizzato.
| Capacità | Pi + Amos `pi-interactive-subagents` | Oh My Pi |
|---|---|---|
| Esecuzione e fan-out | Sempre asincrono e non bloccante; più chiamate partono in parallelo e notificano il parent indipendentemente ([README, “How it works”](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#how-it-works)). Non è documentato un limite di concorrenza configurabile. | `task.batch` è attivo di default e accetta `tasks[]`; con `async.enabled=true` gli agenti sono job in background, altrimenti il parent attende. Un semaforo `task.maxConcurrency` limita sia sync sia async ([task: input, modi e limiti](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/tools/task.md#modes--variants)). |
| Messaggi fra agenti | `subagent_message` corregge uno spawn in corsa al prossimo confine di turno o riapre quello concluso; `ask_question` permette al child di parcheggiarsi e interrogare il parent ([messaging](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#messaging)). | `hub send` consegna steering/follow-up, anche agli agenti parcheggiati, che vengono riattivati; la messaggistica peer è disponibile anche ai child ([task, “Notes”](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/tools/task.md#notes)). Limite documentato: lo steering è testo libero, non uno stato condiviso strutturato di goal/todo. |
| Supervisione e intervento umano | Widget con stati `starting/active/waiting/stalled/running`, tool corrente e completamenti espandibili; il pannello tmux è la sessione reale, quindi l'utente può entrarvi e scrivere direttamente ([status widget](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#status-widget--configuration)). | `Alt+A` apre Agent Hub: roster/albero, attività, modello, costo/token, transcript live, steering, revive e kill; l'utente può mettere a fuoco la sessione del child e scrivergli ([Agent Hub](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/agent-hub.md)). **OMP non manca quindi della supervisione interattiva**; Amos la rende più concreta e terminal-native tramite pannelli separati. |
| Resume e persistenza | Registro nome→sessione persistente attraverso i riavvii; il resume ripristina lo snapshot del loadout originale. Supporta sessioni `standalone`, `lineage-only` o `fork` con contesto del parent ([resume](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#messaging), [session mode](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#session-mode)). | Salva output e transcript (`agent://`, `history://`); agenti idle/parcheggiati sono riattivabili anche dopo il resume del parent ([Agent Hub, “Persisted agents”](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/agent-hub.md#persisted-agents-and-advisors)). Eccezione importante: un task eseguito in workspace isolato viene smontato dopo merge/cattura patch e **non è riattivabile** ([task flow](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/tools/task.md#flow)). |
| Definizioni e routing modelli | File Markdown in `.pi/agents` o `~/.pi/agent/agents`, con modello, thinking, skill, tool, `cwd` e modalità sessione; il singolo spawn può sovrascrivere il modello. Include tre profili (`scout`, `researcher`, `worker`) ([custom agents](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#custom-agents)). | File Markdown `.omp/agents`, agent inclusi e provenienti da estensioni/plugin; routing con override per nome, lista fallback e alias `modelRoles`, più effort per task, prewalk e advisor opzionali ([agent definition](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/task-agent-discovery.md#agent-definition-shape), [routing](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/task-agent-discovery.md#model-and-structured-output-precedence)). |
| Tool e sicurezza applicativa | Allowlist stretta: il child parte con `--no-extensions` e riceve soltanto i tool e le estensioni esplicitamente elencati; lo snapshot preserva il vincolo al resume ([tool access](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#tool-access-control)). È minimo privilegio applicativo, non sandbox OS. | Ogni definizione può limitare `tools` e `spawns`, e il limite di profondità rimuove `task`; però i child headless forzano `tools.approvalMode: yolo`, perché non hanno una UI locale per le conferme ([task flow](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/tools/task.md#flow)). Di conseguenza la qualità dell'allowlist/deny policy del parent è un confine essenziale. |
| Agenti annidati | Solo se `subagent_agents` è presente; la lista autorizza nomi precisi a ogni livello e non esiste uno spawn senza profilo nominato ([tool access](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#tool-access-control)). Il parent attende anche i nipoti prima dell'auto-exit. | Supportati con policy `spawns` e limite `task.maxRecursionDepth` (default `2`); al limite il tool `task` viene rimosso ([recursion gating](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/task-agent-discovery.md#recursion-depth-gating)). Agent Hub conserva la gerarchia parent/child. |
| Filesystem, worktree e conflitti | `cwd` può assegnare una directory diversa, ma il README non documenta workspace/worktree isolati né merge automatici ([role folders](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#role-folders)). **Inferenza:** più worker scriventi nella stessa checkout possono quindi collidere; separare le `cwd` resta responsabilità dell'orchestratore/utente. | Lo spawn ordinario usa la `cwd` del parent. L'isolamento è disponibile soltanto con configurazione globale attiva, `isolated: true`, repository Git e fuori dal plan mode; può applicare patch o cherry-pickare un branch ([task modes](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/tools/task.md#modes--variants)). Verifica l'applicabilità della patch; in caso di conflitto lascia l'artefatto per intervento manuale e preserva lo stash in branch mode ([gestione conflitti](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/tools/task.md#notes)). |
| Output | Il risultato è l'ultimo messaggio assistant, inoltrato al parent; non è documentato un contratto JSON Schema né un merge di file ([auto-exit](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#auto-exit)). | `outputSchema` per item, modalità `permissive`/`strict`, risultato parsato e artefatti completi tramite `agent://`; il child deve concludere con `yield`, con fino a tre reminder ([task outputs](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/tools/task.md#outputs), [flow](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/tools/task.md#flow)). |
| Portabilità e prerequisiti | Questa variante è esplicitamente **tmux-only** e richiede Pi + tmux ([requirements](https://github.com/amosblomqvist/pi-interactive-subagents/blob/c3e8b53c0754ae5ccc19fdab5a7481ec039bc2f7/README.md#requirements)); l'upstream HazAT supporta più multiplexer, ma non è il componente qui confrontato. | Il runtime OMP è multipiattaforma; l'isolamento seleziona backend diversi per Linux, macOS e Windows e ricade su copia ricorsiva quando necessario ([backend di isolamento](https://github.com/can1357/oh-my-pi/blob/4c6feacddb6ff26e5219df7283bc371ad7e1de55/docs/tools/task.md#side-effects)). |
### Giudizio mirato
**Per orchestrazione di subagenti sceglierei OMP**, perché combina fan-out controllato, sync/async, Agent Hub, messaggistica peer, output tipizzato, nesting con limiti e isolamento anti-collisione opzionale. Quindi la risposta alla domanda “ce l'ha anche OMP?” è **sì, e sul piano funzionale offre di più**.
**Pi + Amos resta preferibile in due casi:** quando si vuole entrare fisicamente nei pannelli dei worker mentre lavorano, oppure quando si considera prioritaria una politica child “deny by default” molto leggibile. La sua gestione può essere ottimale per un power user tmux; non è però altrettanto completa come orchestratore automatico, soprattutto per output strutturato, concorrenza limitata e gestione/merge degli artefatti.
Due caveat impediscono un verdetto semplicistico:
1. in OMP “subagent isolato” non significa “ogni subagent”: senza entrambi i toggle necessari, gli agenti scriventi condividono la checkout;
2. isolamento e continuità sono in tensione: il child OMP isolato evita collisioni, ma dopo il merge/cleanup non può essere riattivato; uno non isolato può invece essere parcheggiato e ripreso.
## Cosa include davvero `pi-config`
Nel repository principale ci sono:
- `ask-user-question`: richiesta strutturata con popup TUI e serializzazione dell'interazione;
- `bash-guard`: conferma/blocco euristico di comandi shell pericolosi;
- `browser`: automazione Playwright su Chromium headless, disattivata inizialmente;
- `custom-header`: header TUI personalizzato;
- `prompt-snippets`: regole brevi attivabili sul singolo messaggio;
- `web-fetch` e `web-search`;
- skill per analisi sessioni, PDF, debug web e trascrizione YouTube.
L'elenco e i prerequisiti sono nel [README ufficiale](https://github.com/amosblomqvist/pi-config). Le capacità più ambiziose sono in repository distinti:
- [`pi-interactive-subagents`](https://github.com/amosblomqvist/pi-interactive-subagents), subagent interattivi in tmux;
- [`pi-observational-memory`](https://github.com/amosblomqvist/pi-observational-memory), memoria osservazionale per sessione;
- [`pi-dictate`](https://github.com/amosblomqvist/pi-dictate), STT con Deepgram;
- [`learn`](https://github.com/amosblomqvist/learn), ambiente didattico con quiz, log e agent di ricerca/visualizzazione.
Questi componenti **non formano automaticamente una singola installazione testata, aggiornata e versionata insieme**. Considerarli una “suite” è un'inferenza utile per il confronto, non una promessa del maintainer.
## Dove OMP è realmente superiore
1. **È coerente come prodotto.** Installazione, configurazione YAML, schema, tool, sessioni e aggiornamenti fanno parte dello stesso rilascio ([settings](https://github.com/can1357/oh-my-pi/blob/main/docs/settings.md)).
2. **Il routing dei modelli è molto più sofisticato.** Non si sceglie soltanto un modello: si possono assegnare costi/capacità differenti a pianificazione, task, vision, commit, advisor e attività leggere.
3. **L'intelligence sul codice è integrata.** LSP, DAP, editing AST, evaluator persistenti e review non richiedono di costruire un proprio stack di estensioni.
4. **Subagent e memoria sono parti del sistema**, non componenti companion da sincronizzare a mano.
5. **Ha più opzioni di interoperabilità**: MCP, marketplace e discovery di configurazioni da altri strumenti.
Questa superiorità è soprattutto di **copertura e integrazione**, non una prova automatica di qualità superiore per ogni singola funzione. Le quantità dichiarate nel README di OMP sono affermazioni del progetto, non benchmark indipendenti.
## Dove Pi + la configurazione Amos è migliore
1. **È più facile capire il perimetro.** Ogni estensione è piccola, selezionabile e sostituibile. Si può usare `prompt-snippets` senza accettare browser, memoria o subagent.
2. **Ha meno lock-in architetturale.** Le skill Markdown e molte estensioni TypeScript restano vicine all'ecosistema Pi, anche se oggi gli import verso il vecchio package scope richiedono attenzione.
3. **Alcune idee sono più eleganti che “integrate”.** Gli snippet effimeri per messaggio, gli agent visibili nei pannelli tmux e la memoria in file Markdown per sessione sono facili da osservare e modificare.
4. **Favorisce l'apprendimento del sistema.** È una buona base per chi vuole costruirsi il proprio harness anziché adottare una piattaforma già opinionata.
Il prezzo è tempo operativo: installazione, dipendenze, compatibilità, aggiornamenti e test ricadono sull'utente.
## Sicurezza: la conclusione scomoda
**Né Pi + `pi-config` né OMP forniscono, da soli, un sandbox di sicurezza.**
- In Pi, `bash-guard` è un buon guardrail UX, ma non vede tutte le scritture e non contiene il processo. Le estensioni Pi hanno accesso al sistema con i privilegi dell'utente; la documentazione raccomanda esplicitamente container o microVM ([Pi security](https://github.com/earendil-works/pi#permissions--containerization)).
- In OMP esistono più policy, deny list e modalità di approvazione. Tuttavia `tools.approvalMode` parte da **`yolo`**, i safety override bash non diventano prompt in quella modalità, un comando approvato conserva accesso ambientale e le estensioni girano nello stesso processo ([approval mode](https://github.com/can1357/oh-my-pi/blob/main/docs/approval-mode.md), [extension loading](https://github.com/can1357/oh-my-pi/blob/main/docs/extension-loading.md)). Anche la protezione dei segreti è opt-in.
Se scegliessi OMP imposterei subito almeno:
1. `tools.approvalMode: always-ask` per un ambiente sensibile, oppure `write` come compromesso;
2. deny/prompt espliciti per evaluator, browser/computer e tool non necessari;
3. offuscamento segreti abilitato e configurato;
4. esecuzione in container, VM o microVM quando repository, credenziali o rete sono sensibili.
Con Pi farei la stessa cosa a livello OS e tratterei `bash-guard` come seconda cintura, non come sandbox.
## Raccomandazione per profilo
| Profilo | Scelta consigliata | Perché |
|---|---|---|
| Sviluppatore che vuole essere produttivo subito | **Oh My Pi** | Meno assemblaggio; LSP/DAP, modelli, agent e sessioni sono già integrati. |
| Power user multi-model / molti provider | **Oh My Pi** | Routing per ruolo, fallback e credenziali multiple sono capacità native. |
| Team che vuole una configurazione ripetibile | **Oh My Pi**, release fissata | Installer, Nix/mise, config e release versionate sono più riproducibili; fissare la versione riduce il churn. |
| Hacker di Pi che vuole costruire il proprio ambiente | **Pi + componenti `pi-config`** | Superficie ridotta, sorgenti leggibili, composizione libera. |
| Utente che vuole solo snippet, guard o browser | **Pi + singole estensioni** | Non serve adottare un fork molto più grande per tre capacità. |
| Chi apprezza subagent visibili e interattivi in tmux | **Pi + `pi-interactive-subagents`** | È una scelta UX specifica e ben distinta dall'Agent Hub. |
| Ambiente ad alta sicurezza | **Nessuno dei due senza isolamento OS** | I controlli applicativi non sostituiscono container/microVM; OMP va inoltre tolto da `yolo`. |
| Runtime Pi già integrato via RPC, come ThothII | **Restare su Pi salvo progetto di migrazione dedicato** | OMP offre RPC/ACP, ma package scope, caricamento estensioni, eventi e semantiche del fork richiedono test contrattuali: non è una sostituzione drop-in. |
### Nota specifica per ThothII
Per usare un agente nel terminale del repository, OMP può essere valutato senza cambiare l'architettura. **Sostituire invece il processo Pi che ThothII avvia in modalità RPC è un'altra decisione.** ThothII dipende dal contratto degli eventi RPC, dall'estensione gate, dal resume e dal comportamento di sessione. La [guida di porting di OMP](https://github.com/can1357/oh-my-pi/blob/main/docs/porting-from-pi-mono.md) e la sua [documentazione di caricamento estensioni](https://github.com/can1357/oh-my-pi/blob/main/docs/extension-loading.md) mostrano divergenze sufficienti da richiedere almeno una suite di compatibilità end-to-end prima di considerarlo un sostituto.
## Verdetto
**Il migliore in assoluto, per me, è Oh My Pi — con una release fissata e una configurazione iniziale più restrittiva del default.** Vince quasi tutte le categorie funzionali e riduce drasticamente il lavoro di integrazione.
Non lo sceglierei però “alla cieca”: `yolo` come default è un caveat serio, la superficie enorme rende probabile qualche regressione e il fork crea più dipendenza dalle proprie API. Per una workstation con codice e credenziali reali lo metterei dietro approvazioni esplicite e isolamento OS.
**Pi + `pi-config` è la scelta migliore quando l'obiettivo è un ambiente personale minimale e intenzionale**, non quando si cerca il maggior numero di funzioni. Installerei solo i componenti necessari, controllerei gli import dopo la migrazione da `@mariozechner/*` a `@earendil-works/*` e aggiungerei test prima di usarli in un flusso critico.
@@ -1,44 +0,0 @@
# Z.ai Coding Plan: bonus di quota e client CLI
Verifica effettuata il **7 settembre 2026**, esclusivamente su fonti ufficiali Z.ai/ZCode.
## Risposta breve
**No: Z.ai non documenta un bonus permanente del 150% (+50%) per un altro harness CLI.** La sola pagina ufficiale che usa oggi l'espressione “150% quota boost” è quella di **AutoClaw**: lo presenta come vantaggio a tempo limitato per piani Individual e Team, ma non precisa se significhi quota finale al 150% o incremento del 150%. AutoClaw è inoltre descritto come applicazione desktop per macOS e Windows, non come CLI da installare su un server. ([AutoClaw](https://autoclaw.z.ai/))
C'è però una promozione temporanea più interessante e formulata senza ambiguità: dal **3 al 20 settembre 2026**, ogni giorno fra le **23:00 e le 09:00 UTC+8**, GLM-5.3-Flash consuma zero quota in ZCode, mentre negli **altri agenti ufficialmente supportati la quota disponibile è raddoppiata**. In Italia, nel periodo della campagna, la finestra corrisponde alle **17:00–03:00 CEST**. La promozione vale per tutti i piani a pagamento e soltanto per GLM-5.3-Flash; GLM-5.3 continua a consumare la quota normale. ([GLM-5.3-Flash Usage Campaign](https://docs.z.ai/devpack/notice/event-glm-5.3-flash))
## Quali alternative CLI sono ufficialmente ammesse
La pagina corrente dei tool supportati nomina espressamente questi client utilizzabili da terminale:
| Harness | CLI/server | Stato nella documentazione Z.ai | Quota |
|---|---:|---|---|
| **Pi** | Sì | Esplicitamente incluso fra i Coding Agent Tool | Quota ordinaria; **2× su GLM-5.3-Flash nella finestra della campagna** |
| **Codex** | Sì | Esplicitamente incluso fra i Coding Agent Tool | Come sopra |
| **Claude Code** | Sì | Supportato e corredato da guida CLI ufficiale Z.ai | Come sopra |
| **OpenCode** | Sì | Supportato; guida ufficiale intitolata esplicitamente “OpenCode CLI” | Come sopra |
| **Droid** | Sì | Descritto da Z.ai come agente che gira nel terminale | Come sopra |
| **Crush** | Sì | Descritto esplicitamente come CLI/TUI | Come sopra |
| **Goose** | Sì | Incluso fra i Coding Agent Tool, con esecuzione locale | Come sopra |
| **Oh My Pi (OMP)** | Sì, tecnicamente | **Non è nominato** nell'elenco ufficiale; Z.ai nomina Pi, non OMP | Bonus e conformità **non confermati ufficialmente** |
Fonti: [elenco ufficiale dei tool e endpoint Coding Plan](https://docs.z.ai/devpack/tool/others), [guida Claude Code](https://docs.z.ai/devpack/tool/claude), [guida OpenCode CLI](https://docs.z.ai/devpack/tool/opencode).
Z.ai espone endpoint Coding Plan per Anthropic Messages, OpenAI Chat Completions e OpenAI Responses, ma questo **non rende automaticamente autorizzato qualunque client compatibile**: le condizioni limitano la quota ai tool ufficialmente supportati e avvertono che l'uso con integrazioni non autorizzate può comportare restrizioni. ([Subscription Terms](https://docs.z.ai/legal-agreement/subscription-terms), [Usage Policy](https://docs.z.ai/devpack/usage-policy))
## Implicazione pratica per il server
Per il caso in esame sceglierei **Pi + configurazione Amos direttamente sul server**: Pi è ora nominato ufficialmente da Z.ai, funziona da terminale e, durante la campagna attuale, rientra ragionevolmente negli “other supported Agents” con quota raddoppiata per GLM-5.3-Flash. La configurazione Amos estende Pi senza sostituire il client; resta comunque prudente verificare nel pannello consumi che le chiamate vengano classificate come Pi.
La seconda scelta è **OpenCode**, perché Z.ai fornisce una procedura CLI esplicita (`opencode auth login` → `Z.AI Coding Plan`). Claude Code è altrettanto documentato, ma la scelta dipende dalla preferenza per il suo harness.
Non sceglierei OMP confidando nel bonus: benché possa usare endpoint compatibili, **Oh My Pi non compare per nome** nell'elenco autorizzato. La risposta ufficialmente difendibile è quindi: **Pi sì; OMP non confermato**.
Infine, ZCode dispone oggi di pacchetti Linux, ma la documentazione li definisce sempre **desktop app** (`.AppImage`, `.deb`, `.rpm`) da lanciare con interfaccia grafica; non documenta una modalità CLI/headless equivalente a Pi o OpenCode. ([Installazione ZCode](https://zcode.z.ai/en/docs/install))
## Verdetto
- Se si cerca **esattamente un +50% permanente**, non risulta alcun harness CLI ufficialmente documentato.
- Se si vuole sfruttare la **promozione corrente**, Pi, Codex, Claude Code, OpenCode, Droid, Crush e Goose sono alternative CLI ufficialmente supportate; nel periodo e nella fascia indicati ottengono **2×**, non 150%, usando GLM-5.3-Flash.
- Per questa infrastruttura sceglierei **Pi+Amos sul server**, oppure **OpenCode** se si desidera il percorso d'installazione più esplicitamente documentato da Z.ai.
@@ -1,67 +0,0 @@
# ACP di Zed, Oh My Pi e Pi
_Verifica effettuata il 7 settembre 2026 su documentazione e codice sorgente primari._
## In breve
ACP (Agent Client Protocol) è un protocollo aperto che standardizza il collegamento tra un
editor/IDE e un coding agent. Il paragone utile è con LSP: LSP standardizza editor ↔ language
server, ACP standardizza editor ↔ agente. Il client (per esempio Zed) ospita l'interfaccia;
l'agent process conserva normalmente runtime, modelli, autenticazione, strumenti e configurazione.
ACP usa JSON-RPC 2.0. Copre inizializzazione e autenticazione, creazione/ripristino delle sessioni,
prompt e cancellazione, streaming di testo e pensieri, piani e tool call, comandi, richieste di
permesso e — se entrambe le parti lo supportano — operazioni su filesystem e terminale.
Fonti: [introduzione ACP](https://agentclientprotocol.com/overview/introduction),
[flusso e metodi del protocollo](https://agentclientprotocol.com/protocol/overview),
[External Agents in Zed](https://zed.dev/docs/ai/external-agents).
## Confronto in Zed
| Aspetto | Oh My Pi | Pi |
|---|---|---|
| Tipo di integrazione | Server ACP incorporato: `omp acp` | Adapter comunitario `pi-acp`, installabile dal registry di Zed |
| Collegamento al motore | ACP è una modalità dello stesso motore OMP | L'adapter avvia `pi --mode rpc` e traduce RPC ↔ ACP |
| Output e tool call | Streaming e tool call ACP nativi | Streaming, tool card, posizioni nei file e diff strutturati tradotti dall'adapter |
| File | Può inoltrare `read`/`write` al filesystem del client | Nessuna delega ACP `fs/*`; Pi legge e scrive localmente |
| Terminale | Può creare e seguire terminali del client | Nessuna delega ACP `terminal/*`; i comandi girano localmente |
| Permessi | `edit` e `bash` possono usare `session/request_permission` nell'editor | La UI riceve i tool call, ma non ha la stessa integrazione nativa di file/terminale |
| Sessioni | Implementazione diretta di sessioni, comandi e configurazione ACP | Mappa le sessioni ACP ai file di sessione Pi e supporta `session/load` |
| Skills/comandi | Carica skills, estensioni e slash command OMP | Carica skills e comandi Pi; l'adapter aggiunge comandi per l'uso headless |
| MCP configurati in Zed | OMP contiene il plumbing ACP/MCP nel proprio server | Accettati nei parametri ACP ma non inoltrati a Pi dall'adapter corrente |
| Maturità dichiarata | Funzionalità first-class del progetto | L'adapter si definisce “MVP-style” e centrato soprattutto su Zed |
L'integrazione OMP non è solo una dichiarazione nel README: il comando ACP è parte del sorgente e
il `ClientBridge` instrada `read`, `write`, `bash`, `edit` e richieste di permesso verso il client
quando Zed annuncia le capacità corrispondenti. Fonti:
[README OMP](https://github.com/can1357/oh-my-pi/blob/daf07999c2fee9b22edc7bf8fea1fb6272e0df5e/README.md),
[comando ACP](https://github.com/can1357/oh-my-pi/blob/daf07999c2fee9b22edc7bf8fea1fb6272e0df5e/packages/coding-agent/src/commands/acp.ts),
[ACP ClientBridge](https://github.com/can1357/oh-my-pi/blob/daf07999c2fee9b22edc7bf8fea1fb6272e0df5e/packages/coding-agent/src/modes/acp/acp-client-bridge.ts).
Pi è comunque supportato esplicitamente da Zed: si installa `pi ACP` dal registry. L'integrazione
è però un progetto separato e non una modalità presente nel core Pi. L'adapter corrente conserva
molto dell'esperienza utile — streaming, tool call, diff, resume, comandi e skills — ma dichiara
esplicitamente di non delegare filesystem o terminale a Zed e di non collegare a Pi gli MCP server
ricevuti dal client. Fonti:
[scheda Pi nel registry Zed](https://zed.dev/acp/agent/pi),
[manifest del registry](https://github.com/agentclientprotocol/registry/blob/9ec416a76f69c9ff8a8316931e38f4c74ad41fa8/pi-acp/agent.json),
[README e limiti di `pi-acp`](https://github.com/svkozak/pi-acp/blob/d1cffc047ab37a096ee70ca39cfc1de463db8d12/README.md),
[RPC di Pi](https://github.com/earendil-works/pi/blob/9211da172325117dc59e6f9f25f248dc628b3f81/packages/coding-agent/docs/rpc.md).
## Giudizio
**OMP si trova meglio con ACP perché ACP è una sua interfaccia nativa e il suo livello strumenti è
stato progettato per delegare operazioni all'editor.** Zed non è soltanto una finestra per la chat:
partecipa a file, terminale e autorizzazioni.
**Pi si trova comunque bene con Zed per l'uso quotidiano**, specialmente per conversazione,
streaming, modifiche, diff e ripresa delle sessioni. Oggi, però, l'integrazione è meno profonda:
Zed controlla un adapter che controlla Pi via RPC, mentre file e shell rimangono dal lato Pi.
Per Pi+Amos, i subagenti tmux non diventano automaticamente thread/subagenti nativi di Zed: ACP
espone la sessione Pi principale, mentre l'orchestrazione Amos continua nel proprio runtime e nei
pane tmux. È quindi una combinazione possibile, ma per osservare e pilotare direttamente quei pane
l'esperienza terminale/tmux resta più fedele. Questa conclusione è un'inferenza dall'architettura
dell'adapter (`pi-acp` avvia una singola sessione Pi RPC per sessione ACP) e dai limiti dichiarati,
non una garanzia esplicita del progetto Amos.
-33
View File
@@ -1,33 +0,0 @@
# Prodotti text-to-SQL e natural-language-to-SQL
Ricognizione di prodotti e siti ufficiali che consentono di interrogare dati strutturati
in linguaggio naturale e/o di generare SQL. Sono escluse pubblicazioni accademiche,
benchmark e articoli di terze parti. Le descrizioni riportano solo capacità dichiarate
nelle fonti ufficiali collegate.
Data della ricerca: 2026-09-09.
Nota di verifica: al 2026-09-09 SQLPilot non è stato verificato come raggiungibile. Il fetch
del sito ufficiale `https://sqlpilot.ai/` restituisce `502 Bad Gateway`; anche
`https://www.sqlpilot.ai/`, `http://sqlpilot.ai/`, `/download` e `/signup` non risultano
raggiungibili dal controllo diretto (risoluzione DNS fallita), senza redirect osservabili.
La pagina ufficiale indicizzata in precedenza resta una fonte storica, non una conferma di
disponibilità odierna. Non è stato individuato un URL ufficiale alternativo funzionante.
| Prodotto | URL ufficiale | Descrizione verificabile | Modello |
|---|---|---|---|
| [Vanna AI](https://vanna.ai/) | [Sito](https://vanna.ai/) · [Documentazione](https://vanna.ai/docs/index.html) | Framework/agente SQL che permette agli utenti di porre domande in linguaggio naturale su un database. La documentazione descrive un flusso RAG: si addestra il modello con SQL, DDL e documentazione, poi `ask` genera SQL che può essere eseguito sul database. | Open source; disponibile anche come servizio/soluzione hosted ed enterprise. |
| [Wren AI](https://github.com/Canner/WrenAI) | [Repository ufficiale](https://github.com/Canner/WrenAI) · [Documentazione CLI](https://github.com/Canner/WrenAI/blob/main/docs/core/reference/cli.md) | Layer semantico open source per agenti e applicazioni GenBI. Il progetto dichiara supporto a text-to-SQL su oltre 20 sorgenti dati; la CLI conserva coppie natural-language-to-SQL e usa il contesto semantico/MDL per scrivere ed eseguire query. | Open source (licenza Apache-2.0 per il core dichiarato nel repository). |
| [Dataherald](https://github.com/Dataherald/dataherald) | [Repository ufficiale](https://github.com/Dataherald/dataherald) | Engine natural-language-to-SQL per domande su dati relazionali. Il repository descrive un’API che espone un database in modo interrogabile in linguaggio naturale e include componenti per engine, API enterprise, console amministrativa e Slackbot. | Open source (Apache-2.0); include componenti enterprise self-hosted. |
| [DB-GPT](https://github.com/eosphoros-ai/DB-GPT) | [Repository ufficiale](https://github.com/eosphoros-ai/DB-GPT) · [DB-GPT-Hub](https://github.com/eosphoros-ai/DB-GPT-Hub) | Framework open source per applicazioni data-driven e agenti, con un workflow Text-to-SQL documentato e un progetto Hub per dataset, modelli e fine-tuning dedicati alla conversione testo-SQL. | Open source. |
| [Snowflake Cortex Analyst](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst) | [Documentazione ufficiale](https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-analyst) | Servizio gestito di Snowflake con cui utenti business pongono domande in linguaggio naturale e ricevono risposte senza scrivere SQL. La documentazione lo descrive esplicitamente come sistema agentico che genera risposte text-to-SQL, usando semantic model/views per il contesto. | SaaS/enterprise gestito nella piattaforma Snowflake. |
| [Databricks Genie / Genie Agents](https://docs.databricks.com/gcp/en/genie-agents/concepts) | [Documentazione ufficiale](https://docs.databricks.com/gcp/en/genie-agents/concepts) · [API](https://docs.databricks.com/gcp/en/genie-agents/conversation-api) | Funzionalità Databricks per interagire con dati tramite linguaggio naturale. La documentazione dichiara che Genie converte i prompt in SQL, restituisce quando possibile la query generata e i risultati, e può porre domande di chiarimento. | SaaS/enterprise, integrato in Databricks. |
| [ThoughtSpot Spotter](https://www.thoughtspot.com/product/spotter-semantics) | [Pagina prodotto ufficiale](https://www.thoughtspot.com/product/spotter-semantics) · [SpotGuide](https://tsa-guide.thoughtspot.com/) | Interfaccia di analytics conversazionale: l’utente descrive ciò che vuole in linguaggio naturale e il motore genera query SQL deterministiche tramite il layer semantico. La guida ufficiale presenta Spotter come esperienza “no SQL” per cercare e interrogare i dati. | SaaS/enterprise analytics. |
| [Seek AI](https://www.seek.ai/ai-data-analyst) | [AI Data Analyst](https://www.seek.ai/ai-data-analyst) · [Product Overview](https://www.seek.ai/product-overview) | Piattaforma/agent per dati strutturati con un Dialogue Agent che interpreta domande in linguaggio naturale e un Semantic Parsing Agent che genera query; l’offerta include anche un’interfaccia embedded per prodotti SaaS e un’app nativa Snowflake. | SaaS/enterprise; disponibile anche come componente embedded e Snowflake Native App. |
| ~~SQLPilot~~ | [Sito ufficiale](https://sqlpilot.ai/) | **Non verificato al 2026-09-09**: il dominio ufficiale non è risultato raggiungibile (502/DNS) e non è stato trovato un redirect o un URL ufficiale alternativo funzionante. Una precedente indicizzazione ufficiale descriveva un editor SQL assistito da AI, ma non costituisce conferma di disponibilità odierna. | Non verificato. |
## Note di perimetro
- “Open source” indica un progetto il cui repository ufficiale dichiara una licenza o un core open source; non implica che eventuali servizi hosted siano gratuiti.
- “SaaS/enterprise” indica un prodotto gestito o venduto come piattaforma aziendale; le fonti ufficiali non sempre pubblicano prezzi o dettagli contrattuali.
- Le capacità possono dipendere dal database collegato, dal modello semantico configurato e dai permessi dell’installazione.
@@ -60,8 +60,8 @@ Il comportamento da verificare deriva da:
- stato corrente del progetto in `PROJECT_STATE.md`;
- [contratto Catalog Schema Snapshot](../contracts/catalog-schema-snapshot.md);
- [piano del Metadata Catalog](../plans/2026-08-26-metadata-catalog-from-thothai.md);
- [specifica della generazione descrizioni](../plans/2026-08-28-ai-catalog-description-generation-spec.md);
- [piano del Metadata Catalog](../reports/knowledge-archives-release.md);
- [specifica della generazione descrizioni](../reports/knowledge-archives-release.md);
- [ADR 0007: sincronizzazione autorevole durevole](../adr/0007-durable-authoritative-schema-synchronization.md);
- [ADR 0009: un solo run sequenziale di generazione](../adr/0009-use-one-sequential-description-generation-run.md);
- [ADR 0010: campioni reali limitati](../adr/0010-allow-bounded-real-source-samples-for-description-generation.md);
+3 -205
View File
@@ -1,207 +1,5 @@
#!/usr/bin/env bash
# Current public guides and internal authentication contracts; no retired runbooks.
set -euo pipefail
script_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
root=$script_root
if [[ $# -gt 0 ]]; then
[[ $# -eq 2 && $1 == "--root" && -d $2 ]] || {
echo "usage: auth-docs-smoke.sh [--root DIRECTORY]" >&2
exit 2
}
root=$(cd "$2" && pwd -P)
fi
docs=(
"$root/docs/architecture/authentication.md"
"$root/docs/install/authentication-local.md"
"$root/docs/install/authentication-oidc.md"
"$root/docs/install/authentik.md"
"$root/docs/testing/authentication-manual-acceptance.md"
"$root/docs/architecture/overview.md"
"$root/docs/install/local.md"
"$root/docs/install/server.md"
"$root/docs/install/psd-workspace-setup.md"
"$root/docs/install/reverse-proxy-caddy.md"
"$root/docs/install/reverse-proxy-nginx.md"
"$root/docs/contracts/tht-pi.md"
"$root/docs/contracts/workspace-preprocessing-cli.md"
"$root/docs/guida-utente.md"
"$root/docs/index.md"
"$root/README.md"
"$root/PROJECT_STATE.md"
"$root/mkdocs.yml"
)
for path in "${docs[@]}"; do
[[ -f "$path" ]] || { echo "auth docs smoke: missing $path" >&2; exit 1; }
done
corpus=$(mktemp)
trap 'rm -f "$corpus"' EXIT
cat "${docs[@]}" >"$corpus"
required=(
"tht auth"
"groups"
"TOT Admin"
"THT_OIDC_CLIENT_SECRET"
"THT_AUTHENTIK_API_TOKEN"
"Remember me"
"oidc_mapped_group_missing"
"oidc_callback_failed"
"session.read_all"
"workspace.secrets.manage"
"auth.diagnostics.read"
)
for term in "${required[@]}"; do
rg -Fq "$term" "$corpus" || { echo "auth docs smoke: missing required term: $term" >&2; exit 1; }
done
canonical_compose='docker compose --env-file deploy/env/local.env -f compose.yaml -f deploy/compose.local.yaml up --build -d'
rg -Fxq "$canonical_compose" "$root/docs/install/local.md" || {
echo "auth docs smoke: missing canonical local Compose command" >&2
exit 1
}
if rg -n -F 'docker compose --env-file deploy/env/local.env +' "$corpus"; then
echo "auth docs smoke: noncanonical local Compose command" >&2
exit 1
fi
nav_count=$(awk 'index($0, "architecture/authentication.md") { count++ } END { print count + 0 }' "$root/mkdocs.yml")
[[ $nav_count == 1 ]] || {
echo "auth docs smoke: authentication navigation must appear exactly once" >&2
exit 1
}
python3 - "$root" <<'PY'
import pathlib
import re
import sys
root = pathlib.Path(sys.argv[1])
architecture = (root / "docs/architecture/authentication.md").read_text()
user_row = "| `user` | `session.use` |"
admin_row = (
"| `admin` | `session.use`, `session.read_all`, `session.manage_all`, `settings.manage`, "
"`workspace.manage`, `workspace.secrets.manage`, `database.manage`, `pi.manage`, `auth.diagnostics.read` |"
)
if user_row not in architecture or admin_row not in architecture:
raise SystemExit("auth docs smoke: role-to-permission map is not exact")
diagnostic_heading = "## Diagnostics and ordering"
diagnostic_start = architecture.find(diagnostic_heading)
diagnostic_end = architecture.find("\n## ", diagnostic_start + len(diagnostic_heading))
diagnostic_section = architecture[diagnostic_start:diagnostic_end if diagnostic_end >= 0 else None]
match = re.search(r"```text\n([\s\S]*?)```", diagnostic_section)
expected_codes = [
"auth_ready",
"auth_config_incomplete",
"auth_config_invalid",
"auth_session_store_invalid",
"local_user_registry_invalid",
"local_admin_missing",
"oidc_secret_missing",
"oidc_discovery_unreachable",
"oidc_issuer_mismatch",
"oidc_jwks_unreachable",
"oidc_group_catalog_unreachable",
"oidc_group_catalog_unauthorized",
"oidc_mapped_group_missing",
"oidc_mapped_group_ambiguous",
"oidc_groups_claim_invalid",
"oidc_device_flow_unavailable",
]
actual_codes = [] if match is None else [line for line in match.group(1).splitlines() if line]
if actual_codes != expected_codes:
raise SystemExit("auth docs smoke: diagnostic code union is not exact")
for relative, language, forbidden, required in [
("docs/install/reverse-proxy-caddy.md", "caddyfile", "forward_auth", "forward_auth"),
("docs/install/reverse-proxy-nginx.md", "nginx", "auth_request", "auth_request"),
]:
source = (root / relative).read_text()
direct_start = source.find("## Direct ThothII-managed OIDC")
deprecated_start = source.find("## Deprecated upstream migration mode")
if direct_start < 0 or deprecated_start <= direct_start:
raise SystemExit(f"auth docs smoke: {relative} does not split direct and deprecated modes")
direct = source[direct_start:deprecated_start]
deprecated_end = source.find("\n## ", deprecated_start + 4)
deprecated = source[deprecated_start:deprecated_end if deprecated_end >= 0 else None]
blocks = re.findall(rf"```{language}\n([\s\S]*?)```", direct)
direct_code = "\n".join(blocks)
if "/api/auth/oidc/login" not in direct or "/api/auth/oidc/callback" not in direct:
raise SystemExit(f"auth docs smoke: {relative} omits unchanged public OIDC paths")
if re.search(rf"(?m)^\s*{forbidden}\b", direct_code):
raise SystemExit(f"auth docs smoke: {relative} applies external auth in direct OIDC mode")
deprecated_code = "\n".join(
re.findall(rf"```{language}\n([\s\S]*?)```", deprecated)
)
if not re.search(rf"(?m)^\s*{required}\b", deprecated_code):
raise SystemExit(f"auth docs smoke: {relative} omits scoped deprecated upstream auth")
PY
if rg -n -i --pcre2 '\bthothii-admin\b|\bthothctl\b' "$corpus"; then
echo "auth docs smoke: forbidden obsolete host CLI wording" >&2
exit 1
fi
if rg -n -i --pcre2 -- '--password(?!-file)\b(?:[[:space:]]+|=)\S+' "$corpus"; then
echo "auth docs smoke: plaintext password option" >&2
exit 1
fi
if rg -n -i --pcre2 '(?:^|[,{[:space:]])password[[:space:]]*:[[:space:]]*\S+|"password"[[:space:]]*:[[:space:]]*(?:"[^"]+"|[^,}[:space:]]+)' "$corpus"; then
echo "auth docs smoke: plaintext password field" >&2
exit 1
fi
if rg -n -i --pcre2 '(?:unmapped|additional|extra)[^.\r\n]{0,160}groups?[^.\r\n]{0,160}(?:generate|produce|emit|cause|trigger|raise|create|result)[^.\r\n]{0,160}(?:warnings?|alerts?|advisory|advisories|notices?|notifications?|noise)|(?:warnings?|alerts?|advisory|advisories|notices?|notifications?|noise)[^.\r\n]{0,160}(?:generate|produce|emit|cause|trigger|raise|create|result)[^.\r\n]{0,160}(?:unmapped|additional|extra)[^.\r\n]{0,160}groups?' "$corpus"; then
echo "auth docs smoke: misleading noise claim for unmapped groups" >&2
exit 1
fi
# Projected server authentication documentation contract.
projection_docs=(
"$root/docs/install/server.md"
"$root/docs/install/authentication-local.md"
"$root/docs/testing/authentication-manual-acceptance.md"
"$root/docs/testing/psd-server-project-a-manual.md"
"$root/docs/plans/2026-08-20-psd-server-project-a-standalone.md"
"$root/PROJECT_STATE.md"
)
projection_corpus=$(mktemp)
trap 'rm -f "$corpus" "$projection_corpus"' EXIT
cat "${projection_docs[@]}" >"$projection_corpus"
projection_required=(
"canonical authentication root" "runtime projection" "CURRENT" "generations"
"root:root 0700/0600" "10001:10001 0700/0600"
"THT_AUTH_RUNTIME_ROOT"
"auth status --json" "auth publish"
"candidate or recovery" "Mac, Windows, and local direct-file authentication"
"explicit authorization"
)
for term in "${projection_required[@]}"; do
rg -Fqi "$term" "$projection_corpus" || {
echo "auth docs smoke: missing runtime-projection term: $term" >&2
exit 1
}
done
if rg -n -i --pcre2 "(?:useradd|groupadd)[^\n]{0,100}10001|10001[^\n]{0,100}(?:useradd|groupadd)" "$projection_corpus"; then
echo "auth docs smoke: runtime projection creates a host 10001 identity" >&2
exit 1
fi
if rg -n -i --pcre2 '(?:^|[.!?]\s+)(?:(?:mount|bind)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b[^\n]{0,120}(?:core|/run/thothii-auth)|(?:core|the core service)\s+(?:mounts?|binds?)\s+(?:the\s+)?(?:canonical authentication root|auth-canonical)\b)|auth-canonical[^\n]{0,120}:/run/thothii-auth' "$projection_corpus"; then
echo "auth docs smoke: canonical authentication is mounted into core" >&2
exit 1
fi
if rg -n -i --pcre2 '(?:(?:operators?\s+)?(?:may|can|should|must)\s+(?!not\b|never\b)(?:edit|write|modify|prune)|^\s*(?:sudo\s+)?(?:vi|vim|nano|sed|cp|mv|rm|tee|printf|echo)\b)[^\n]*(?:CURRENT|generations)' "$projection_corpus"; then
echo "auth docs smoke: runtime projection permits direct selector or generation edits" >&2
exit 1
fi
if rg -n -i --pcre2 '(?:^\s*(?:sudo\s+nginx\s+-T|(?:sudo\s+)?(?:printenv|env)(?:\s|$)|(?:sudo\s+)?(?:diff|cat|less|more|head|tail|yq)\s+[^\n]*(?:secret|auth\.yaml|users\.yaml))|(?:may|can|should|must)\s+(?!not\b|never\b)(?:dump|print|capture|attach|include|run)\b[^\n]*(?:password|auth\.yaml|users\.yaml|YAML|raw environment|nginx\s+-T|secret-bearing diff))' "$projection_corpus"; then
echo "auth docs smoke: runtime projection documents unsafe evidence collection" >&2
exit 1
fi
if rg -n -i --pcre2 'Project A (?:has been|was|is) started|legacy[- ]stack (?:has been|was|is) changed' "$projection_corpus"; then
echo "auth docs smoke: runtime projection claims live Project A or legacy mutation" >&2
exit 1
fi
echo "auth docs smoke: required terms and forbidden wording checks passed"
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
exec python3 "$root/scripts/verify-auth-docs.py" auth "$@"
+1 -93
View File
@@ -1,96 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
tmp=$(mktemp -d "${TMPDIR:-/tmp}/thoth-auth-docs.XXXXXX")
trap 'rm -rf "$tmp"' EXIT
files=(
docs/architecture/authentication.md
docs/install/authentication-local.md
docs/install/authentication-oidc.md
docs/install/authentik.md
docs/testing/authentication-manual-acceptance.md
docs/architecture/overview.md
docs/install/local.md
docs/install/server.md
docs/install/psd-workspace-setup.md
docs/install/reverse-proxy-caddy.md
docs/install/reverse-proxy-nginx.md
docs/contracts/tht-pi.md
docs/contracts/workspace-preprocessing-cli.md
docs/guida-utente.md
docs/index.md
README.md
PROJECT_STATE.md
mkdocs.yml
)
make_fixture() {
local name=${1:?fixture name required}
local fixture="$tmp/$name"
mkdir -p "$fixture"
for relative in "${files[@]}"; do
mkdir -p "$fixture/$(dirname "$relative")"
cp "$root/$relative" "$fixture/$relative"
done
printf '%s\n' "$fixture"
}
expect_rejected() {
local name=${1:?fixture name required}
local fixture_text=${2:?fixture text required}
local expected=${3:?expected error required}
local fixture output
fixture=$(make_fixture "$name")
printf '%s\n' "$fixture_text" >>"$fixture/README.md"
output="$tmp/$name.output"
if "$root/scripts/auth-docs-smoke.sh" --root "$fixture" >"$output" 2>&1; then
echo "auth docs fixture unexpectedly passed: $name" >&2
exit 1
fi
rg -Fq "$expected" "$output" || {
echo "auth docs fixture failed for the wrong reason: $name" >&2
sed -n '1,20p' "$output" >&2
exit 1
}
echo "auth docs negative fixture rejected: $name"
}
positive=$(make_fixture positive)
printf '%s\n' \
'Use --password-file <file>; never pass a password value.' \
'Additional unmapped groups are silently ignored without warnings, alerts, or advisories.' \
>>"$positive/README.md"
"$root/scripts/auth-docs-smoke.sh" --root "$positive" >/dev/null
echo "auth docs positive fixture passed"
expect_rejected thothctl-intervening \
'Run thothctl --installation <descriptor> --json auth check.' \
'forbidden obsolete host CLI wording'
expect_rejected alternate-admin \
'Run thothii-admin users list.' \
'forbidden obsolete host CLI wording'
expect_rejected password-option \
'Run tht auth user add demo --password example-value.' \
'plaintext password option'
expect_rejected yaml-password \
'password: example-value' \
'plaintext password field'
expect_rejected json-password \
'{"password": "example-value"}' \
'plaintext password field'
expect_rejected unmapped-warning \
'Unmapped OIDC groups generate warnings.' \
'misleading noise claim for unmapped groups'
expect_rejected unmapped-alert \
'Unmapped provider groups trigger operator alerts.' \
'misleading noise claim for unmapped groups'
expect_rejected unmapped-advisory \
'An advisory is emitted for every unmapped group.' \
'misleading noise claim for unmapped groups'
expect_rejected compose-plus \
'docker compose --env-file deploy/env/local.env + -f compose.yaml -f deploy/compose.local.yaml up --build -d' \
'noncanonical local Compose command'
echo "auth docs smoke fixture suite passed"
exec python3 "$root/scripts/test-verify-auth-docs.py" auth
+4 -9
View File
@@ -110,18 +110,13 @@ if (JSON.stringify(config).includes("fixture-model-api-key")) {
NODE
done
for manual in "$root/README.md" "$root/docs/installazione-docker-4-contesti.md"; do
grep -Fq -- './scripts/run-stack.sh' "$manual" || {
echo "${manual#"$root/"} lacks the canonical local launcher" >&2
exit 1
}
grep -Fq -- 'docker compose --env-file deploy/env/server.env' "$manual" \
&& grep -Fq -- '-f compose.yaml -f deploy/compose.server.yaml' "$manual" || {
echo "${manual#"$root/"} lacks the canonical server base+profile command" >&2
for manual in "$root/docs/install/standalone-manual-it.md" "$root/docs/install/standalone-manual-en.md"; do
grep -Fq -- '--configure-only' "$manual" && grep -Fq -- 'catalog-migrate' "$manual" || {
echo "${manual#"$root/"} omits explicit configuration or migration" >&2
exit 1
}
if rg -q 'local-compose\.workspace-registry|server-compose\.workspace-registry' "$manual"; then
echo "${manual#"$root/"} still references a superseded standalone Compose example" >&2
echo "${manual#"$root/"} references a retired Compose example" >&2
exit 1
fi
done
+2 -2
View File
@@ -13,7 +13,7 @@ targets=(
deploy/secrets/README.md
docs/install
docs/index.md
docs/installazione-docker-4-contesti.md
docs/operations/compose-reference.md
scripts/build-local.sh
scripts/build-local.ps1
scripts/docker-smoke.sh
@@ -43,7 +43,7 @@ case "$rg_status" in
;;
esac
for document in README.md docs/installazione-docker-4-contesti.md; do
for document in docs/operations/compose-reference.md; do
grep -Fq -- '-f deploy/compose.session-server.yaml.example' "$document" || {
echo "$document omits the required public-server session override" >&2
exit 1
+1 -1
View File
@@ -40,7 +40,7 @@ if [[ -d docker ]]; then
done < <(find docker -type f -print0)
fi
for file in README.md .env.example docs/installazione-docker-4-contesti.md; do
for file in README.md .env.example docs/operations/compose-reference.md; do
add_file install_files "$file"
done
if [[ -d docs/install ]]; then
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env python3
"""Mutation fixtures for current auth/DWH documentation; never contacts services."""
import importlib.util
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SCRIPT = ROOT / "scripts/verify-auth-docs.py"
spec = importlib.util.spec_from_file_location("authdocs", SCRIPT)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
mode = sys.argv[1] if len(sys.argv) > 1 else "auth"
assert mode in ("auth", "dwh")
relative = next(iter(module.AUTH if mode == "auth" else module.DWH))
with tempfile.TemporaryDirectory(prefix="thoth-auth-docs-") as tmp:
fixture = Path(tmp)
shutil.copytree(ROOT / "docs", fixture / "docs")
for item in ROOT.glob("*.md"):
shutil.copy2(item, fixture / item.name)
target = fixture / relative
original = target.read_text()
def check(expected=None):
result = subprocess.run([sys.executable, str(SCRIPT), mode, "--root", tmp], capture_output=True, text=True)
if expected is None:
assert result.returncode == 0, result.stderr
else:
assert result.returncode != 0 and expected in result.stderr, result.stderr
check()
mutations = [
("curl -k https://example.invalid", "TLS bypass"),
("curl --insecure https://example.invalid", "TLS bypass"),
("verify_tls=false", "TLS bypass"),
("DWH_API_KEY=synthetic", "raw environment secret"),
('curl -H "X-API-Key: synthetic" https://example.invalid', "raw key header"),
("tht auth user add demo --password synthetic", "secret argument"),
("password: synthetic", "plaintext password"),
("chmod 644 /protected/demo.key", "world-readable secret"),
("sudo nginx -T", "raw nginx capture"),
("git diff /protected/secret", "raw diff capture"),
("docker compose up dwh-auth", "Compose coupling"),
("thtdwh_v1." + "a" * 16 + "." + "b" * 43, "credential literal"),
("secret_sha256: " + "a" * 64, "credential digest"),
("auth-canonical:/run/thothii-auth:ro", "canonical auth root"),
("useradd --uid 10001 core", "host runtime identity"),
("nano /protected/generations/one/auth.yaml", "direct auth projection edit"),
]
for snippet, error in mutations:
target.write_text(original + "\n```sh\n" + snippet + "\n```\n")
check(error)
target.write_text(original + '\nDo not use `curl -k`.\n```sh\nDWH_API_KEY_FILE=/protected/demo.key\n```\n')
check()
if mode == "auth":
target.write_text(original.replace("`memory.manage`, ", ""))
check("role-to-permission")
target.write_text(original.replace("auth_ready\n", "auth_unknown\n"))
check("diagnostic code")
target.write_text(original + "\n[broken](missing-file.md)\n")
check("broken local link")
target.unlink()
check("missing")
print(f"{mode} documentation mutation fixtures passed")
+1 -316
View File
@@ -1,319 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
verify="$root/scripts/verify-dwh-auth-docs.sh"
temp_root=
report_pass() {
printf 'case=%s status=PASS\n' "$1"
}
report_fail() {
printf 'case=%s status=FAIL\n' "$1" >&2
exit 1
}
cleanup() {
if [[ "$temp_root" == /tmp/thothii-dwh-auth-docs.* && -d "$temp_root" ]]; then
rm -rf -- "$temp_root"
fi
}
trap cleanup EXIT
[[ -x "$verify" ]] || report_fail verifier_missing
temp_root=$(mktemp -d /tmp/thothii-dwh-auth-docs.XXXXXXXX) || report_fail fixture_root
fixture_root="$temp_root/fixture"
mkdir -p "$fixture_root/docs/install" "$fixture_root/docs/operations" \
"$fixture_root/docs/testing/evidence" "$fixture_root/scripts" "$fixture_root/deploy/psd"
for relative in \
docs/install/dwh-auth-server.md \
docs/install/dwh-auth-client-enrollment.md \
docs/install/dwh-auth-tls.md \
docs/operations/psd-dwh-auth-rollout.md \
docs/testing/dwh-auth-manual-acceptance.md \
docs/testing/evidence/psd-dwh-auth-rollout-report-template.md \
docs/install/local-workspace-registry.md \
docs/install/server-workspace-registry.md \
docs/install/psd-workspace-setup.md \
docs/guida-utente.md \
docs/index.md \
mkdocs.yml; do
mkdir -p "$fixture_root/$(dirname "$relative")"
cp "$root/$relative" "$fixture_root/$relative"
done
cp -a "$root/docs/." "$fixture_root/docs/"
cp "$root/deploy/psd/workspace-bindings.env.example" "$fixture_root/deploy/psd/workspace-bindings.env.example"
"$verify" --root "$fixture_root" || report_fail positive_source
report_pass positive_source
expect_rejected() {
local name=$1 target=$2 addition=$3
local case_root="$temp_root/$name"
cp -a "$fixture_root" "$case_root"
printf '\n%s\n' "$addition" >>"$case_root/$target"
if "$verify" --root "$case_root" >/dev/null 2>&1; then
report_fail "$name"
fi
report_pass "$name"
}
exercise_header_file_curl() {
local v1_header="$temp_root/synthetic-v1.header"
local legacy_header="$temp_root/synthetic-legacy.header"
command -v curl >/dev/null 2>&1 || report_fail header_file_curl_command
printf '%s\n' 'X-API-Key: synthetic-v1' >"$v1_header"
printf '%s\n' 'X-API-Key: legacy opaque value /:[]' >"$legacy_header"
chmod 0600 "$v1_header" "$legacy_header"
if ! python3 - "$v1_header" "$legacy_header" <<'PY'
import http.server
import pathlib
import stat
import subprocess
import sys
import threading
accepted = {"synthetic-v1", "legacy opaque value /:[]"}
class Handler(http.server.BaseHTTPRequestHandler):
def log_message(self, _format, *_args):
pass
def do_GET(self):
self.send_response(204 if self.headers.get("X-API-Key") in accepted else 401)
self.end_headers()
server = http.server.ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
for header_file in sys.argv[1:]:
if stat.S_IMODE(pathlib.Path(header_file).stat().st_mode) != 0o600:
raise SystemExit(1)
result = subprocess.run(
["curl", "--silent", "--show-error", "--output", "/dev/null", "--write-out", "%{http_code}", "--header", f"@{header_file}", f"http://127.0.0.1:{server.server_port}/rpc/ping"],
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
)
if result.returncode != 0 or result.stdout != "204":
raise SystemExit(1)
finally:
server.shutdown()
thread.join()
PY
then
report_fail header_file_curl_synthetic
fi
report_pass header_file_curl_synthetic
}
# Build synthetic only-in-fixture text at runtime: it is never a provisioned credential.
fake_key="thtdwh_v1.$(printf 'A%.0s' {1..16}).$(printf 'A%.0s' {1..43})"
fake_digest="$(printf 'A%.0s' {1..43})"
expect_rejected credential_literal docs/install/dwh-auth-client-enrollment.md "$fake_key"
expect_rejected opaque_legacy_header_literal docs/install/dwh-auth-client-enrollment.md "curl --header 'X-API-Key: opaque-legacy-fixture' https://example.invalid/dwh/rpc/ping"
expect_rejected credential_digest_literal docs/testing/evidence/psd-dwh-auth-rollout-report-template.md "\"secret_sha256\": \"$fake_digest\""
expect_rejected curl_insecure docs/install/dwh-auth-tls.md 'curl -k https://example.invalid/dwh/rpc/ping'
expect_rejected tls_disabled docs/install/dwh-auth-tls.md 'verify_tls=false'
expect_rejected secret_in_environment docs/install/dwh-auth-client-enrollment.md "export THT_WS_PSD_CLINICAL_DWH_API_KEY=$fake_key"
expect_rejected secret_in_argv docs/install/dwh-auth-client-enrollment.md "curl -H 'X-API-Key: $fake_key' https://example.invalid/dwh/rpc/ping"
expect_rejected curl_config_artifact docs/install/dwh-auth-server.md 'curl --config /root/dwh-auth-provision/request.conf https://example.invalid/dwh/rpc/ping'
expect_rejected world_readable_secret docs/install/dwh-auth-server.md 'chmod 0644 /root/dwh-auth-provision/client.key'
expect_rejected sudo_raw_nginx_capture docs/operations/psd-dwh-auth-rollout.md 'sudo nginx -T > /tmp/nginx-full.conf'
expect_rejected raw_diff_capture docs/operations/psd-dwh-auth-rollout.md 'sudo diff -u /etc/nginx/sites-available/policlinicosandonato /root/backup.conf'
expect_rejected git_raw_diff_capture docs/operations/psd-dwh-auth-rollout.md 'git diff --no-index /root/old.conf /root/new.conf'
expect_rejected raw_nginx_capture docs/operations/psd-dwh-auth-rollout.md 'nginx -T > /tmp/nginx-full.conf'
expect_rejected compose_coupling docs/install/dwh-auth-server.md 'docker compose up dwh-auth'
expect_replacement_rejected() {
local name=$1 target=$2 needle=$3 replacement=$4
local case_root="$temp_root/$name"
cp -a "$fixture_root" "$case_root"
[[ $(grep -Foc -- "$needle" "$case_root/$target") -eq 1 ]] || report_fail "${name}_fixture"
sed -i "s~$needle~$replacement~" "$case_root/$target"
if "$verify" --root "$case_root" >/dev/null 2>&1; then report_fail "$name"; fi
report_pass "$name"
}
expect_global_replacement_rejected() {
local name=$1 target=$2 needle=$3 replacement=$4
local case_root="$temp_root/$name"
cp -a "$fixture_root" "$case_root"
[[ $(grep -Foc -- "$needle" "$case_root/$target") -gt 0 ]] || report_fail "${name}_fixture"
sed -i "s~$needle~$replacement~g" "$case_root/$target"
if "$verify" --root "$case_root" >/dev/null 2>&1; then report_fail "$name"; fi
report_pass "$name"
}
exercise_header_file_curl
exercise_journal_scanner() {
local journal_source="$fixture_root/docs/install/dwh-auth-server.md"
local journal_code
journal_code=$(python3 - "$journal_source" <<'PY2'
import pathlib
import sys
source = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8")
start = source.index("if sudo python3 -c '") + len("if sudo python3 -c '")
end = source.index("' \"$since\" \"$v1_key_file\" \"$legacy_key_file\"; then", start)
print(source[start:end])
PY2
) || report_fail journal_extract
[[ "$journal_code" == *"process.stdout.read1("* ]] || report_fail journal_chunk_reader
[[ "$journal_code" != *"enumerate(process.stdout"* ]] || report_fail journal_unbounded_reader
local fake_bin="$temp_root/fake-bin"
local fake_journalctl="$fake_bin/journalctl"
local data_file="$temp_root/journal.data"
local pid_file="$temp_root/journal.pid"
local stdout_file="$temp_root/journal.stdout"
local stderr_file="$temp_root/journal.stderr"
mkdir -p "$fake_bin"
python3 - "$fake_journalctl" <<'PY2'
import pathlib
import sys
path = pathlib.Path(sys.argv[1])
path.write_text("""#!/usr/bin/env python3
import os
import pathlib
import sys
import time
pathlib.Path(os.environ["JOURNAL_PID_FILE"]).write_text(str(os.getpid()))
data = pathlib.Path(os.environ["JOURNAL_DATA_FILE"]).read_bytes()
for offset in range(0, len(data), 997):
os.write(sys.stdout.fileno(), data[offset:offset + 997])
if os.environ.get("JOURNAL_HOLD") == "1":
time.sleep(30)
raise SystemExit(int(os.environ.get("JOURNAL_EXIT", "0")))
""")
path.chmod(0o755)
PY2
local key_file="$temp_root/synthetic-real.key"
local legacy_file="$temp_root/synthetic-legacy.key"
printf '%s' 'REAL_SECRET_SYNTHETIC' >"$key_file"
printf '%s' 'LEGACY_SECRET_SYNTHETIC' >"$legacy_file"
chmod 0600 "$key_file" "$legacy_file"
run_journal_case() {
local name=$1 expected=$2 data=$3 exit_code=$4 hold=$5
printf '%s' "$data" >"$data_file"
: >"$pid_file" "$stdout_file" "$stderr_file"
if PATH="$fake_bin:$PATH" JOURNAL_DATA_FILE="$data_file" JOURNAL_PID_FILE="$pid_file" JOURNAL_EXIT="$exit_code" JOURNAL_HOLD="$hold" \
python3 -c "$journal_code" synthetic-since "$key_file" "$legacy_file" >"$stdout_file" 2>"$stderr_file"; then
actual=0
else
actual=$?
fi
[[ "$actual" -eq "$expected" ]] || report_fail "journal_${name}_status"
[[ ! -s "$stdout_file" && ! -s "$stderr_file" ]] || report_fail "journal_${name}_output"
if [[ -s "$pid_file" ]]; then
pid=$(cat "$pid_file")
[[ ! -e "/proc/$pid" ]] || report_fail "journal_${name}_reaped"
fi
report_pass "journal_${name}"
}
local python_bin
python_bin=$(command -v python3) || report_fail python_missing
run_journal_case clean 0 $'INFO clean\n' 0 0
run_journal_case oversized_line 2 "$(printf 'A%.0s' {1..1048577})" 0 0
run_journal_case too_many_lines 2 "$(printf 'x\n%.0s' {1..10001})" 0 0
run_journal_case child_failure 2 $'INFO child failure\n' 7 0
run_journal_case actual_key_across_chunk 1 "$(printf 'A%.0s' {1..65530})REAL_SECRET_SYNTHETIC" 0 1
local empty_path="$temp_root/empty-path"
mkdir -p "$empty_path"
printf '%s' 'INFO spawn failure\n' >"$data_file"
: >"$pid_file" "$stdout_file" "$stderr_file"
if PATH="$empty_path" JOURNAL_DATA_FILE="$data_file" JOURNAL_PID_FILE="$pid_file" JOURNAL_EXIT=0 JOURNAL_HOLD=0 \
"$python_bin" -c "$journal_code" synthetic-since "$key_file" "$legacy_file" >"$stdout_file" 2>"$stderr_file"; then
actual=0
else
actual=$?
fi
[[ "$actual" -eq 2 ]] || report_fail journal_spawn_failure_status
[[ ! -s "$stdout_file" && ! -s "$stderr_file" ]] || report_fail journal_spawn_failure_output
[[ ! -s "$pid_file" ]] || report_fail journal_spawn_failure_child
report_pass journal_spawn_failure
chmod 000 "$key_file"
: >"$pid_file" "$stdout_file" "$stderr_file"
if PATH="$fake_bin:$PATH" JOURNAL_DATA_FILE="$data_file" JOURNAL_PID_FILE="$pid_file" JOURNAL_EXIT=0 JOURNAL_HOLD=0 \
"$python_bin" -c "$journal_code" synthetic-since "$key_file" "$legacy_file" >"$stdout_file" 2>"$stderr_file"; then
actual=0
else
actual=$?
fi
chmod 0600 "$key_file"
[[ "$actual" -eq 2 ]] || report_fail journal_key_unreadable_status
[[ ! -s "$stdout_file" && ! -s "$stderr_file" ]] || report_fail journal_key_unreadable_output
[[ ! -s "$pid_file" ]] || report_fail journal_key_unreadable_child
report_pass journal_key_unreadable
python3 - "$key_file" <<'PY2'
import pathlib
import sys
pathlib.Path(sys.argv[1]).write_bytes(b"K" * 65_537)
PY2
run_journal_case long_key_across_chunks 1 "$(printf 'A%.0s' {1..65535})$(printf 'K%.0s' {1..65537})" 0 1
}
exercise_journal_scanner
expect_replacement_rejected missing_exact_gui_label docs/install/dwh-auth-client-enrollment.md "Validate workspace source" "Validate workspace"
expect_replacement_rejected server_transport_contradiction docs/operations/psd-dwh-auth-rollout.md 'server PSD: `postgres_direct` read-only' 'server PSD: `rest_api` read-only'
expect_replacement_rejected missing_mac_local_marker deploy/psd/workspace-bindings.env.example "Mac/local/remota" "server PSD"
expect_replacement_rejected missing_private_ca docs/install/psd-workspace-setup.md "TLS_CA_FILE" "TLS_CA_REMOVED"
expect_global_replacement_rejected missing_socket_path docs/install/dwh-auth-server.md "/run/dwh-auth/verify.sock" "/run/dwh-auth/other.sock"
expect_replacement_rejected rest_transport_flag deploy/psd/workspace-bindings.env.example "THT_WS_PSD_CLINICAL_DWH_TRANSPORT=rest_api" "THT_WS_PSD_CLINICAL_DWH_TRANSPORT=postgres_direct"
expect_replacement_rejected wrong_key_output_path docs/install/dwh-auth-server.md "key_output=/root/dwh-auth-provision/psd-mac-primary.key" "key_output=/root/dwh-auth-provision/psd-mac-primary-primary.key"
expect_replacement_rejected wrong_legacy_key_id docs/install/dwh-auth-server.md "legacy_key_id=legacy-shared" "legacy_key_id=legacy-public-key-id"
expect_global_replacement_rejected missing_header_file_transport docs/install/dwh-auth-server.md '--header "@' '--header "X-API-Key: '
expect_global_replacement_rejected missing_legacy_header_file docs/install/dwh-auth-server.md "legacy_header_file" "retired_auth_header"
expect_global_replacement_rejected missing_safe_restore docs/install/dwh-auth-server.md "registry_staging" "registry_overlay"
expect_replacement_rejected unsafe_restore_overlay docs/install/dwh-auth-server.md "sudo tar --acls --xattrs -C \"\$registry_staging\" -xf \"\$registry_backup\"" "sudo tar --acls --xattrs -C /var/lib -xf \"\$registry_backup\""
expect_replacement_rejected missing_guarded_registry_publish docs/install/dwh-auth-server.md "if ! sudo mv -T -- \"\$registry_root\" \"\$registry_previous\"; then" "sudo mv -T -- \"\$registry_root\" \"\$registry_previous\""
expect_replacement_rejected missing_candidate_check_guard docs/install/dwh-auth-server.md "if ! sudo /usr/local/sbin/dwh-auth --registry-root \"\$registry_candidate\" check; then" "sudo /usr/local/sbin/dwh-auth --registry-root \"\$registry_candidate\" check"
expect_replacement_rejected unsafe_registry_publish_target docs/install/dwh-auth-server.md "if ! sudo mv -T -- \"\$registry_root\" \"\$registry_previous\"; then" "if ! sudo mv \"\$registry_root\" \"\$registry_previous\"; then"
expect_replacement_rejected missing_key_file_byte_guard docs/install/dwh-auth-server.md "key_file_bytes=PASS" "key_file_bytes=SKIPPED"
expect_replacement_rejected missing_failed_registry_publish_target docs/install/dwh-auth-server.md "if ! sudo mv -T -- \"\$registry_root\" \"\$registry_staging/failed-dwh-auth\"; then" "if ! sudo mv \"\$registry_root\" \"\$registry_staging/failed-dwh-auth\"; then"
expect_global_replacement_rejected missing_rollback_registry_publish_target docs/install/dwh-auth-server.md "if ! sudo mv -T -- \"\$registry_previous\" \"\$registry_root\"; then" "if ! sudo mv \"\$registry_previous\" \"\$registry_root\"; then"
expect_global_replacement_rejected missing_exact_header_bytes docs/install/dwh-auth-server.md 'dd if="$1" bs=65536 status=none' 'dd if="$1" bs=1'
expect_replacement_rejected missing_actual_key_journal_scan docs/install/dwh-auth-server.md "journal_actual_key_scan=PASS" "journal_generic_scan=PASS"
expect_replacement_rejected missing_journal_actual_key_match docs/install/dwh-auth-server.md "any(needle in searchable for needle in needles)" "False"
expect_replacement_rejected missing_journal_returncode_guard docs/install/dwh-auth-server.md "if process.returncode != 0:" "if process.returncode == 0:"
journal_contract_source="$fixture_root/docs/install/dwh-auth-server.md"
if grep -Fq 'result.stdout.splitlines' "$journal_contract_source" \
|| grep -Fq 'enumerate(process.stdout' "$journal_contract_source" \
|| ! grep -Fq 'max_journal_bytes = 1_048_576' "$journal_contract_source" \
|| ! grep -Fq 'max_journal_lines = 10_000' "$journal_contract_source" \
|| ! grep -Fq 'max_chunk_bytes = 65_536' "$journal_contract_source" \
|| ! grep -Fq 'process.stdout.read1(' "$journal_contract_source" \
|| ! grep -Fq 'remaining = max_journal_bytes - bytes_seen' "$journal_contract_source" \
|| ! grep -Fq 'searchable = carry + chunk' "$journal_contract_source" \
|| ! grep -Fq 'process.kill()' "$journal_contract_source"; then
report_fail journal_streaming_limits
fi
report_pass journal_streaming_limits
expect_replacement_rejected missing_socket_legacy_probe docs/install/dwh-auth-server.md "socket_legacy=PASS" "socket_legacy=SKIPPED"
expect_replacement_rejected missing_https_post_revoke_v1 docs/install/dwh-auth-server.md "https_v1_post_revoke=PASS" "https_v1_post_revoke=SKIPPED"
expect_replacement_rejected missing_https_post_revoke_legacy docs/install/dwh-auth-server.md "https_legacy_post_revoke=PASS" "https_legacy_post_revoke=SKIPPED"
expect_global_replacement_rejected rollout_post_revoke_status docs/operations/psd-dwh-auth-rollout.md "v1=2xx post-revoca" "v1=204 post-revoca"
report_pass summary
exec python3 "$root/scripts/test-verify-auth-docs.py" dwh
+94
View File
@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""Validate current authentication documentation, not retired rollout transcripts."""
import argparse
import re
from pathlib import Path
AUTH = {
"docs/architecture/authentication.md": ["## Diagnostics and ordering", "HttpOnly", "SameSite=Lax", "authRevision", "401", "403", "direct, non-empty", "ignored silently"],
"docs/install/authentication-local.md": ["tht auth", "--password-file"],
"docs/install/authentication-oidc.md": ["groups", "THT_OIDC_CLIENT_SECRET", "THT_AUTHENTIK_API_TOKEN", "/api/auth/oidc/callback"],
"docs/install/authentik.md": ["groups"],
"docs/install/authentication-upstream.md": ["AUTH_MODE=upstream"],
"docs/testing/authentication-manual-acceptance.md": ["Remember me", "403", "Header identità inventati", "non sostituiscono"],
}
DWH = {
"docs/install/dwh-auth-server.md": ["/var/lib/dwh-auth/", "/run/dwh-auth/verify.sock", "key create", "key revoke", "encrypted", "401", "503"],
"docs/install/dwh-auth-client-enrollment.md": ["rest_api", "postgres_direct", "ssh_tunnel", "API_KEY_FILE", "TLS_CA_FILE", "/rpc/ping", "401", "503"],
"docs/install/dwh-auth-tls.md": ["SAN", "TLS_CA_FILE", "openssl x509 -noout -fingerprint -sha256"],
}
CODES = """auth_ready auth_config_incomplete auth_config_invalid auth_session_store_invalid
local_user_registry_invalid local_admin_missing oidc_secret_missing oidc_discovery_unreachable
oidc_issuer_mismatch oidc_jwks_unreachable oidc_group_catalog_unreachable
oidc_group_catalog_unauthorized oidc_mapped_group_missing oidc_mapped_group_ambiguous
oidc_groups_claim_invalid oidc_device_flow_unavailable""".split()
ADMIN = """session.use session.read_all session.manage_all settings.manage workspace.manage
workspace.secrets.manage database.manage memory.manage evidence.manage pi.manage auth.diagnostics.read""".split()
def verify(root, mode):
requirements = AUTH if mode == "auth" else DWH
sources = {}
for relative, tokens in requirements.items():
path = root / relative
if not path.is_file():
raise ValueError(f"missing {relative}")
source = path.read_text()
sources[relative] = source
for token in tokens:
if token.lower() not in source.lower():
raise ValueError(f"{relative}: missing topic {token}")
for target in re.findall(r"(?<!!)\[[^]]*\]\(([^)#]+)(?:#[^)]+)?\)", source):
if "://" in target or target.startswith("mailto:"):
continue
candidate = (path.parent / target).resolve()
if not candidate.is_file() or root.resolve() not in candidate.parents:
raise ValueError(f"{relative}: broken local link {target}")
corpus = "\n".join(sources.values())
# Negative prose can explain a forbidden command; only executable examples are
# checked for command safety. Credential literals are forbidden everywhere.
code = "\n".join(re.findall(r"```[^\n]*\n([\s\S]*?)```", corpus))
for pattern, label, source in [
(r"\b(?:thothctl|thothii-admin)\b", "obsolete host CLI", corpus),
(r"(?im)^\s*(?:[\"']?password[\"']?)\s*:\s*\S+|\"password\"\s*:\s*\"[^\"]+\"", "plaintext password", code),
(r"thtdwh_v1\.[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{43}", "credential literal", corpus),
(r"(?mi)^\s*[\"']?secret_sha256[\"']?\s*[:=]\s*[\"']?[A-Za-z0-9_-]{16,}", "credential digest", corpus),
(r"(?mi)^\s*(?:export\s+)?[A-Z][A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD)\s*=\s*[^\s#]+", "raw environment secret", code),
(r"(?i)--(?:password|api-key|token)(?!-file)\b(?:\s+|=)\S+", "secret argument", code),
(r"(?i)(?:-H|--header)\s+['\"][^'\"]*X-API-Key\s*:", "raw key header", code),
(r"(?i)curl[^\n]*(?:\s-k\b|--insecure)|verify_tls\s*[:=]\s*false|insecure_skip_verify\s*[:=]\s*true", "TLS bypass", code),
(r"(?i)chmod\s+0?[0-7][0-7][4-7]\s+[^\n]*(?:\.key|secret|provision|auth\.yaml|users\.yaml)", "world-readable secret", code),
(r"(?m)^\s*(?:sudo\s+)?nginx\s+-T\b", "raw nginx capture", code),
(r"(?m)^\s*(?:sudo\s+)?(?:diff\b|git\s+diff\b)", "raw diff capture", code),
(r"(?i)docker\s+compose[^\n]*\bdwh-auth\b", "DWH service Compose coupling", code),
(r"auth-canonical[^\n]*:/run/thothii-auth", "canonical auth root mounted into core", code),
(r"(?:useradd|groupadd)[^\n]*10001", "host runtime identity creation", code),
(r"(?im)^\s*(?:sudo\s+)?(?:vi|vim|nano|sed|cp|mv|rm|tee)\b[^\n]*(?:CURRENT|generations)", "direct auth projection edit", code),
]:
if re.search(pattern, source):
raise ValueError(f"forbidden {label}")
if mode == "auth":
architecture = sources["docs/architecture/authentication.md"]
user_row = "| `user` | `session.use` |"
admin_row = "| `admin` | " + ", ".join(f"`{p}`" for p in ADMIN) + " |"
if user_row not in architecture or admin_row not in architecture:
raise ValueError("role-to-permission map is not exact")
section = architecture.split("## Diagnostics and ordering", 1)[1].split("\n## ", 1)[0]
block = re.search(r"```text\n([\s\S]*?)```", section)
if not block or block.group(1).split() != CODES:
raise ValueError("diagnostic code union is not exact")
if re.search(r"(?:extra|unmapped|additional) groups[^.\n]*(?:generate|emit|produce) (?:warnings|alerts)", corpus, re.I):
raise ValueError("misleading noise claim for unmapped groups")
print(f"{mode} documentation contract passed")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("mode", choices=["auth", "dwh"])
parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parent.parent)
args = parser.parse_args()
try:
verify(args.root, args.mode)
except (ValueError, OSError) as exc:
parser.exit(1, f"{args.mode} docs: {exc}\n")
+2 -132
View File
@@ -1,135 +1,5 @@
#!/usr/bin/env bash
# Current public guides and internal authentication contracts; no retired runbooks.
set -euo pipefail
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
if [[ $# -gt 0 ]]; then
[[ $# -eq 2 && $1 == --root && -d $2 ]] || { echo 'usage: verify-dwh-auth-docs.sh [--root DIRECTORY]' >&2; exit 2; }
root=$(cd "$2" && pwd -P)
fi
python3 - "$root" <<'PY'
import pathlib, re, sys
root = pathlib.Path(sys.argv[1])
docs = {
"server": "docs/install/dwh-auth-server.md",
"client": "docs/install/dwh-auth-client-enrollment.md",
"tls": "docs/install/dwh-auth-tls.md",
"rollout": "docs/operations/psd-dwh-auth-rollout.md",
"manual": "docs/testing/dwh-auth-manual-acceptance.md",
"evidence": "docs/testing/evidence/psd-dwh-auth-rollout-report-template.md",
"local": "docs/install/local-workspace-registry.md",
"server_registry": "docs/install/server-workspace-registry.md",
"psd": "docs/install/psd-workspace-setup.md",
"guide": "docs/guida-utente.md",
"index": "docs/index.md",
"nav": "mkdocs.yml",
"psd_template": "deploy/psd/workspace-bindings.env.example",
}
text = {}
for label, relative in docs.items():
path = root / relative
if not path.is_file():
raise SystemExit(f"dwh-auth docs: missing {relative}")
text[label] = path.read_text(encoding="utf-8")
requirements = {
"server": ["key_output=/root/dwh-auth-provision/psd-mac-primary.key", "legacy_key_id=legacy-shared", "v1_header_file", "legacy_header_file", "random_header_file", "--header \"@", "registry_staging", "registry_previous", "registry_restore_rollback", "journal_actual_key_scan=PASS", "sys.argv[2:]", "read_bytes()", "subprocess.Popen", "stderr=subprocess.DEVNULL", "returncode != 0", "max_journal_bytes = 1_048_576", "max_journal_lines = 10_000", "process = None", "except OSError:", "stdout=subprocess.PIPE", "max_chunk_bytes = 65_536", "process.stdout.read1(", "remaining = max_journal_bytes - bytes_seen", "searchable = carry + chunk", "max_needle_length", "process.kill()", "process.wait()", "socket_v1=PASS", "socket_legacy=PASS", "https_v1_post_revoke=PASS", "https_legacy_post_revoke=PASS", "key_file_bytes=PASS", "dd if=\"$1\" bs=65536 status=none", "manifest", "journalctl", "systemctl disable --now", "trap", "/var/lib/dwh-auth", "root:dwh-auth", "2750", ".writer.lock", "0640", "/run/dwh-auth/verify.sock", "0660", "systemd", "key create", "key list", "key status", "key revoke", "check", "backup", "rollback", "disinstallazione", "rest_api", "postgres_direct", "ssh_tunnel"],
"client": ["Workspace management", "Validate workspace source", "Test workspace connections", "Save entered secrets", "Forget stored value", "API_KEY_FILE", "THT_WS_PSD_CLINICAL_DWH_API_KEY_FILE", "TLS_CA_FILE", "/rpc/ping", "rest_api", "postgres_direct", "ssh_tunnel", "401", "503", "rotazione", "revoca"],
"tls": ["self-issued", ".it", ".com", "SAN", "TLS_CA_FILE", "openssl x509 -noout -fingerprint -sha256", "fuori banda", "rinnovo", "curl -k"],
"rollout": ["v1=2xx", "legacy=2xx", "v1=2xx post-revoca", "legacy=401 post-revoca", "file header curl protetti 0600", "PASS/FAIL", "no raw diff", "Task 9", "Task 10", "IN_DISCUSSION", "postgres_direct", "rest_api", "legacy-shared", "nginx -t", "204", "401", "503", "Qdrant", "Ollama", "rollback"],
"manual": ["credenziali reali", "sintetici", "/rpc/ping", "204", "401", "503", "TLS", "registry", "postgres_direct", "ssh_tunnel"],
"evidence": ["ID pubblici", "owner", "mode", "timestamp", "checksum", "approvazione"],
}
for label, tokens in requirements.items():
lowered = text[label].lower()
for token in tokens:
if token.lower() not in lowered:
raise SystemExit(f"dwh-auth docs: {docs[label]} lacks required topic: {token}")
for path in [root / docs[k] for k in ("server", "client", "tls", "rollout", "manual", "evidence", "local", "server_registry", "psd", "guide", "index")]:
source = path.read_text(encoding="utf-8")
for target in re.findall(r"(?<!!)\[[^]]*\]\(([^)#]+)(?:#[^)]+)?\)", source):
if "://" in target or target.startswith("mailto:"):
continue
candidate = (path.parent / target).resolve()
if not candidate.is_file() or root.resolve() not in candidate.parents:
raise SystemExit(f"dwh-auth docs: broken local link {path.relative_to(root)} -> {target}")
nav = text["nav"]
for relative in (docs["server"], docs["client"], docs["tls"], docs["rollout"], docs["manual"], docs["evidence"]):
nav_relative = relative.removeprefix("docs/")
if nav.count(nav_relative) != 1:
raise SystemExit(f"dwh-auth docs: navigation must include once: {nav_relative}")
corpus = "\n".join(text.values())
for pattern, label in [
(r"thtdwh_v1\.[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{43}", "credential literal"),
(r"(?mi)^\s*[\"']?secret_sha256[\"']?\s*[:=]\s*[\"']?[A-Za-z0-9_-]{16,}", "credential digest literal"),
(r"(?mi)^\s*(?:export\s+)?[A-Z][A-Z0-9_]*(?:API_KEY|SECRET|TOKEN|PASSWORD)\s*=\s*(?!/|<)[^\s#]+", "secret in environment"),
(r"(?i)(?:curl|dwh-auth)[^\n]{0,240}(?:(?:-H|--header)\s+['\"][^'\"]*X-API-Key\s*:|--(?:api-key|token|password)\b)", "secret in argv"),
(r"(?im)^(?!.*(?:non usare|mai usare)).*curl\s+(?:[^\n]*\s)?(?:-k|--insecure)\b|verify_tls\s*=\s*false|insecure_skip_verify", "TLS bypass"),
(r"(?i)chmod\s+0?[0-7][0-7][4-7]\s+[^\n]*(?:\.key|secret|provision)", "world-readable secret"),
(r"(?m)^\s*(?:sudo\s+)?nginx\s+-T\b", "raw Nginx capture"),
(r"(?m)^\s*(?:sudo\s+)?(?:diff\b|git\s+diff\b)", "raw diff capture"),
(r"(?m)^\s*(?:sudo\s+)?curl[^\n]*--config\b", "curl config artifact"),
(r"(?i)docker\s+compose[^\n]*\bdwh-auth\b", "Compose coupling"),
]:
if re.search(pattern, corpus):
raise SystemExit(f"dwh-auth docs: forbidden {label}")
server_direct = re.search(r"server PSD[^\n]{0,100}postgres_direct", text["rollout"], re.I)
psd_direct = re.search(r"server PSD[^\n]{0,100}postgres_direct", text["psd"], re.I)
if not server_direct or not psd_direct:
raise SystemExit("dwh-auth docs: PSD server must remain postgres_direct")
template = text["psd_template"]
if "tht_ws_psd_clinical_dwh_transport=rest_api" not in template.lower() or "mac/local/remota" not in template.lower() or not re.search(r"mai .*server psd", template, re.I):
raise SystemExit("dwh-auth docs: PSD REST template must be explicitly Mac/local/remota, never server PSD")
if "TLS_CA_FILE" not in text["psd"] or re.search(r"(?i)nessuna CA|HTTPS pubblico", text["psd"]):
raise SystemExit("dwh-auth docs: PSD setup contradicts private CA TLS requirement")
restore_steps = (
'registry_staging="/var/lib/.dwh-auth-restore-$run_id"',
'registry_candidate="$registry_staging/dwh-auth"',
'sudo tar --acls --xattrs -C "$registry_staging" -xf "$registry_backup"',
'if ! sudo /usr/local/sbin/dwh-auth --registry-root "$registry_candidate" check; then',
'if ! sudo mv -T -- "$registry_root" "$registry_previous"; then',
'if ! sudo mv -T -- "$registry_candidate" "$registry_root"; then',
'if ! sudo mv -T -- "$registry_previous" "$registry_root"; then',
'if ! sudo mv -T -- "$registry_root" "$registry_staging/failed-dwh-auth"; then',
)
restore_positions = [text["server"].find(step) for step in restore_steps]
if any(position < 0 for position in restore_positions) or restore_positions != sorted(restore_positions):
raise SystemExit("dwh-auth docs: restore must stage/check then use guarded same-filesystem renames")
if text["server"].count('if ! sudo mv -T -- "$registry_previous" "$registry_root"; then') != 2:
raise SystemExit("dwh-auth docs: every restore rollback rename must use guarded mv -T --")
if re.search(r"sudo tar[^\n]*-C /var/lib[^\n]*(?:-x|--extract)[^\n]*registry_backup", text["server"]):
raise SystemExit("dwh-auth docs: restore must not overlay the live registry")
journal_steps = (
"subprocess.Popen(",
"[\"journalctl\", \"-u\", \"dwh-auth\", \"--since\", sys.argv[1]",
"sys.argv[2:]",
"stderr=subprocess.DEVNULL",
"stdout=subprocess.PIPE",
"max_journal_bytes = 1_048_576",
"max_journal_lines = 10_000",
"process = None",
"except OSError:",
"max_chunk_bytes = 65_536",
"process.stdout.read1(",
"remaining = max_journal_bytes - bytes_seen",
"searchable = carry + chunk",
"max_needle_length",
"process.kill()",
"if process is not None:",
"process.wait()",
"if process.returncode != 0:",
"any(needle in searchable for needle in needles)",
)
if any(step not in text["server"] for step in journal_steps):
raise SystemExit("dwh-auth docs: journal scan must fail closed and check the actual key bytes")
print("dwh-auth documentation contract passed")
PY
exec python3 "$root/scripts/verify-auth-docs.py" dwh "$@"
+1
View File
@@ -24,6 +24,7 @@ INTERNAL_PAGES = {
"disambiguazione-iniziale.md", "gestione-memory.md", "installazione-docker-4-contesti.md",
"install/authentication-upstream.md", "operations/docker-refresh.md",
"operations/shell-and-localization.md",
"operations/compose-reference.md", "operations/public-docs-publication.md",
}