6.3 KiB
Workspace preprocessing CLI contract
thothctl workspace is a closed native host interface. Only the seven paths below are representable; there is no passthrough, shell, arbitrary child argv, or host-selected bootstrap run ID.
workspace inspect --workspace ID [--json]
workspace preprocess dwh --workspace ID [--resume RUN] [--json]
workspace schema suggest-fks --workspace ID [--from-sql FILE]... [--assume COLUMN=TABLE]... [--output FILE] [--json]
workspace schema check --workspace ID --resume RUN [--annotations FILE --reviewed-candidates sha256:HEX] [--json]
workspace index-schema --workspace ID [--json]
workspace preprocess evidence --workspace ID [--dry-run] [--resume RUN] [--json]
workspace preprocess run --workspace ID [--resume RUN] [--json]
IDs are lowercase [a-z][a-z0-9-]{2,62} and outer run IDs are exactly 32 lowercase hexadecimal characters. --from-sql accepts at most 32 canonical regular non-symlink files, each at most 1 MiB and 16 MiB total. --assume accepts at most 256 values of 256 bytes, each matching column=table. Annotations are one canonical UTF-8 file at most 16 MiB. Existing output, links, hardlinks, directories, replacement races, and reparse points are refused with a generic unsafe-file error.
The bounded schema-v1 stdin envelope is exact: omitted fields are not equivalent to explicit zero, empty, or null fields. Its fields are, per operation (in addition to the always-required schemaVersion:1, operation, and workspaceId):
inspect: no additional fields.dwh:resumeonly when--resume RUNwas supplied.run:resumeonly when--resume RUNwas supplied.evidence:resumeonly when supplied, anddryRunonly when--dry-runwas supplied.suggest-fks:sqlonly when one or more--from-sqlfiles were supplied, andassumeonly when one or more--assumevalues were supplied. Eachsqlitem is exactly{basename, contentBase64, sha256}.check:resumeis required;annotationsandreviewedCandidatesare either both present or both absent.annotationsis exactly{basename, contentBase64, sha256}.index-schema: no additional fields.
No other fields, duplicate JSON value, host path, raw SQL, or raw annotation content are accepted. SQL and annotations use a logical basename, base64 bytes, and a declared sha256:<hex> digest. The request envelope is independently capped at 24 MiB (after JSON/base64 encoding), enough to carry the frozen 1 MiB-per-file/16 MiB aggregate raw ingress bounds; every supplied field/value must exactly match the command-derived envelope. Candidate responses may carry the internal hostExport (mediaType, sha256, contentBase64) only for suggest-fks; its object is strict (unknown fields rejected) and is always verified, even without --output: YAML media type (application/yaml or text/yaml), UTF-8, digest, and decoded size at most 700 KiB. It is removed from the public result and written exclusively only after result/run/identity validation. The exact once-encoded final stdout bytes, including JSON keys and human chrome, are scanned for every declared nonempty secret before publication. Output publication uses restrictive mode 0600 and refuses existing leaves, links, hardlinks, directories, replacement races, and reparse points. On Linux, publication uses an anonymous O_TMPFILE inode and linkat(..., AT_EMPTY_PATH); the link operation is the final commit, and no post-commit check can turn success into a not-published error. On Darwin, the named-stage implementation is a trusted-parent mode: the target parent and ancestors must remain namespace-stable and same-UID stage mutation is explicitly outside the threat model. It verifies the stage identity/link count before using renameatx_np(..., RENAME_EXCL) as the final no-replace commit. It does not claim protection against a same-UID hostile hard-linker. On Windows, the stage is held open with DELETE|WRITE and zero sharing, then renamed atomically with SetFileInformationByHandle(FileRenameInfo) rooted at the retained parent handle; replacement is disabled and the rename is final.
The public result has schema version 1 and only these fields: status, code, workspace/revision/descriptor/operation identities, optional run and child run IDs, completed stages, counts, artifact identities, and warnings. Revisions/descriptors are 40-hex; run IDs are 32-hex; artifact digests are sha256:<hex>. Allowed statuses are succeeded, unchanged, dry_run, blocked, and failed. Allowed codes are ok, workspace_not_found, workspace_not_activatable, binding_missing, preprocessing_conflict, preprocessing_resume_mismatch, manual_review_required, evidence_materialization_required, effective_config_mismatch, semantic_index_incompatible, annotation_invalid, egress_policy_refused, and registry_bootstrap_recovery_conflict. succeeded, unchanged, and dry_run require ok and child exit 0. blocked requires one of manual_review_required, evidence_materialization_required, preprocessing_conflict, preprocessing_resume_mismatch, or registry_bootstrap_recovery_conflict, and child exit 3. failed requires a non-ok operational code other than those blocked-only codes, and child exit 1. A nonzero child exit is never accepted for another status/code combination. The public thothctl exit mapping is fixed independently of child details: 0 for succeeded/unchanged/dry-run, 3 for an expected blocked result, 2 for command grammar or proven unsafe host-file failures, and 1 for operational or indeterminate failures (including invalid child envelopes, output-limit failures, child execution failures, and candidate cleanup uncertainty). A physical stdout write failure after candidate publication is a committed/indeterminate reconcile case; inspect the destination and private stages before retrying. Stdout is capped at 1 MiB after final human/JSON encoding and stderr at 64 KiB after sanitization; output is never allowed to exceed those bounds. In human mode, a registry_bootstrap_recovery_conflict result prints exactly Bootstrap recovery is ambiguous or corrupt; inspect the installation registry jobs. and prints neither a candidate export nor any run/candidate ID. Compose is invoked only as compose run --rm --no-deps --no-TTY workspace-maintenance ...; output never includes child stderr or secrets.